返回文章

文章 2021

TS

TS 个人总结笔记

Typescript必读系列个人笔记
本文目录21
  1. 名词解释
  2. 高级类型
  3. type guards(类型守护) 和区分类型
  4. type 和 interface
  5. Enum
  6. 多态的 this 类型
  7. 索引类型(Index types)和 index signatures(字符串索引签名)
  8. Mapped types 映射类型
  9. Conditional Types(有条件类型)
  10. Distribute conditional types(分布有条件类型)
  11. Type inference in conditional type(有条件类型的类型推断)
  12. 特殊的类型
  13. ThisType
  14. Declaration Merging(声明合并)
  15. interface 声明合并
  16. namespace
  17. .d.ts 的加载规则
  18. 全局作用域和模块作用域
  19. 类型区分
  20. 特点区分
  21. Reference

名词解释

  • Ambient simply means “without implementation”.

高级类型

type guards(类型守护) 和区分类型

  • as 关键字: pet as Fish

  • (T: any) => T is string: 用户自定义的断言方法

  • in 关键字: “swim” in pet

  • typeof 关键字: typeof x === “number”

  • instanceof 关键字: padder instanceof SpaceRepeatingPadder

type 和 interface

  • type 可以重命名 primitives(原始类型), unions(联合类型), tuples(类型数组)

  • type 并不创建新类型, 只是给类型取了个新名字

  • interface 可以拓展并合并同名类型, type 则会报错

Enum

  1. 数字枚举在不指定初始化值时默认从 0 取值

  2. 如果上一个 key 的值为 number, 则下一个 key 的默认值为 number + 1

  3. 如果上一个 key 的值不为 number, 则下一个 key 一定要有 initializer

  4. 枚举成员的值除了是常量,还可以是计算出来的结果

  5. 只有数字枚举类型可以用计算值

  6. 数字枚举值可以反向映射,字符串枚举值不行

  7. 枚举 key 不能是计算属性

  8. 枚举前面加个 const 可以减少开销

  9. 外部枚举 declare

多态的 this 类型

在类中可以使用 this 多态类型, 能很容易的表现连贯接口间的继承, 例如:

text
class ScientificCalculator extends BasicCalculator {
  public constructor(value = 0) {
    super(value);
  }
  public sin() {
    this.value = Math.sin(this.value);
    return this;
  }
  // ... other operations go here ...
}

let v = new ScientificCalculator(2)
  .multiply(5)
  .sin()
  // 这里 this 类型继承了 add()
  .add(1)
  .currentValue();

索引类型(Index types)和 index signatures(字符串索引签名)

其中 key 的类型可以为:

  • number

  • string

  • symbol

可以使用 keyofT[K] 来操作 index signature, 又因为 string 类型的 key 有可能被 number 类型的 key 所引用到, 所以:

text
interface A {
  [key: string]: any;
}

type B = keyof A;
// type B = string | number

Mapped types 映射类型

由一个类型生成新类型的过程叫映射类型, 常用的关键字为:

  • keyof: 获取类型的 key 可选类型

  • in: 和 key 一起使用, 代表属于某联合类型

  • extends: 这里的继承类似于断言, A extends BA 属于 B

  • never: 可以进行类型判断

  • infer: 类型判断占位类型

  • &: 可能与索引类型共用来完成通用+指定类型配置

  • ?: 移除参数的可选配置

  • new: new (…args: any) => any 代表构造函数

text
// 常用的映射类型如下
type Readonly2<T> = {
  readonly [P in keyof T]: T[P];
};
type Partial2<T> = {
  [P in keyof T]?: T[P];
};
type Nullable2<T> = {
  [P in keyof T]: T[P] | null;
};
type Pick2<T, P extends keyof T> = {
  [key in P]: T[P];
};
type Record2<T extends string | number | symbol, V> = {
  [key in T]: V;
};
type Required2<T> = {
  [P in keyof T]-?: T[P];
};
type TypeName<T> = T extends string
  ? 'string'
  : T extends number
  ? 'number'
  : T extends boolean
  ? 'boolean'
  : T extends undefined
  ? 'undefined'
  : T extends Function
  ? 'function'
  : 'object';
type Diff<T, U> = T extends U ? never : T;
type Filter<T, U> = T extends U ? T : never;
type Exclude2<T, K> = T extends K ? never : K;
type Omit2<T, P> = Pick<T, Exclude<keyof T, P>>;
type InstanceType2<T extends new (...args: any) => any> = T extends new (
  ...args: any
) => infer R
  ? R
  : any;
type ConstructorParameters2<
  T extends new (...args: any) => any
> = T extends new (...args: infer R) => any ? R : never;

// https://github.com/type-challenges/type-challenges/blob/master/questions/296-medium-permutation/README.md
type Permutation<T, K = T> = [T] extends [never]
  ? []
  : K extends any
  ? [K, ...Permutation<Exclude<T, K>>]
  : never;

Conditional Types(有条件类型)

T extends U ? X : Y 为有条件的类型, 如果没有充足的信息的话, 类型可能为 X | Y

Distribute conditional types(分布有条件类型)

在映射条件类型时, 会分别映射每个条件的类型,例如:

T extends U ? X : YT = A | B | C 时的结果为 (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)

text
type T6 = TypeName<string | string[] | undefined>;
//   ^ = type T6 = "string" | "undefined" | "object"

Type inference in conditional type(有条件类型的类型推断)

可以有多重类型判断

text
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>;
//   ^ = type T0 = string
type T1 = Unpacked<string[]>;
//   ^ = type T1 = string
type T2 = Unpacked<() => string>;
//   ^ = type T2 = string
type T3 = Unpacked<Promise<string>>;
//   ^ = type T3 = string
type T4 = Unpacked<Promise<string>[]>;
//   ^ = type T4 = Promise
type T5 = Unpacked<Unpacked<Promise<string>[]>>;
//   ^ = type T5 = string

多重类型判断有可能会合并类型

text
type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;

type T1 = Foo<{ a: string; b: string }>;
//   ^ = type T1 = string
type T2 = Foo<{ a: string; b: number }>;
//   ^ = type T2 = string | number

特殊的类型

ThisType

ThisType 一般会和 & 共用, ThisType 只会指定 this 的类型

text
// 没有ThisType情况下
const foo = {
  bar() {
    console.log(this.a); // error,在foo中只有bar一个函数,不存在a
  },
};

// 使用ThisType
const foo: { bar: any } & ThisType<{ a: number }> = {
  bar() {
    console.log(this.bar); // error,因为没有在ThisType中定义
    console.log(this.a); // ok
  },
};

foo.bar; // ok
foo.a; // error,在外面的话,就跟ThisType没有关系了

Declaration Merging(声明合并)

interface 声明合并

  • 非方法的属性合并时必须不重复或有相同的类型

  • 方法属性在合并的时候, 后加载的会有更高的优先级, 但是 single string literal type (e.g. not a union of string literals) 会被冒泡到顶部, 优先级更高

namespace

  • 全局作用域下, 不可以定义值属性, 而且所有的类型属性都会被暴露, 不需要显示写 export

  • 模块作用域下, 可以定义值和类型, 只有 export 的属性才可以被外部访问到; 可以合并同名的 namespace 以及里面的 interface 等, 但是不能共享不同 namespace 下的值

namespace 可以和 class, function 和 enum 合并:

text
// namespace 和 class 合并
class Logger {
  public logLevel = Logger.LogLevel.Info;
}

namespace Logger {
  export enum LogLevel {
    Verbose,
    Info,
  }
}

// namespace 和 function 合并
function buildLabel(name: string): string {
  return buildLabel.prefix + name + buildLabel.suffix;
}

namespace buildLabel {
  export let suffix = '';
  export let prefix = 'Hello, ';
}

// namespace 和 enum 合并
enum Color {
  red = 1,
  green = 2,
  blue = 4,
}

namespace Color {
  export function mixColor(colorName: string) {
    if (colorName == 'yellow') {
      return Color.red + Color.green;
    } else if (colorName == 'white') {
      return Color.red + Color.green + Color.blue;
    } else if (colorName == 'magenta') {
      return Color.red + Color.blue;
    } else if (colorName == 'cyan') {
      return Color.green + Color.blue;
    }
  }
}

.d.ts 的加载规则

  • 和 nodejs 模块加载类似

  • 除了加载同级同名的 *.d.ts 文件, 还会依次加载当前文件夹的所有的 纯 *.d.ts 文件

  • 如果在 *.d.ts 中使用了 export, 则和 *.ts 加载逻辑类似

全局作用域和模块作用域

类型区分

  • 类型全局作用域: *.d.ts 如果没有 import/export, 并且没有同名的 .ts/.js 文件

  • 类型的类似 ts 模块作用域: *.d.ts 文件包括 import/export, 不能使用 /// <reference 引入

  • 类型的模块作用域: *.d.ts 如果有同名的 .ts/.js 文件, 可以使用 /// <reference 引入, 但是拿不到全局类型

  • ts 的全局作用域: *.ts 文件不包括 import/export

  • ts 的模块作用域: *.ts 文件包括 import/export, 本地类型可以覆盖全局类型, 并不会与全局类型冲突

特点区分

  • 是否可以被 import 或 export

  • 是否可以使用全局类型

  • 类型是否为全局类型

  • 本地类型是否可以覆盖全局类型

  • 是否可以被 /// <reference 引入

Reference

https://github.com/type-challenges/type-challenges/blob/master/README.zh-CN.md

返回首页
上一篇JS 执行机制下一篇typescript-module-resolution

Discussion

留言与讨论

想法、补充和不同意见都欢迎。