likes
comments
collection
share

初识TypeScript

作者站长头像
站长
· 阅读数 29

注意:本文章为TypeScript4 官方文档翻译阅读笔记

入门

  1. ts 的好处
    1. 静态检查,在运行之前就会报错
    2. 捕捉 js 中非异常的错误:比如读取对象不存在的属性不会报错,而是 undefined
    3. 提示可能操作的属性
  2. ts 写法并不会影响 js 的运行结果
  3. 编译后的 ts 文件并不会包含任何类型相关的代码,因为浏览器不识别 ts
  4. tsc 默认将 ts 代码转换为 ES3 ,目前大多数浏览器已经支持了 ES6 可以执行 tsc --target es2015 hello.ts 设置编译后的目标版本。
  5. noImplicitAny 当类型被隐式推断为 any 时,会抛出一个错误。
  6. strictNullChecks 决定 nullundefined 的行为。打开的情况下在操作之前需要先判断下是否存在(类型收窄),没打开的情况下可以被正确的访问,或者被赋值给任意类型的属性(可能会产生 bug

基础的类型

  1. 原始类型:stringnumberboolean

  2. 数组:number[] 或者 Array<number>

  3. any 简而言之就是不做类型检查

  4. 使用 letconstvar 声明变量的时候不是必须进行类型注解,ts 会根据初始值做出正确的类型推断

  5. 函数类型注解: function functionName(params:paramsType):returnType{},没必要明确 returnType ,因为 ts 会根据 return 语句推断正确的类型

  6. 对象类型:只需要简单的列出它的属性和对应的类型。

    1. 可选属性:{prop?:propType} ,操作可选属性前判断属性是否存在:obj.prop?.doSomething
  7. 联合类型:基于已存在的类型构建新的类型。params:string | number 表示 params 可以是 stringnumber 其中任何一种类型

    1. 虽然 params 的值是满足 stringnumber 其中任何一种类型即可,但是对于 params 的操作,必须是 stringnumber 共有的,比如不能调用 params.toUpperCase() 因为 number 上不存在 toUpperCase 方法
  8. 类型别名:为联合类型或者对象类型创建一个唯一的名称: type typeName = string|number

  9. 接口:命名对象类型的另一种方式:interface Animal {prpName:type}

  10. 类型别名和接口的不同:

    1. 类型别名无法添加新特性(声明同名类型会报错),接口可以添加新特性(直接声明同名接口,内容会被合并)
    2. 接口只用来声明对象的形状,不能重命名原始类型,类型别名可以
  11. 类型断言:当开发者比 ts 更加了解类型时,可以使用类型断言

    1. 因为类型断言会在编译的时候被移除,所以运行时并不会有类型断言的检查,即使类型断言是错误的,也不会有异常或者 null 产生。
    2. 使用 as 进行类型断言:const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
    3. 使用尖括号进行类型断言:const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas"); 但是在 jsx 中不能用
    4. 仅仅允许类型断言转换为一个更加具体或者更不具体的类型。这个规则可以阻止一些不可能的类型指定:const x = "hello" as number; 则不行。一定要这样的话可以使用双重断言:const a = (expr as any) as T;
  12. 字面量类型:当函数只能传入某些固定值时:function printText(s: string, alignment: "left" | "right" | "center") {}

    1. 字面量推断:通常 ts 会将声明的变量设置为更加宽泛的类型,而不会推断为字面量类型:

      declare function handleRequest(url: string, method: "GET" | "POST"): void;
      
      const req = { url: "https://example.com", method: "GET" };
      handleRequest(req.url, req.method);
      
      // Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
      

      req.method 被推断为 string ,而不是 "GET",因为在创建 req 和 调用 handleRequest 函数之间,可能还有其他的代码,或许会将 req.method 赋值一个新字符串比如 "Guess" 。所以 TypeScript 就报错了。

      有两种方式可以解决:

      1. 添加一个类型断言改变推断结果:

        // Change 1:
        const req = { url: "https://example.com", method: "GET" as "GET" };
        // Change 2
        handleRequest(req.url, req.method as "GET");
        

        修改 1 表示“我有意让 req.method 的类型为字面量类型 "GET",这会阻止未来可能赋值为 "GUESS" 等字段”。 修改 2 表示“我知道 req.method 的值是 "GET"”.

      2. 使用 as const 把整个对象转为一个类型字面量:

        const req = { url: "https://example.com", method: "GET" } as const;
        handleRequest(req.url, req.method);
        

        as const 效果跟 const 类似,但是对类型系统而言,它可以确保所有的属性都被赋予一个字面量类型,而不是一个更通用的类型比如 string 或者 number

  13. 非空断言操作符:在任意表达式后面写上 ! ,是一个有效的类型断言,表示它的值不可能是 null 或者 undefinedconsole.log(x!.toFixed())。注意只有在开发者确定表达式不为nullundefined 的时候使用

  14. 枚举:会添加到语言运行时,一般不用,必须使用时使用。

类型收窄

  1. 类型收窄会将类型推倒为更精确的类型:

    function padLeft(padding: number | string, input: string) {
    return new Array(padding + 1).join(" ") + input;
        // Operator '+' cannot be applied to types 'string | number' and 'number'.
        // 这里提示我们,把'string | number' 和 'number' 可能并不会达到我们想要的效果
    }
    
    function padLeft(padding: number | string, input: string) {
    if (typeof padding === "number") {
        return new Array(padding + 1).join(" ") + input; //在这里padding的类型会被推倒为number
    }
    return padding + input; // 这里padding的类型会被推倒为string
    }
    
    /**
     * TypeScript 要学着分析这些使用了静态类型的值在运行时的具体类型。目前 TypeScript 已经实现了比如 if/else 、三元运算符、循环、真值检查等情况下的类型分析。
    
    在我们的 if 语句中,TypeScript 会认为 typeof padding === number 是一种特殊形式的代码,我们称之为类型保护 (type guard),TypeScript 会沿着执行时可能的路径,分析值在给定的位置上最具体的类型。
    
    TypeScript 的类型检查器会考虑到这些类型保护和赋值语句,而这个将类型推导为更精确类型的过程,我们称之为收窄 (narrowing)。 
    */
    
    1. typeof 类型保护: ts 会根据 typeof 的结果进行类型收窄,针对 object 结果,会将 null 也包含在内
    2. 真值收窄:根据 if 中的 &&||! 进行类型收窄
    3. 等值收窄:使用 switch 语句和等值检查比如 == !== == != 去收窄类型。
    4. in 操作符收窄:in 用于判断对象中是否存在某个属性,通过 in 的结果进行类型收窄
    5. instanceof 收窄:通过 instanceof 结果进行类型收窄
    6. 赋值语句收窄:根据赋值语句的右值,正确的收窄左值
  2. 类型判断式:通过一个函数实现类型保护,这个函数返回的类型是类型判断式

    type Fish = { swim: () => void };
    type Bird = { fly: () => void };
    
    function isFish(pet: Fish | Bird): pet is Fish { // pet is Fish就是我们的类型判断式,一个类型判断式采用 parameterName is Type的形式,但 parameterName 必须是当前函数的参数名。
    return (pet as Fish).swim !== undefined; 
    }
    
    // Both calls to 'swim' and 'fly' are now okay.
    let pet = getSmallPet();
    
    if (isFish(pet)) {
    pet.swim(); // let pet: Fish
    } else {
    pet.fly(); // let pet: Bird
    }
    
    // 也可以用 isFish 在 Fish | Bird 的数组中,筛选获取只有 Fish 类型的数组
    const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];
    const underWater1: Fish[] = zoo.filter(isFish);
    // or, equivalently
    const underWater2: Fish[] = zoo.filter(isFish) as Fish[];
    
    // 在更复杂的例子中,判断式可能需要重复写
    const underWater3: Fish[] = zoo.filter((pet): pet is Fish => {
    if (pet.name === "sharkey") return false;
    return isFish(pet);
    });
    
  3. 可辨别联合:当联合类型中的每个类型,都包含了一个共同的字面量类型的属性,TypeScript 就会认为这是一个可辨别联合,然后可以将具体成员的类型进行收窄。

  4. 穷尽检查:借助 never 类型可以赋值给任何类型,然而,没有类型可以赋值给 never (除了 never 自身)。

    interface Triangle {
    kind: "triangle";
    sideLength: number;
    }
    
    interface Circle {
    kind: "circle";
    radius: number;
    }
    
    interface Square {
    kind: "square";
    sideLength: number;
    }
    
    
    type Shape = Circle | Square | Triangle;
    
    function getArea(shape: Shape) {
    switch (shape.kind) {
        case "circle":
        return Math.PI * shape.radius ** 2;
        case "square":
        return shape.sideLength ** 2;
        default:
        const _exhaustiveCheck: never = shape;
        // Type 'Triangle' is not assignable to type 'never'.
        return _exhaustiveCheck;
    }
    }
    //因为 TypeScript 的收窄特性,执行到 default 的时候,类型被收窄为 Triangle,但因为任何类型都不能赋值给 never 类型,这就会产生一个编译错误。通过这种方式,你就可以确保 getArea 函数总是穷尽了所有 shape 的可能性。
    
    

函数

  1. 函数表达式:type GreetFunction = (a: string) => void;

  2. 调用签名:用于函数有自己的属性值的情况

    type DescribableFunction = {
    description: string;
    (someArg: number): boolean;
    };
    function doSomething(fn: DescribableFunction) {
    console.log(fn.description + " returned " + fn(6));
    }
    
  3. 构造签名:用于 new 操作符调用的函数

    type SomeConstructor = {
    new (s: string): SomeObject;
    };
    function fn(ctor: SomeConstructor) {
    return new ctor("hello");
    }
    
  4. 泛型函数:函数输出的类型取决于输入的类型,或者两个输入的类型以某种形式相互关联。泛型就是用一个相同类型来关联两个或者更多的值

    //通过给函数添加一个类型参数 Type,并且在两个地方使用它,我们就在函数的输入(即数组)和函数的输出(即返回值)之间创建了一个关联
    function firstElement<Type>(arr: Type[]): Type | undefined {
    return arr[0];
    }
    
  5. 推断:不必明确指出泛型的类型,根据输入可以判断出来:

    function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] {
    return arr.map(func);
    }
    
    // Parameter 'n' is of type 'string'
    // 'parsed' is of type 'number[]'
    const parsed = map(["1", "2", "3"], (n) => parseInt(n));
    
  6. 约束:对类型的参数进行限制

    function longest<Type extends { length: number }>(a: Type, b: Type) {
    if (a.length >= b.length) {
        return a;
    } else {
        return b;
    }
    }
    
    // longerArray is of type 'number[]'
    const longerArray = longest([1, 2], [1, 2, 3]);
    // longerString is of type 'alice' | 'bob'
    const longerString = longest("alice", "bob");
    // Error! Numbers don't have a 'length' property
    const notOK = longest(10, 100);
    // Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
    

    正是因为我们对 Type 做了 { length: number } 限制,我们才可以被允许获取 a b 参数的 .length 属性。没有这个类型约束,我们甚至不能获取这些属性,因为这些值也许是其他类型,并没有 length 属性。

    基于传入的参数,longerArraylongerString 中的类型都被推断出来了。

    • 但是要注意:
      function minimumLength<Type extends { length: number }>(
      obj: Type,
      minimum: number
      ): Type {
      if (obj.length >= minimum) {
          return obj;
      } else {
          return { length: minimum };
          // Type '{ length: number; }' is not assignable to type 'Type'.
          // '{ length: number; }' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint '{ length: number; }'.
      }
      }
      //其中的问题就在于函数理应返回与传入参数相同类型的对象,而不仅仅是符合约束的对象
      // 'arr' gets value { length: 6 }
      const arr = minimumLength([1, 2, 3], 6);
      // and crashes here because arrays have
      // a 'slice' method, but not the returned object!
      console.log(arr.slice(0));
      
  7. 声明类型参数:通常能自动推断泛型调用中传入的类型参数,也可以手动指定

    function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
    return arr1.concat(arr2);
    }
    
    const arr = combine<string | number>([1, 2, 3], ["hello"]);
    
  8. 函数的可选参数:使用 ? 声明

  9. 回调函数中的可选参数:不能使用 ? 声明,ts 会按照不会传入进行推断。正确的做法是不要在回调函数中设置可选参数,除非确定不传入这个参数。对于多余的参数,ts 会像 js 一样忽略

  10. 函数重载:针对一个函数存在不同的调用方式的情况

    举个例子。你可以写一个函数,返回一个日期类型 Date ,这个函数接收一个时间戳(一个参数)或者一个 月/日/年 的格式 (三个参数)。

    function makeDate(timestamp: number): Date;
    function makeDate(m: number, d: number, y: number): Date;
    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);
    }
    }
    const d1 = makeDate(12345678);
    const d2 = makeDate(5, 5, 5);
    const d3 = makeDate(1, 3);
    
    // No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
    

    在这个例子中,我们写了两个函数重载,一个接受一个参数,另外一个接受三个参数。前面两个函数签名被称为重载签名

    然后,我们写了一个兼容签名的函数实现,我们称之为实现签名 ,但这个签名不能被直接调用。尽管我们在函数声明中,在一个必须参数后,声明了两个可选参数,它依然不能被传入两个参数进行调用。 but 实现签名不能直接被调用这里不是很理解~~~

  11. 其他的一些类型

    1. void 表示函数没有任何返回值,不等同于 undefined
    2. object 表示任何不是原始类型的值,函数也是对象,也可以有属性
    3. unknown 类似于 any 但是更安全,对于 unknown 做任何事情都是不合法的
    4. never 表示一个值不会再被观察到,比如函数中抛出异常
  12. 剩余参数:function multiply(n: number, ...m: number[]) {}

  13. 参数结构:function sum({ a, b, c }: { a: number; b: number; c: number }) {}

  14. 函数的可赋值性:除了函数字面量以外,即使规定了返回值类型为 void ,也不强制函数不能返回值:

    type voidFunc = () => void;
    
    const f1: voidFunc = () => {
    return true;
    };
    
    const f2: voidFunc = () => true;
    
    const f3: voidFunc = function () {
    return true;
    };
    

    当一个函数字面量定义返回一个 void 类型,函数是一定不能返回任何东西的:

    function f2(): void {
    // @ts-expect-error
    return true;
    }
    
    const f3 = function (): void {
    // @ts-expect-error
    return true;
    };
    

对象类型

  1. 对象类型可以使用匿名的对象类型,接口,类型别名

  2. 属性修饰符

    1. 可选属性:属性名后加一个问号?,可选值不传入的时候会是 undefined ,可以借助结构设置默认值,也可以在操作前判断避免bug
    2. 只读属性:在属性名前边加一个 readonly,针对基本数据类型是不可设置值的,对于引用数据类型,里边属性可以更新,但是引用地址不能变
    3. 索引签名:不知道对象类型中的全部属性名字但是知道特点时使用
      1. 同时支持 string 和 number 类型,但数字索引的返回类型一定要是字符索引返回类型的子类型。这是因为当使用一个数字进行索引的时候,JavaScript 实际上把它转成了一个字符串。这就意味着使用数字 100 进行索引跟使用字符串 100 索引,是一样的
      interface Animal {
      name: string;
      }
      
      interface Dog extends Animal {
      breed: string;
      }
      
      // Error: indexing with a numeric string might get you a completely separate type of Animal!
      interface NotOkay {
      [x: number]: Animal;
      // 'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.
      [x: string]: Dog;
      }
      
  3. 属性继承:使用 extends 继承另外一个接口:interface A extends B,C,D{}

  4. 交叉类型:type ColorfulCircle = Colorful & Circle; 连结 ColorfulCircle 产生了一个新的类型,新类型拥有 ColorfulCircle 的所有成员。

  5. 接口继承与交叉类型:最原则性的不同就是在于冲突怎么处理,使用继承的方式,如果重写类型会导致编译错误,但交叉类型不会

    interface Colorful {
    color: string;
    }
    
    interface ColorfulSub extends Colorful {
    color: number
    }
    // Interface 'ColorfulSub' incorrectly extends interface 'Colorful'.
    // Types of property 'color' are incompatible.
    // Type 'number' is not assignable to type 'string'.
    
    //------------------------------------------------------------------------
    interface Colorful {
    color: string;
    }
    
    type ColorfulSub = Colorful & {
    color: number
    }
    //虽然不会报错,那 color 属性的类型是什么呢,答案是 never,取得是 string 和 number 的交集。
    
  6. 泛型对象类型:对对象的属性类型不确定的时候使用:

    interface Box<Type> {
    contents: Type;
    }
    //当我们引用 Box 的时候,我们需要给予一个类型实参替换掉 Type:
    let box: Box<string>;
    
    //类型别名也是可以使用泛型的
    type Box<Type> = {
    contents: Type;
    };
    
    //类型别名不同于接口,可以描述的不止是对象类型,所以我们也可以用类型别名写一些其他种类的的泛型帮助类型。
    type OrNull<Type> = Type | null;
    
    type OneOrMany<Type> = Type | Type[];
    
    type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;
            
    type OneOrManyOrNull<Type> = OneOrMany<Type> | null
    
    type OneOrManyOrNullStrings = OneOrManyOrNull<string>;
                
    type OneOrManyOrNullStrings = OneOrMany<string> | null
    
    1. Array 类型:类似于上面的 Box 类型,Array 本身就是一个泛型
      interface Array<Type> {
      /**
       * Gets or sets the length of the array.
      */
      length: number;
      
      /**
       * Removes the last element from an array and returns it.
      */
      pop(): Type | undefined;
      
      /**
       * Appends new elements to an array, and returns the new length of the array.
      */
      push(...items: Type[]): number;
      
      // ...
      }
      
    2. ReadonlyArray 类型:一个特殊类型,它可以描述数组不能被改变。更简短的写法 readonly Type[]
  7. 元组类型:元组类型是另外一种 Array 类型,当你明确知道数组包含多少个元素,并且每个位置元素的类型都明确知道的时候,就适合使用元组类型。也支持 readonly 。在大部分的代码中,元组只是被创建,使用完后也不会被修改,所以尽可能的将元组设置为 readonly 是一个好习惯。

泛型

  1. 泛型就是用一个相同类型来关联两个或者更多的值

  2. 泛型的调用方式有两种,只有类型推断失败的时候才会明确输入类型:

    function identity<Type>(arg: Type): Type {
    return arg;
    }
    
    // 明确输入类型
    let output = identity<string>("myString"); // let output: string
    
    // 类型推断
    let output = identity("myString"); // let output: string
    
  3. 泛型类型:借助泛型接口

    interface GenericIdentityFn<Type> {
    (arg: Type): Type;
    }
    
    function identity<Type>(arg: Type): Type {
    return arg;
    }
    
    let myIdentity: GenericIdentityFn<number> = identity;
    
    //方式二
    interface GenericIdentityFn {
    <Type>(arg: Type): Type;
    }
    
    function identity<Type>(arg: Type): Type {
    return arg;
    }
    
    let myIdentity: GenericIdentityFn = identity;
    
  4. 泛型类:类似于泛型接口。注意静态成员并不能使用类型参数。

    class GenericNumber<NumType> {
    zeroValue: NumType;
    add: (x: NumType, y: NumType) => NumType;
    }
    
    let myGenericNumber = new GenericNumber<number>();
    myGenericNumber.zeroValue = 0;
    myGenericNumber.add = function (x, y) {
    return x + y;
    };
    
  5. 泛型约束:一直泛型中必须存在某些属性时使用

    interface Lengthwise {
    length: number;
    }
    
    function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
    console.log(arg.length); // Now we know it has a .length property, so no more error
    return arg;
    }
    
    1. 在泛型约束中使用类型参数:声明一个类型参数,这个类型参数被其他类型参数约束。 ​ 举个例子,我们希望获取一个对象给定属性名的值,为此,我们需要确保我们不会获取 obj 上不存在的属性。所以我们在两个类型之间建立一个约束
      function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
      return obj[key];
      }
      
      let x = { a: 1, b: 2, c: 3, d: 4 };
      
      getProperty(x, "a");
      getProperty(x, "m");
      
      // Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.
      
    2. 在泛型中使用类类型:这里没太看懂,后续用到了再去看吧

keyof 操作符

  1. 对一个对象类型使用 keyof 操作符,会返回该对象属性名组成的一个字符串或者数字字面量的联合。
    type Point = { x: number; y: number };
    type P = keyof Point; //"x" | "y"
    
    type Arrayish = { [n: number]: unknown };
    type A = keyof Arrayish; // type A = number
    
    
    type Mapish = { [k: string]: boolean };
    type M = keyof Mapish;// type M = string | number  因为 JavaScript 对象的属性名会被强制转为一个字符串,所以 obj[0] 和 obj["0"] 是一样的。
    
    
  2. 对类和接口也可以使用 keyof

typeof操作符

  1. 用于获取一个变量或者属性的类型
  2. 仅仅用来判断基本的类型,自然是没什么太大用,和其他的类型操作符搭配使用才能发挥它的作用。 举个例子:比如搭配 TypeScript 内置的 ReturnType<T>。你传入一个函数类型,ReturnType<T> 会返回该函数的返回值的类型
    function f() {
        return { x: 10, y: 3 };
    }
    type P = ReturnType<typeof f>;
    
    // type P = {
    //    x: number;
    //    y: number;
    // }
    

索引访问类型

使用索引访问类型查找另外一个类型上的特定属性:

type Person = { age: number; name: string; alive: boolean };

type Age = Person["age"];
// type Age = number

type I1 = Person["age" | "name"];  
// type I1 = string | number
 
type I2 = Person[keyof Person];
// type I2 = string | number | boolean
 
type AliveOrName = "alive" | "name";
type I3 = Person[AliveOrName];  
// type I3 = string | boolean

作为索引的只能是类型,这意味着你不能使用 const 创建一个变量引用:

const key = "age";
type Age = Person[key];
// Type 'key' cannot be used as an index type.
// 'key' refers to a value, but is being used as a type here. Did you mean 'typeof key'?

// 然而你可以使用类型别名实现类似的重构:
type key = "age";
type Age = Person[key];

条件类型

  1. 类似于 js 中的三元表达式,根据表达式结果返回不同的类型:

    interface Animal {
    live(): void;
    }
    
    interface Dog extends Animal {
    woof(): void;
    }
    
    type Example1 = Dog extends Animal ? number : string;     
    // type Example1 = number
    
    type Example2 = RegExp extends Animal ? number : string;     
    // type Example2 = string
    
  2. 可以使用条件类型简化函数重载

    // 未使用条件类型
    interface IdLabel {
    id: number /* some fields */;
    }
    interface NameLabel {
    name: string /* other fields */;
    }
    
    function createLabel(id: number): IdLabel;
    function createLabel(name: string): NameLabel;
    function createLabel(nameOrId: string | number): IdLabel | NameLabel;
    function createLabel(nameOrId: string | number): IdLabel | NameLabel {
    throw "unimplemented";
    }
    
    // 使用条件类型
    type NameOrId<T extends number | string> = T extends number
    ? IdLabel
    : NameLabel;
    
  3. 条件类型约束:type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;

  4. 在条件类型里推断:提供了 infer 关键词,可以从正在比较的类型中推断类型,然后在 true 分支里引用该推断结果。

    type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
    
    // 举个例子,我们可以获取一个函数返回的类型:
    type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
    ? Return
    : never;
    
    // 当从多重调用签名(就比如重载函数)中推断类型的时候,会按照最后的签名进行推断,因为一般这个签名是用来处理所有情况的签名
    declare function stringOrNum(x: string): number;
    declare function stringOrNum(x: number): string;
    declare function stringOrNum(x: string | number): string | number;
    
    type T1 = ReturnType<typeof stringOrNum>;                     
    // type T1 = string | number
    
  5. 分发条件类型:使用条件类型时传入一个联合类型,就会变成分发的

    type ToArray<Type> = Type extends any ? Type[] : never;
    
    type StrArrOrNumArr = ToArray<string | number>;        
    // type StrArrOrNumArr = string[] | number[]
    

    让我们分析下 StrArrOrNumArr 里发生了什么,这是我们传入的类型:string | number;接下来遍历联合类型里的成员,相当于:ToArray<string> | ToArray<number>所以最后的结果是:string[] | number[]

    通常这是我们期望的行为,如果你要避免这种行为,你可以用方括号包裹 extends 关键字的每一部分:

    type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
    
    // 'StrArrOrNumArr' is no longer a union.
    type StrArrOrNumArr = ToArrayNonDist<string | number>;
    // type StrArrOrNumArr = (string | number)[]
    

映射类型

  1. 一个类型需要基于另外一个类型,但是又不想拷贝一份,这个时候可以考虑使用映射类型

    type OptionsFlags<Type> = {
    [Property in keyof Type]: boolean;
    };
    
    type FeatureFlags = {
    darkMode: () => void;
    newUserProfile: () => void;
    };
    
    type FeatureOptions = OptionsFlags<FeatureFlags>;
    // type FeatureOptions = {
    //    darkMode: boolean;
    //    newUserProfile: boolean;
    // }
    
  2. 有两个可能遇到的映射修饰符,一个是 readonly,用于设置属性只读,一个是 ? ,用于设置属性可选。可以通过前缀 - 或者 + 删除或者添加这些修饰符,如果没有写前缀,相当于使用了 + 前缀。

    // 删除属性中的只读属性
    type CreateMutable<Type> = {
    -readonly [Property in keyof Type]: Type[Property];
    };
    
    type LockedAccount = {
    readonly id: string;
    readonly name: string;
    };
    
    type UnlockedAccount = CreateMutable<LockedAccount>;
    
    // type UnlockedAccount = {
    //    id: string;
    //    name: string;
    // }
    
    // 删除属性中的可选属性
    type Concrete<Type> = {
    [Property in keyof Type]-?: Type[Property];
    };
    
    type MaybeUser = {
    id: string;
    name?: string;
    age?: number;
    };
    
    type User = Concrete<MaybeUser>;
    // type User = {
    //    id: string;
    //    name: string;
    //    age: number;
    // }
    
  3. 通过 as 实现键名重新映射

    type Getters<Type> = {
        [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
    };
    
    interface Person {
        name: string;
        age: number;
        location: string;
    }
    
    type LazyPerson = Getters<Person>;
    
    // type LazyPerson = {
    //    getName: () => string;
    //    getAge: () => number;
    //    getLocation: () => string;
    // }
    

模板字面量类型

  1. 类似 js 中的模板字符串:
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
 
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
// type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"

  1. 没有类型注解会被隐式设置为 any

  2. 字段设置初始值会被用于推断类型,比如初始值是0,那么类型会被推断为 number

  3. --strictPropertyInitialization 用于控制类字段是否需要在构造函数中初始化,但是如果在构造函数中调用另一个函数进行字段的初始化是不会被 ts 识别的

  4. readonly 修饰符:阻止字段在构造函数之外被被赋值

  5. 构造函数签名和普通函数类似,区别就在于:

    1. 构造函数不能有类型参数(关于类型参数,回想下泛型里的内容)。
    2. 构造函数不能有返回类型注解,因为总是返回类实例类型
  6. ts 会提醒 super 的调用

  7. Getters / Setter

    1. 如果 get 存在而 set 不存在,属性会被自动设置为 readonly
    2. 如果 setter 参数的类型没有指定,它会被推断为 getter 的返回类型
    3. getters 和 setters 必须有相同的成员可见性(Member Visibility)
  8. implements 语句检查一个类是否满足一个特定的 interfaceclass Sonar implements interfaceA,interface2{}

  9. 成员可见性:

    1. public :以在任何地方被获取

    2. protected:仅对子类可见

    3. private :不允许访问,即使是子类

    4. static :静态成员,和实例没有关系,通过类直接访问。静态成员同样可以使用 public protectedprivate 这些可见性修饰符,也可以被继承

    5. 类静态块:写一系列有自己作用域的语句,也可以获取类里的私有字段。

      class Foo {
          static #count = 0;
      
          get count() {
              return Foo.#count;
          }
      
          static {
              try {
                  const lastInstances = loadLastInstances();
                  Foo.#count += lastInstances.length;
              }
              catch {}
          }
      }
      
  10. 泛型类:

    class Box<Type> {
        contents: Type;
        constructor(value: Type) {
            this.contents = value;
        }
    }
    
    const b = new Box("hello!");
    // const b: Box<string>
    
转载自:https://juejin.cn/post/7218797742641610810
评论
请登录