TS

Last Edited Time
Feb 12, 2022 09:52 AM
date
Jul 16, 2021
slug
typescript
status
Published
tags
Typescript
必读系列
个人笔记
summary
TS 个人总结笔记
type
Post
目录

名词解释

  • 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 取值
  1. 如果上一个 key 的值为 number, 则下一个 key 的默认值为 number + 1
  1. 如果上一个 key 的值不为 number, 则下一个 key 一定要有 initializer
  1. 枚举成员的值除了是常量,还可以是计算出来的结果
  1. 只有数字枚举类型可以用计算值
  1. 数字枚举值可以反向映射,字符串枚举值不行
  1. 枚举 key 不能是计算属性
  1. 枚举前面加个 const 可以减少开销
  1. 外部枚举 declare

多态的 this 类型

在类中可以使用 this 多态类型, 能很容易的表现连贯接口间的继承, 例如:
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 所引用到, 所以:
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 代表构造函数
// 常用的映射类型如下
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)
type T6 = TypeName<string | string[] | undefined>;
//   ^ = type T6 = "string" | "undefined" | "object"

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

可以有多重类型判断
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
多重类型判断有可能会合并类型
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 的类型
// 没有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 合并:
// 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

type-challenges/README.zh-CN.md at master · type-challenges/type-challenges
TypeScript 类型体操姿势合集 高质量的类型可以提高项目的可维护性并避免一些潜在的漏洞。市面上也已经有许多优秀的类型工具库,像是 ts-toolbelt, utility-types, SimplyTyped 等等。我们也从这些项目中获取了许多灵感。 本项目意在于让你更好的了解 TS 的类型系统,编写你自己的类型工具,或者只是单纯的享受挑战的乐趣!我们同时希望可以建立一个社区,在这里你可以提出你在实际环境中遇到的问题,或者帮助他人解答疑惑 - 这些问题也可能被选中成为题库的一部分! 点击下方徽章查看题目内容 你可以通过如下几种方式参与贡献这个项目 分享你的答案或解题思路 提案加入新的题目 完善已有题目的测试用例 提供针对题目的学习资料或方法 分享你在真实项目中遇到的类型问题(无论你找到答案与否)- 大家会一起帮你找到解决的思路 通过在 Issue 下留言帮助他人 开一个新的 Issue 并选择相应的模板即可,感谢参与! 💡 尽管你可以使用中文进行讨论,我们还是鼓励你尝试使用英文发起 Issue 与评论,这样可以让你的问题被更多人看到也可以帮助更多人。 这个项目诞生于和 @hardfist @MeCKodo 两位朋友做的类型体操。同时也非常感谢 @sinoon 在项目初期提供了宝贵的反馈意见与参与的贡献。 MIT
type-challenges/README.zh-CN.md at master · type-challenges/type-challenges