IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    TypeScript 复习总结(二) · 看不见我的美 · 是你瞎了眼

    馬腊咯稽发表于 2020-04-24 00:00:00
    love 0
    TypeScript 复习总结(二)

    编译上下文

    tsconfig.json 用来给文件分组,告诉 TS 哪些文件是有效的,哪些是无效的;还包含有正在被使用的编译选项的信息。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    
    {
     // 编译相关选项
     "compilerOptions": {
     /* 基础选项 */
     "target": "es5", // 目标版本:'es3(default)', 'es5', 'es2015', 'es2016', 'es2017', or 'esnext'
     "module": "commonjs", // 指定使用模块:'commonjs', 'amd', 'system', 'umd', 'es2015' or 'esnext'
     "lib": [], // 指定要包含在编译中的库文件
     "allowJs": true, // 允许编译 js 文件
     "checkJs": true, // 报告 js 文件中的错误
     "jsx": "preserve", // 指定 jsx 代码的生成:'preserve', 'react-native', or 'react'
     "declaration": true, // 生成相应的 '.d.ts' 文件
     "sourceMap": true, // 生成相应的 '.map' 文件
     "outFile": "./", // 将输出文件合并为一个文件
     "outDir": "./", // 指定输出目录
     "rootDir": "./", // 用来控制输出目录结构 --outDir
     "removeComments": true, // 删除编译后的所有的注释
     "noEmit": true, // 不生成输出文件
     "importHelpers": true, // 从 tslib 导入辅助工具函数
     "isolatedModules": true, // 将每个文件做为单独的模块(与 'ts.transpileModule' 类似)
     /* 严格的类型检查选项 */
     "strict": true, // 启用所有严格类型检查选项
     "noImplicitAny": true, // 在表达式和声明上有隐含的 any 类型时报错
     "strictNullChecks": true, // 启用严格的 null 检查
     "noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误
     "alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
     /* 额外的检查 */
     "noUnusedLocals": true, // 有未使用的变量时,抛出错误
     "noUnusedParameters": true, // 有未使用的参数时,抛出错误
     "noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误
     "noFallthroughCasesInSwitch": true, // 不允许 switch 的 case 语句贯穿
     /* 模块解析选项 */
     "moduleResolution": "node", // 模块解析策略:'node' (Node.js) or 'classic' (TypeScript)
     "baseUrl": "./", // 用于解析非相对模块名称的基目录
     "paths": {}, // 模块名到基于 baseUrl 的路径映射的列表
     "rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容
     "typeRoots": [], // 包含类型声明的文件列表
     "types": [], // 需要包含的类型声明文件名列表
     "allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。
     /* Source Map Options */
     "sourceRoot": "./", // 指定调试器应该找到 TS 文件而不是源文件的位置
     "mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置
     "inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
     "inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
     /* 其他选项 */
     "experimentalDecorators": true, // 启用装饰器
     "emitDecoratorMetadata": true // 为装饰器提供元数据的支持
     },
     // 显式指定需要编译的文件
     "files": [
     "./some/file.ts"
     ],
     // 指定需要编译的文件,支持路径模式匹配
     "include": [
     "./folder"
     ],
     // 指定不需要编译的文件,支持路径模式匹配
     "exclude": [
     "./folder/**/*.spec.ts",
     "./folder/someSubFolder"
     ]
    }
    

    声明空间

    在 TS 里存在两种声明空间:类型声明空间与变量声明空间。类型声明空间包含用来当做类型注解的内容:

    1
    2
    3
    4
    5
    6
    
    class Foo {}
    interface Bar {}
    type Bas = {};
    let foo: Foo;
    let bar: Bar;
    let bas: Bas;
    

    变量声明空间包含可用作变量的内容,在上文中 Foo 提供了一个类型 Foo 到类型声明空间,此外它同样提供了一个变量 Foo 到变量声明空间:

    1
    2
    3
    4
    5
    
    class Foo {}
    const someVar = Foo;
    const someOtherVar = 123;
    const foo = 123;
    const bar: foo; // Error,foo 只能用在变量声明空间
    

    类型兼容

    类型兼容性用于确定一个类型是否能赋值给其他类型。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    // 两者类型不兼容
    let str: string = 'hello';
    let num: number = 123;
    str = num; // Error
    num = str; // Error
    // 结构化类型只要求结构匹配
    interface Point {
     x: number;
     y: number;
    }
    class Point2D {
     x: number;
     y: number;
     constructor(x, y) {}
    }
    let p: Point = new Point2D(1, 2); // Point2D 类型可以赋值给 Point 类型
    interface Point3D {
     x: number;
     y: number;
     z: number;
    }
    let point: Point = { x: 0, y: 10 };
    let point3D: Point3D = { x: 0, y: 10, z: 20 };
    point = point3D; // OK
    point3D = point; // Error, Property 'z' is missing in type 'Point' but required in type 'Point3D'
    

    模块

    在默认情况下,当你开始在一个新的 TS 文件中写下代码时,它处于全局命名空间中。如果你在相同的项目里创建了一个新的文件 bar.ts,TS 类型系统将会允许你使用变量 foo,就好像它在全局可用一样:

    1
    2
    3
    4
    
    // foo.ts
    const foo = 123;
    // bar.ts
    const bar = foo; // 可以获取到 foo
    

    全局模块是危险的,推荐使用文件模块;文件模块也被称为外部模块,如果在你的 TS 文件的根级别位置含有 import 或者 export,那么它会在这个文件中创建一个本地的作用域。

    1
    2
    3
    4
    
    // foo.ts
    export const foo = 123;
    // bar.ts
    const bar = foo; // Error,找不到 foo
    

    在项目中,一般会建立一个全局类型模块 global.d.ts,可以将通用的类型提取出来作为 lib.d.ts 的扩充。

    interface 与 type

    1. 都可以描述一个对象或者函数:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    interface User {
     name: string;
     age: number;
     eat(food: string): string;
    }
    interface NewUser {
     new (name: string, age?: number): User;
    }
    type User = {
     name: string;
     age: number;
     eat(food: string): string;
    };
    type NewUser = new (name: string, age?: number): User;
    
    1. 都允许拓展,type 使用 “&",interface 使用 extends:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    // interface extends interface
    interface PartialPointX {
     x: number;
    }
    interface Point extends PartialPointX {
     y: number;
    }
    // type extends type
    type PartialPointX = { x: number };
    type Point = PartialPointX & { y: number };
    // interface extends type
    type PartialPointX = { x: number };
    interface Point extends PartialPointX {
     y: number;
    }
    // type extends interface
    interface PartialPointX {
     x: number;
    }
    type Point = PartialPointX & { y: number };
    
    1. 类都可以实现 interface 和 type:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    interface Point1 {
     x: number;
     y: number;
    }
    type Point2 = {
     x: number;
     y: number;
    };
    class SomePoint1 implements Point1 {
     x = 1;
     y = 2;
    }
    class SomePoint2 implements Point2 {
     x = 1;
     y = 2;
    }
    
    1. type 可以声明基本类型别名、联合类型、元组等类型:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    // 基本类型别名,给 string 类型起了一个叫 Name 的别名
    type Name = string;
    // 联合类型
    interface Dog {
     wong();
    }
    interface Cat {
     miao();
    }
    type Pet = Dog | Cat;
    // 具体定义数组每个位置的类型
    type PetList = [Dog, Pet];
    
    1. type 语句中可以使用 typeof 获取实例的类型进行赋值:
    1
    2
    3
    
    // 当你想获取一个变量的类型时,使用 typeof
    let div = document.createElement('div');
    type DivType = typeof div; // HTMLDivElement
    
    1. interface 能够声明合并(接口可以定义多次):
    1
    2
    3
    4
    5
    6
    7
    
    interface User {
     name: string;
     age: number;
    }
    interface User {
     sex: string;
    }
    

    undefined、null、never 与 void

    默认情况下 never、null 和 undefined 是所有类型的子类型;就是说你可以把 never、null 和 undefined 赋值给任何类型的变量。只有 never 可以赋值给 never 类型;当你指定了 –strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自。void 表示没有任何类型,never 表示永远不存在的值的类型。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    function error(message: string): undefined;
    function error(message: string): null;
    function error(message: string): void;
    function error(message: string): never {
     throw new Error(message);
    }
    function infiniteLoop(): undefined;
    function infiniteLoop(): null;
    function infiniteLoop(): void;
    function infiniteLoop(): never {
     while (true) {
     // ...
     }
    }
    

    any 与 unknown

    any 类型本质上是类型系统的一个逃逸舱;允许我们对 any 类型的值执行任何操作,而无需事先执行任何形式的检查。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    let value: any;
    value = 'hello'; // OK
    value = true; // OK
    value = 42; // OK
    value = []; // OK
    value = {}; // OK
    value = null; // OK
    value = undefined; // OK
    value = Symbol('type'); // OK
    value = new Error(); // OK
    

    就像所有类型都可以被归为 any,所有类型也都可以被归为 unknown;这使得 unknown 成为 TypeScript 类型系统的另一种顶级类型。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    let value: unknown;
    value = 'hello'; // OK
    value = true; // OK
    value = 42; // OK
    value = []; // OK
    value = {}; // OK
    value = null; // OK
    value = undefined; // OK
    value = Symbol('type'); // OK
    value = new Error(); // OK
    

    对 value 变量的所有赋值都被认为是类型正确的。

    1
    2
    3
    4
    5
    6
    7
    
    let value: unknown;
    let value1: unknown = value; // OK
    let value2: any = value; // OK
    let value3: boolean = value; // Error
    let value4: number = value; // Error
    let value5: string = value; // Error
    let value6: object = value; // Error
    

    unknown 类型只能被赋值给 any 类型和 unknown 类型本身,只有能够保存任意类型值的容器才能保存 unknown(未知)类型的值。

    1
    2
    3
    4
    5
    6
    
    let value: unknown;
    value.foo.bar; // Error
    value.trim(); // Error
    value(); // Error
    new value(); // Error
    value[0][1]; // Error
    

    TS 不允许我们对类型为 unknown 的值执行任意操作;我们必须通过类型保护、断言类型等方式缩小 unknown 的类型范围,才能对其进行操作。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    let unknownValue: unknown;
    let stringValue: string;
    let numberValue: number;
    if (isObject(unknownValue)) {
     unknownValue.valueOf();
    }
    stringValue = unknownValue as string;
    numberValue = <number>unknownValue;
    function isObject(value: any): value is object {
     return Object.prototype.toString.call(value) === '[object Object]';
    }
    

    在联合类型中,unknown 类型会吸收任何类型;如果任一组成类型是 unknown,联合类型也会相当于 unknown;在交叉类型中,任何类型都可以吸收 unknown 类型,任何类型与 unknown 相交不会改变结果类型:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    type UnionType0 = unknown | any; // any
    type UnionType1 = unknown | null; // unknown
    type UnionType2 = unknown | undefined; // unknown
    type UnionType3 = unknown | string; // unknown
    type UnionType4 = unknown | number[]; // unknown
    type IntersectionType0 = unknown & any; // any
    type IntersectionType1 = unknown & null; // null
    type IntersectionType2 = unknown & undefined; // undefined
    type IntersectionType3 = unknown & string; // string
    type IntersectionType4 = unknown & number[]; // number[]
    

    private 与 protect

    当成员被标记成 private 时,它就不能在声明它的类的外部访问;protected 成员在派生类中仍然可以访问。

    TS 使用的是结构性类型系统;当我们比较两种不同的类型时,并不在乎它们从何处而来,如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的;当我们比较带有 private 或 protected 成员的类型的时候,如果其中一个类型里包含一个 private 成员,那么只有当另外一个类型中也存在这样一个 private 成员,并且它们都是来自同一处声明时,我们才认为这两个类型是兼容的;对于 protected 成员也使用这个规则。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    class Parent {
     public name: string;
     private age: number;
     protected job: string;
     readonly sex: boolean;
     constructor(name, age, job, sex) {
     this.name = name;
     this.age = age;
     this.job = job;
     this.sex = sex;
     }
    }
    class Child extends Parent {
     constructor(name, age, job, sex) {
     super(name, age, job, sex);
     this.name = name;
     this.age = age; // Error,私有属性只能在类内部访问
     this.job = job;
     this.sex = sex; // Error,sex 属性是只读的,不能修改
     }
    }
    let p: Parent = new Parent('lee', 18, 'developer', true);
    console.log(p.age); // Error,私有属性只能在类内部访问
    console.log(p.job); // Error,受保护属性只能在类及其子类中访问
    

    instanceof

    通过判断构造函数进行类型保护。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
    interface Animal {
     type: string;
     speak: () => string;
     [prop: string]: any;
    }
    class Cat implements Animal {
     type = '猫';
     speak() {
     return '喵喵';
     }
     catchMouse() {
     console.log('捉老鼠');
     }
    }
    class Dog implements Animal {
     type = '狗';
     speak() {
     return '汪汪';
     }
     watchDoor() {
     console.log(this.speak());
     }
    }
    let miao = new Cat();
    let wang = new Dog();
    function selectAnimal(animal: Animal) {
     if (animal instanceof Dog) {
     animal.watchDoor();
     } else if (animal instanceof Cat) {
     animal.catchMouse();
     } else {
     animal.speak();
     }
    }
    selectAnimal(miao);
    selectAnimal(wang);
    

    typeof

    typeof 操作符可以用来获取一个变量声明或对象的类型,也可以用来类型保护(基本类型)。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    interface Person {
     name: string;
     age?: number;
    }
    const sam: Person = { name: 'sam', age: 32 };
    type SamType = typeof sam; // Person
    function toArray<T>(x: T): Array<T> {
     return [x];
    }
    type ToArrayType = typeof toArray; // <T>(x: T) => T[]
    

    keyof

    keyof 操作符可以用于获取某种类型的所有键,其返回类型是联合类型。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    interface Person {
     name: string;
     age?: number;
    }
    type KeyoOfPerson = keyof Person;
    const p1: KeyoOfPerson = 'name';
    const p2: KeyoOfPerson = 'age';
    type KeyoOfPersonArray = keyof Array<Person>;
    const p3: KeyoOfPersonArray = 'length';
    const p4: KeyoOfPersonArray = 'push';
    type keys = keyof { [props: string]: string };
    // TS 支持两种索引,『字符串索引』和『数字索引』
    const p5: keys = 'string';
    const p6: keys = 'number';
    

    infer

    infer 的作用是让 TS 自己推断,并将推断的结果存储到一个临时变量中,并且只能用于 extends 语句中;它与泛型的区别在于,泛型是声明一个“参数”,而 infer 是声明一个“中间变量”。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    type Unpacked<T> = T extends (infer U)[]
     ? U
     : T extends (...args: any[]) => infer U
     ? U
     : T extends Promise<infer U>
     ? U
     : T;
    type T0 = Unpacked<string>; // string
    type T1 = Unpacked<string[]>; // string
    type T2 = Unpacked<() => string>; // string
    type T3 = Unpacked<Promise<string>>; // string
    type T4 = Unpacked<Promise<string>[]>; // Promise<string>
    type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string
    type CustomReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
    type T6 = CustomReturnType<() => number>; // number
    

    in

    用来遍历枚举类型或类型保护(属性 key)。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    // 遍历枚举
    type Keys = 'a' | 'b' | 'c';
    type Obj = {
     [p in Keys]: any;
    };
    // 类型保护
    interface Boss {
     name: string;
     worth: number;
    }
    interface Employee {
     name: string;
     salary: number;
    }
    type Human = Boss | Employee;
    function detectBoss(human: Human) {
     if ('worth' in human) {
     console.log(human.worth);
     } else {
     console.log('not boss');
     }
    }
    

    is

    is 关键字用来创建自定义类型保护:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    interface Foo {
     foo: number;
     common: string;
    }
    interface Bar {
     bar: number;
     common: string;
    }
    function isFoo(arg: Foo | Bar): arg is Foo {
     return typeof (arg as Foo).foo === 'number';
    }
    function example(val: Foo | Bar) {
     if (isFoo(val)) {
     val.foo++;
     } else {
     val.bar--;
     }
    }
    example({ bar: 123, common: '456' });
    

    参考

    • TypeScript: Interfaces VS Types
    • TypeScript 3.0: unknown 类型
    • TypeScript 强大的类型别名


沪ICP备19023445号-2号
友情链接