Published on

TypeScript的笔记(整个梗概)

Authors
  • avatar
    Name
    Et cetera
    Twitter

1.1 为什么需要 TypeScript,以及相较于 Javascript 解决的痛点

  • JavaScript 往往在项目编译后才能发现错误,而 TypeScript 通过类型的限制能够做到和大部分强 类型语言一样在编写阶段提出错误

    function getLength(arg) {
      return arg.length
    }
    
    getLength(123) // 只会在运行后报错,实际项目中会阻塞项目执行
    
  • 而 TypeScript 提供了类型检验情况下在代码编写阶段提供了规范,尤其方便大型项目的开发,以及多人 协作开发情况下.同时还有提供的类型检验为第三方库的 API 提供了非常好的类型注解

    function getLength(arg: { length: number }) {
      return arg.length
    }
    getLength(123) // 会直接提示错误
    
    TypeScript 和 JavaScript 的关系图

1.2 TypeScript 的运行环境

  • 一般需要两个步骤
    1. 通过 tsc 把 TS 编译成 JS 代码
    2. 在浏览器或者 Node 环境下运行 JS 代码
  • 简化过后两种方式:
    • 通过 webpack(主要:ts-loader),配置本地的 TS 编译环境和开启一个本地服务,可以直接运行在浏览器上
    • 通过 ts-node 库,为 TS 的运行提供执行环境
      • npm i ts-lib @types/node -D

2.1 TypeScript 的类型

基本类型

  • 字符串类型和数字类型

    let band: string = 'OOR' // 描述类型时用字符串类型用首字母小写的 string
    let count: number = 10969 // 数字类型
    // 在 TS 中 undefined 和 null各自的类型也是 undefined 和 null,意味着他们既
    // 是实际的值也是自己的类型(有点字面量类型的意思)
    let und: undefined = undefined
    let n: null = null
    const height = 1.85 // 此时 height 的类型为 1.85,成为字面量类型
    
  • 数组类型

    // 两种定义方式
    let arr: string[] = ['taka', 'toru']
    let arr1: Array<string> = ['ryota', 'tomoya'] // 通过泛型定义
    // 注意:在真实开发中,数组一般存放相同的数据类型,同时在以上两种数组定义方式后,对数组的增删改查的参数也只能是string类型
    // 例如下面这样就不行
    arr.push(10969) // 10969 为 number 类型,而 arr 为每个元素都为字符串的数组,所以 TS 会抛出异常
    // 也可以直接通过类型推导
    let arr2 = ['aimyon', '爱缪']
    
  • 对象类型

    let obj: {
      band: string
      // band:代表obj 对象必须要有的 key ,string: 限制该 key 的 value 的类型
      year?: number
      // year?: key 后面加 ? 表示可选类型, obj 这个对象可以没有year这个属性,即不是必须
    } = {
      band: 'ONE OK ROCK',
    }
    
    • 声明一个对象类型有两种方式,类型别名type和接口interface

      • // 类型别名方式
        type Obj = {
          type: string
        }
        
        // 接口方式
        interface Obj1 {
          type: string
        }
        
      • 区别: 大部分情况下,两种方式可以任意选择没有区别

  • 函数定义类型

    • 函数类型表达式

      // function sum(num1: xxx, num2: xxx): void {}
      // xxx 代表函数行参所接受的类型,即传递实参必须遵守该类型
      // void 表示函数没有返回值,同理可以设置为 number 等基础类型
      function sum(num1: number, num2: number): void {}
      
      // 另一种方式
      // 因为函数也是一种对象,所以可以直接赋值一种类型
      type FooType = (num1: number, num2: number) => void
      
      function calc(fn: Footype) {
        console.log(fn(10969, 3636))
      }
      
      const foo: FooType = (num1: number, num2: number) => {}
      
    • 调用签名

      • 从对象角度来看待一个函数,也可以有其他属性

        interface Foo {
          name: string
          age: number
          // 函数可以调用: 函数调用签名
          (num1: number): number
        }
        
        const foo: Foo = (num1: number): number => {
          return 109696
        }
        foo.name = 'oor'
        foo.age = 3636
        
    • 如何选择怎么定义函数类型

      1. 如果只是描述函数类型本身(函数可以被调用),使用函数类型表达式
      2. 如果在描述函数作为对象可以被调用,同时也有其他属性时,使用函数调用签名
    • 函数参数类型

      • 函数的参数可以有默认值
        1. 有默认值的情况下,参数的类型注解可以省略
        2. 有默认值的参数是可以接收一个 undefined 的值
      • 剩余参数
        function sum(...nums: number[]) {
          let total = 0
          for (const num of nums) total += num
          return total
        }
        
    • 对象/函数中的 this

      • 通过 tsconfig.json 中的一个配置开启是否支持模糊 this 指向
        "noImplicitThis": true // 默认为false,开启为true
        
      • TS 内置的两种 this 工具(都是通过泛型使用)
        • ThisParameterType:获取 FooType 类型中 this 的类型ThisParameterType<FooType>
        • OmitThisParameterType:删除 this 参数类型,剩余的参数类型OmitThisParameterType<FooType>
        • ThisType
  • TS 的特殊类型

    • any 类型

      • any 类型表示不限制标识符的任意类型
      • any 类型做任何事情都是合法的
    • unkonwn 类型

      • 类似于 any 类型
      • 但是 unknown 类型的值做任何事情都是不合法的
        // 但是类似于这样重新赋值是可以的
        let a: unkonwn = 'oor'
        a = 10969
        // 只是不能对他做任何操作,简单说就是不能调用任何方法
        // 比如直接进行
        {
          /* console.log(a.length) // 因为TS 不确定unknown 类型下的该标识符到底是什么类型,所以需要对unknown类型进行操作(此处为取 a 的 length)时,需要对其先进行类型缩小 */
        }
        if (typeof a === 'string') {
          console.log(a.length)
        }
        
    • void 类型

      • 通常用于指定函数没有返回值,具体操作在函数定义类型中
      • 当基于上下文的类型推导,推导出返回类型为 void 的时候,并不会强制函数一定不能返回内容
    • never 类型:表示永远不会发生值的类型

      • 如果一个函数中是一个死循环或抛出异常,那么这个函数肯定不会返回东西,所以用 void 类型或其他具体的类型作为返回值类型都不太合适,这时候就可以用 never 类型
      • 使用场景:
        • 一个函数如上是一个死循环或者返回异常
        • 封装框架/工具库的时候
    • tuple(元组) 类型

      • 特点:元组数据结构中可以存放不同的数据类型,取出来的 item 也是有明确的类型的

      • 对比数组: 虽然数组也可以通过联合类型定义可以存放不同数据类型的数组,但却无法确定每个 item 的具体数据类型

      • {
          /* 元组的定义 */
        }
        const info: [string, number, string, number] = ['oor', 10969, 'aimyon', 3636]
        const value = info[1] // value: number
        
      • 应用:

        {
          /* 使用泛型前,暂时不对initailState做太多类型操作 */
        }
        function useState(initialState: number): [number, (number) => void] {
          let stateValue = initialState
          function setValue(newValue: number) {
            stateValue = newValue
          }
          return [stateValue, setValue]
        }
        
  • TS 的类型操作

    • 联合类型

      • TS 的类型系统允许我们使用多种运算符,从现有类型中构建新类型
      • 通过逻辑运算符的或 |,将多种类型联合,使一个标识符可以使用多种类型
        let band: number | string = 'oor'
        
      • 但是由于band变量可能是number可能是string,为number时不能使用字符串的方法,于是需要特殊情况进行缩小类型
      • 类型缩小:
        if (typeof band == 'number') {
          console.log('band成立多少年:', band)
        } else {
          console.log('band名是:', band.toUpperCase())
        }
        
    • 交叉类型

      • 通过逻辑运算符的或 &,使得要满足两种或多种类型
    • 类型别名type

      • 当想要多次使用在类型注解中编写的同一种类型时,需要重复编写多次,于是可以使用类型别名定义一次类型,多次使用

      • 实例:

        type IDType = number | string
        
        let id: IDType = 10969
        function printID(id: IDType) {
          console.log(id)
        }
        
    • 接口interface

    • typeinterface的区别:

      1. type使用类型更广,接口类型只能用来声明对象

      2. interface声明对象时,可以多次声明

        1. type不允许两个相同名称的别名同时存在

        2. interface可以多次声明同一个接口名称

          interface Obj {
            a: number
          }
          
          interface Obj {
            b: number
          }
          
          let obj: Obj = { a: 1, b: 2 }
          
        3. interface支持继承

          interface Band {
            vocal: string
            guitar: string
          }
          
          interface Member extends Band {
            bassist: string
            durmmer: string
          }
          
          const oor: Member = {
            vocal: 'vocal',
            guitar: 'guitar',
            bassist: 'bassist',
            durmmer: 'durmmer',
          }
          
        4. interface可以被类实现

      3. 所以一般开发过程中声明对象类型时候用interface,其他时候用type

    • 类型断言as

      • 有时候 TS 无法获取具体的类型信息,这时候需要使用类型断言
        • 比如通过 document.getElementById, TS 只知道该函数会返回 HTMLElement,但不知道返回的具体类型
      • 但是 TS 只允许断言成更具体的类型,或不具体(any/unkonwn)的类型(由大到小,由小到大,尤其由小到大不能丢弃小)
    • 非空类型断言

      interface Obj {
        foo: number
        bar?: {
          a: number
        }
      }
      
      const obj: Obj = {
        foo: 1,
      }
      // 访问:可选链调用
      console.log(obj.bar?.a)
      
      // 属性赋值:此时不能用可选链
      // 方式一:类型缩小
      // 方式二:非空类型断言(有点危险,比如此时就没有a这个属性)
      obj.bar!.a = 123
      
    • 类型缩小

      • typeof
      • switch
      • instanceof

TypeScript 的类型推导

  • 案例:
    // 声明一个标识符时,如果有进行赋值,会根据赋值的类型推导出标识符的类型注解
    let singer = 'aimyon'
    singer = 'taka' // singer 类型是 string
    singer = 3636 // 会报错,不能将 number 类型赋值给 string 类型