Leon's blog Leon's blog
首页
前端
场景
技术
更多
关于
收藏
索引
GitHub (opens new window)

Leon Yu

做一个有个性的开发者~
首页
前端
场景
技术
更多
关于
收藏
索引
GitHub (opens new window)
  • 技术
  • TypeScript

    • 前言
    • 教程中的难点
      • 教程中的难点2
      • 教程中的难点3
    • NodeJS

    • shell

    • vscode开发

    • 图解HTTP阅读

    • 计算机网络

    • 技术
    • TypeScript
    leon yu
    2024-05-19
    目录

    教程中的难点

    提示

    本文整理自 TypeScript 官方文档中的 handlebook,版本为 V5.4

    # TypeScript 基础

    • 静态类型检查
      • Typescript 会检查类型是否符合
      • Typescript 会检查参数值是否符合预期
    function greet(person, date) {
      console.log(`Hello ${person}, today is ${date}!`)
    }
    
    greet("Brendan") // 提示缺一个参数
    
    1
    2
    3
    4
    5
    • 类型注释永远不会改变程序运行时的行为
    • 默认情况下,可以使用 any,并且可以将 null 和 undefined 赋值给任何其他类型
    • 可以使用 use strict 来改变 Typescipt 检验类型的程度

    # 常见的 TypeScript 类型

    • 最原始的类型:string number boolean
    • 指定数组类型的方法
      • arr: number[]
      • arr: Array<number>
    • any 是特殊类型,如果不希望特定值导致类型检验错误可以使用 any
    • TypeScript 允许制定 Functions 的输入和输出值的类型
    • 指定返回 Promise 的函数
    async function getFavoriteNumber(): Promise<number> {
      return 26
    }
    
    1
    2
    3
    • 在函数作为回调函数而匿名时,ts 会自动对其进行转义
    • 对象类型
      • obj: { first: string, second: number }
      • 在对象类型的属性后加入?表示可选属性
    • 使用 | 可以连接两个符号
    • 类型别名(type alias)
      • type Point = { x: number; y: number }
      • type alias 只是其他类型别名,并不是创造了一种新的类型,可以理解为引用了某种类型
    • 接口声明(interfaces)
      • 也可以用以声明对象的属性,大部分情况下,interface 和 type 是可以混用的
    • interface 和 type 的区别
      • type alias 不可以重新打开加入新的属性,接口始终是可以扩展的
    • 类型断言
      • 可以使用类型断言来帮助 ts 理解对应的类型,但是如果判断错了,ts 将不会生成异常或者 null
    • 可列举的类型
      • 可以在类型中引入特定的字符串和数字
      • boolean 实际上只是联合了 true 和 false 的类型
      • 使用 as const 将会使一个对象被转化为一个可列举的对象类型
    • null 和 undefined
      • 开启 strictNullChecks 后不允许 undefined 和 null 赋值给任何类型
      • 非空断言运算符,x!.toFixed()
    • bigInt 和 symbol 不太常见

    # 类型缩窄

    • 通常,我们需要使用 if - else 来缩小类型的范围
    funciton padLeft (padding: number | string, input: string): string {
      if (typeof padding === 'number') {
        return ' '.repeat(padding) + input
      }
      return padding + input
    }
    
    1
    2
    3
    4
    5
    6
    • 使用 typeof 来规避 null 或 undefined 存在 typeof null === 'object' 的问题,官方更加推荐使用 !variable 来判断对应的变量是否为 null 或者 undefined
    • Typescript 还会根据等号条件(==, !=, ===, !==)来进行类型范围的缩小
    • TypeScript 会根据 in 关键字来缩小类型检查的范围
      • in 关键字会检查某个属性是否出现在一个对象的原型链上
    • instanceof 也可以帮助 TypeScript 缩小类型范围
    • 在进行第一次赋值时,TypeScript 会对变量进行合适的类型缩小
    • 在开头的代码中,TypeScript 实际上用到了控制流分析,第一个 if 后,padding 就只能是 string 了,所以可以在控制流的情况下限制了 padding 的类型
    • 类型谓语
      • 使用类型谓语可以进行类型守卫,常见用法如下
    interface Bird {
      fly: () => void
    }
    
    interface Fish {
      swim: () => void
    }
    
    function isFish(pet: Fish | Bird): pet is Fish {
      return (pet as Fish).swim !== undefined
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    在使用类型谓语时,实际上已经已经默认返回值为 boolean 类型了,所以不需要指定返回值的类型。 类型谓语的作用在于提前缩小参数的范围,从而使用更简单的逻辑来判断参数是否为某个类型。

    • 在已经消除了所有可能性之后,ts 使用 never 表示剩余的类型可能性。

    • 场景题

    /**
     * @description 设想一个场景,要定义一个可能是圆形也可能是正方形的类型,如何书写类型以便于更好的求得面积
     */
    function getArea(shape) {
      switch (shape.kind) {
        case "circle":
          return Math.PI * shape.radius * shape.radius
        case "square":
          return shape.side * shape.side
        default:
          throw new Error("unexpected shape")
      }
    }
    // 测试
    var shape = {
      kind: "circle",
      radius: 10,
    }
    console.log(getArea(shape)) // 314.1592653589793
    var shape2 = {
      kind: "square",
      side: 10,
    }
    console.log(getArea(shape2)) //100
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    # 函数专题

    • 通常使用类似于箭头函数的形式来描述一个函数的类型
      • fn: (a: string) => string
      • 以上的写法无法描述函数的属性,例如 description
    // 定义函数的属性
    type DescribableFunction = {
      description: string
      (someArg: string): boolean // 此处不能使用箭头,而是使用 :
    }
    
    function doSomething(fn: DescribableFunction) {
      console.log(fn.description + " " + fn("foo"))
    }
    
    function isFoo(someArg: string) {
      return someArg === "foo"
    }
    
    isFoo.description = "isFoo"
    doSomething(isFoo)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    • 构造函数同样也可以使用 TypeScript 进行类型声明
    type someConstructor = {
      new (s: string) => someObject
    }
    function fn(ctor: someConstructor) {
      return new ctor('hello')
    }
    
    1
    2
    3
    4
    5
    6
    • 有些函数既可以使用 new 关键字调用,也可以直接调用,于是可以组合
    interface CallOrConstruct {
      (n?: number): string
      new (s: string): Date
    }
    
    1
    2
    3
    4
    • 泛型
    function firstElement<type>(arr: type[]): type | undefined {
      return arr[0]
    }
    
    console.log(firstElement(["1", "2", "3", 1]))
    console.log(firstElement([1, 2, 3]))
    console.log(firstElement([]))
    
    1
    2
    3
    4
    5
    6
    7
    • 要在函数名后面加 <TypeName> 来声明这是一个泛型函数,传入的类型和返回值可以根据这个泛型函数运算
      • 泛型的传入可以不止一个
    • 在使用泛型时,可以使用相应的声明来对传入的类型做一次类型检查
    function longest<Type extends { length: number }>(a: Type, b: Type) {
      if (a.length > b.length) {
        return a
      }
      return b
    }
    
    const longerArray = longest([1, 2], [1, 2, 3])
    const longerString = longest("alice", "bob")
    // const notOk = longest(1, 2) // 1没有length属性,ts报错
    console.log(longerArray, longerString)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 使用泛型约束时返回的类型必须是传入的类型之一,而不是与目标匹配的类型
    function minimumLength<Type extends { length: number }> {
      obj: Type
      minimum: number
    }: Type {
      if (obj.length >= minimum) {
        return obj
      }
      // 这里会报错,因为 { length: minimum } 虽然符合限制,但是却不是传入的类型
      return { length: minimum }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    • 在调用包含泛型的函数时,可以手动声明函数的泛型
    function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
      return arr1.concat(arr2)
    }
    // const arr = combine([1,2,3], ['1']) // 将会报错,因为第一个参数决定了类型为number,而第二个参数又为string
    const arr = combine<number | string>([1, 2, 3], ["1"]) // 不会报错,手动声明了泛型的范围
    
    1
    2
    3
    4
    5
    • 如何写一个合适的泛型

      • 使用 type 本身,而不是约束,如使用 type 而不是 type extends any
      • 使用尽可能少的泛型参数
      • 泛型参数总是要出现两次,一次是传入,一次是传出,如果只有一次需要这个类型,也许我们不需要泛型
    • 在回调函数中加入可选参数需要进一步限制类型,尽可能不要在回调函数中传入可选参数

    • 重载签名

    function makeDate(timestamp: number): Date // 重载签名1
    function makeDate(m: number, d: number, y: number): Date // 重载签名2 需要与重载签名1兼容
    function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
      // 实现签名,需要与前两个重载签名兼容
      if (d !== undefined && y !== undefined) {
        return new Date(y, mOrTimestamp, d)
      } else {
        return new Date(mOrTimestamp)
      }
    }
    
    // 此时 makeDate 函数可以传入一个参数或者传入3个参数
    makeDate(12345678) // 传入一个参数
    makeDate(1, 2, 3) // 传入3个参数
    // makeDate(1, 2) // 传入2个参数将会报错,因为没有定义传入两个参数的重载函数
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    • 编写重载的规则

      • 尽可能使用联合类型的参数,而不是重载函数
    • 在函数中声明 this 的类型

    interface User {
      id: number
      admin: false
      becomeAdmin: () => void
    }
    
    interface DB {
      ... // 其他DB的属性
      filterUsers(filter: (this: User) => boolean): User[];
    }
    
    const user = {
      id: 1,
      admin: false,
      becomeAdmin: function () {
        this.admin = true
      }
    }
    
    const db: DB = getDB()
    db.filterUsers(function (this: User) { // 如果涉及到 this,务必使用 function 而不是箭头函数
      return this.admin
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    • 其他需要了解到类型

      • void 表示不返回值的函数的返回值
      • object (并非是 Object)表示任何不是基元的值
      • unknown 可以接受任何类型的值,但是 unknown 不能接受任何操作
      • never 表示会报错或者终止程序的返回值,或是确定联合中已不剩任何内容
      • Function 描述了全局函数类型
    • 剩余参数

    // 使用展开运算符传入剩余参数
    function multiply(n: number, ...m: number[]) {
      return m.map((x) => n * x)
    }
    
    // 同时也可以使用展开运算符传入参数
    // TypeScript 默认数组不是不可修改的,因此会有一些需要注意的地方
    const args = [8, 5]
    const angle = Math.atan2(...args) // 扩张参数必须具有元组类型或传递给 rest 参数。
    
    const args2 = [8, 5] as const // 使用 const 声明确保此数组不会发生变化
    const angle2 = Math.atan2(...args2) // 此时不会再报错
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    • 参数结构
    // 写法1
    function sum({ a, b, c }: { a: number; b: number; c: number }) {
      return a + b + c
    }
    
    // 写法2
    type ABC = {
      a: number
      b: number
      c: number
    }
    
    function sum({ a, b, c }: ABC) {
      return a + b + c
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    • void 返回值
    // 通常定义返回值类型为void的函数可以返回值
    type voidFunc = () => void
    
    const f1: voidFunc = () => {
      return true
    }
    
    const f2: voidFunc = () => true
    
    const f3: voidFunc = function () {
      return true
    }
    
    // literal function 定义了void返回值但是返回将会报错
    function f(): void {
      return 1 // 不能将类型“number”分配给类型“void”。
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    最近更新~: 2024/08/19, 01:05:55
    前言
    教程中的难点2

    ← 前言 教程中的难点2→

    最近更新
    01
    应用层
    01-11
    02
    计算机网络和因特网
    12-22
    03
    关于
    12-22
    更多文章>
    Theme by Vdoing | Copyright © 2024-2025 主题来自 Evan Xu | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式