TypeScript 入门学习笔记

492 阅读5分钟

经常听到小伙伴说 Angular 很麻烦,又要学 RxJS,又要学 TS。讲道理,RxJS 理解起来确实需要时间,但是 TS 使用起来应该不会很难,特别是从工具使用者的角度来说,应该不太难理解才对。

TS 中的几个概念

  • TS 是 JS 的超集。或者说,JS 也是 TS。如何理解呢?比如说,你把任意一个 JS 文件后缀换成 TS,用 TS 的方式运行,跟直接运行 JS 是一样的。
    // 以下代码在 TS 中运行也是成立的。
    const a = 1;
    function add(a, b) {
      return a + b;
    }
    
  • TS = JS + Type, 也就是说,TS 是加上了类型约束的 JS。
    const a :number = 1;
    
    function add(a: number, b: number) {
      return a + b;
    }
    
  • TS 的类型并不是运行时,而是主要为编辑器服务的,编译时。试图在运行时或者变量的类型是无法被保证的(除了 JS 中支持的判断类型的方法)

TS 中类型的使用

  • JS 中的基本类型:
    let a: number
    let b: string
    
  • Function 的参数和返回值
    function func(a: string): string {}
    
    (a: string): string => {
    }
    
  • 当然,因为 JS 中 Function 也是 Object,本省也就是一种类型的 Object
    let func: (a: string) => string
    // 注意,这里定义 Func 的类型和 arrow function 是不同的
    
  • 除了基本类型以外,跟其他语言一样,class 当然也是一种类型:
    class Test {
    }
    
    const test: Test = new Test();
    
  • 我们知道 JS 中是可以直接使用 {} 来创建对象的,而非一定需要借助于 class。对于 object, 我们可以直接给 object 定义类型。
    const test: {
      a: number;
      b: number;
    } = {
      a: 1,
      b: 2
    };
    
    // 当然也可以给它取个名字,通过 `interface` 关键字
    interface Test {
      a: string;
      b: string;
    }
    
    // 或者 `type` 关键字
    type Test {
      a: number;
      b: number;
    }
    
  • 数组也是类似的:
    const data: string[]
    
  • 类型不仅仅可以是基础类型,class, function,interface, 它甚至可以是具体的值
    const a: 1 = 1;
    
    // 当然,也可以是多个值
    const a: 1 | 2 | 3 = 1;
    
    // 很自然的 | 两边可以是各种类型
    const a: 1 | string | (arg: any[]) => void
    

TS 中的类型推断

TS 并不像某些强类型语言一样,必须要清楚的指定变量的类型,TS 在一些条件下可以动态的推荐类型。

  • 变量根据赋值内容推断类型:

    let a = 1
    a = '' // Type 'string' is not assignable to type 'number'
    
  • Function 根据代码推断返回类型

    function add(a: number, b: number) {
      return a + b;
    }
    
    let c: string = add(1, 2); // Type 'number' is not assignable to type 'string'
    

除了以上对于省略类型的推断,TS 代码逻辑中进行类型推断,以便缩小变量的 scope, 主要针对 if block 中的类型进行推断。

逻辑也很简单,如果 if 条件中,如果能够明确类型,在当前 block 中,会以推断的类型使用。因为 if 是具体的业务逻辑,所以,它一定也是满足 JS 语法的代码。所以 JS 中,能够判断类型的方法不外乎以下几种:

  • typeof 用来判断 JS 中的类型 number / string / function / object
    function addOne(a: number | string) {
     if (typeof a === 'string') {
       return a + '_1'; // (parameter) a: string
     }
    
     return a + 1; // (parameter) a: number
    }
    
  • empty check。当然我们也可以通过 if 来处理 block 中是否为空。
    function addOne(a?: number | string) {
      if (a) {
        console.log(a); // (parameter) a: string | number
      }
    }
    
  • instanceof 判断 class
    typeof 类似,不做赘述。
    
  • in 判断 interface。实际上,在运行时,代码并不知道任何 interface 相关的信息,那么我们如何区分不同的 interface 呢。答案是差异字段。
    interface A {
      a: string;
    }
    
    interface B {
      a: string;
      b: string;
    }
    
    function testFunc(param: A | B) {
      if ('b' in param) {
        console.log(param); // (parameter) param: B
      }
    }
    
  • 当然,字段差异其实不仅仅是「有」,「没有」的差异,也可以是类型差异,前提是用来差异的字段必须是 union type:
    interface A {
       type: 'a';
       a: number;
     }
    
     interface B {
       type: 'b';
       a: number;
     }
    
     function testFunc(param: A | B) {
       if (param.type === 'a') {
         console.log(param); // (parameter) param: B
       }
     }
    

类型操作(基于一个类型,创造另一个类型)

类型编程。如果说,TS 是给 JS 加上了类型的话,其实是不够全面的,TS 的强大在于,类型本身,也是可编程的。也就是说,我们可以基于一定的输入类型,创造出一个新的类型。当然,可以理解成,对于范型的编程。举个最简单的例子,我们需要写一个方法,修改 object 的 property 的值:

// 常规编程语言能做到的类型约束
function updateProperty<T>(obj: T, key: string, value: any):void {
}

这里的问题,很明显,既然已经能够明确当前 obj 的类型,在 key,value 上是否可以给予明确的类型约束呢?

function updateProperty<
  T extends object, 
  K extends keyof T, 
  V extends T[K]>(obj: T, key: K, value: V) {}

const obj: B = { type: 'b', a: '111' };
updateProperty(obj, 'a', 1111); // Argument of type 'number' is not assignable to parameter of type 'string'.

TS 中为类型编程,当然核心是 create types。

  • 范型 (类型编程的基础)
  • keyof
  • typeof
  • indexed access types
  • conditional types
  • mapped types
  • template literal types

很容易看出来,所谓类型编程,核心是对于 object 的类型编程

  • 获得 object 所有 key 的类型: keyof
    interface A {
      type: 'a';
      a: number;
    }
    
    type KeyOfA = keyof A;
    const key: keyof A = 'a';
    
  • 获得 object key 对应 value 的类型: T['a'] (indexed access types)
    interface A {
      type: 'a';
      a: number;
    }
    
    let a: A['a'] = '11'; // Type 'string' is not assignable to type 'number'
    
  • 根据 key,value 的类型和对应关系,创造 object 类型 : mapped types
    {
      [key: string]: string
    }
    
    // 当然,key 也可以是一组 string / number / symbol (union type)
    {
      [key in 'a' | 'type']: string
    }
    
    // 很容易想到,我们可以根据其他类型生成 object 类型
    type Test = {
      [key in keyof A]: A[key]
    }
    

非 object 相关

  • 动态获得变量类型:typeof
    const currentValue: string = '';
    type CurrentType = typeof currentValue; // type CurrentType = string
    
  • 类型的条件判断:conditional types
  • 模版类型:template literal types