Ts入门到放弃

TS 的核心能力在于给 JS 提供静态类型检查,是有类型定义的 JS 的超集,包括 ES5、ES5+ 和其他一些诸如泛型、类型定义、命名空间等特征的集合。

本次仅会针对类型声明部分配合示例进行着重介绍,更详细的内容以及特性可以查看 Typescript handbook以及changelog。

TOC


  1. 类型 & 操作符
    • 没有明确的指定类型的时候推测出一个类型
    • 如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查
  2. 声明 & 模块
  3. 泛型
  4. 体操
  5. 拓展

类型

  1. Function、object、Object、Array、number、string、enum、any、unknown、undefined、void、null、never
  2. tuple []
  3. intersection &
  4. union |
    • 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
    • 联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型

结构子类型

typescript 的子类型是基于 结构子类型 的,只要结构可以兼容,就是子类型(Duck Type)

class Test {
  x: number;
}

function get(test: Test) {
  return test.x;
}

class Test2 {
  x: number;
}

const test2 = new Test2();

// Passed
get(test2);

java、c++ 等传统静态类型语言是基于 名义子类型 的,必须显示声明子类型关系(继承),才可以兼容

对象子类型

子类型中必须包含源类型所有的属性和方法

function get(test: { x: number }) {
  return test.x;
}

const test = {
  x: 1,
  y: "2",
};

// Passed
get(test);

注意: 如果直接传入一个对象字面量是会报错

function get(test: { x: number }) {
  return test.x;
}

// Error!
// Argument of type '{ x: number; y: string; }' is not assignable to parameter of type '{ x: number; }'.
// Object literal may only specify known properties, and 'y' does not exist in type '{ x: number; }'.
get({ x: 1, y: "2" });

这是 ts 中的另一个特性,叫做: excess property check,当传入的参数是一个对象字面量时,会进行额外属性检查

函数子类型(逆变与协变)

对于函数类型来说,函数参数的类型兼容是反向的,我们称之为 逆变 返回值的类型兼容是正向的,称之为 协变

我们允许一个函数类型中,返回值类型是协变的,而参数类型是逆变的。返回值类型是协变的,意思是 A ≼ B 就意味着 (T → A) ≼ (T → B) 。参数类型是逆变的,意思是 A ≼ B 就意味着 (B → T) ≼ (A → T) ( A 和 B 的位置颠倒过来了)

允许不变的列表(immutable)在它的参数类型上是协变的,但是对于可变的列表(mutable),其参数类型则必须是不变的(invariant),既不是协变也不是逆变

逆变与协变并不是 TS 中独有的概念,在其他静态语言(java 中数组即是逆变又是协变)中也有相关理念

  • A ≼ B 表示 A 是 B 的子类型,A 包含 B 的所有属性和方法。
  • A => B 表示以 A 为参数,B 为返回值的方法。 (param: A) => B

如果我们现在有三个类型 Animal、 Human、 Man,那么肯定存在下面的关系:

Man ≼ Human ≼ Animal

所以存在 Animal => Man 是 Human => Human 的子类型,可以简单理解为对于参数的类型由于逆变可以接收参数类型的父类型,对于函数的返回值由于协变可以接收返回值类型的子类型

函数的参数为多个时可以转化为 Tuple 的类型兼容性,长度大的是长度小的子类型,再由于函数参数的逆变特性,所以函数参数少的可以赋值给参数多的(参数从前往后需一一对应)

具体 demo 和示例可以参考链接

What are covariance and contravariance?

在 TypeScript 中, 参数类型是双向协变的 ,也就是说既是协变又是逆变的,而这并不安全。但是现在你可以在 TypeScript 2.6 版本中通过 --strictFunctionTypes 或 --strict 标记来修复这个问题。

逆变协变本质上还是为了满足里式替换

any、unknown、void、never

  • any: 任意类型(属于 Union type)
  • unknown: 未知的类型

任何类型都能分配给 unknown,但 unknown 不能分配给其他基本类型,而 any 可以分配和被分配任意类型

  • void: TypeScript中的void是undefined的子类型。JS中的函数总会有返回,要么是一个具体的值,要么是undefined。而void和undefined在ts中有一个很大的区别在于void作为返回类型可以用不同的类型替换
function wrapper(callback: () => void) {}

function test(): number {
 return 1;
}

// works well
wrapper(test)
  • never: 表示哪些用户无法达到的类型(异常)
function throwErr(): never {
  throw new Error("an error");
}

const age = 18;

throwErr();

// Unreachable code detected.
age.toFixed(2);

never 还可以用于联合类型的幺元:

// string | number
type T = string | number | never;

通过在联合类型中 never 类型幺元的特性可以做到很多过滤的操作 如过滤 Human 中 age 和 name 之外的成员

type Human = {
  age: number;
  name: string;
  lover: string;
  gender: 1 | 0;
};

type Filter = {
  [P in {
    [K in keyof T]: K extends "age" | "name" ? K : never;
  }[keyof T]]: T[P];
};

// type T = {
//     age: number;
//     name: string;
// }
type T = Filter;

// 上面只是为了试验 never 的幺元特性 Filter可以更简单的写法
// type Filter = {
//   [K in Extract

Keyword & Operators

typeof

一般可以使用 typeof 来进行类型保护来避免访问错误的属性或者方法

function test(b: any) {
  if (typeof b === "string") {
    // Property 'test' does not exist on type 'string'
    b.test();
  }
}

此外就是主要用来进行类型推断

const test = (a: string) => a.length;

const obj = {
  a: 1,
  b: false,
};

// (a: string) => number
type A = typeof test; // (x: string) => number
// {
//   a: number;
//   b: boolean;
// }
type B = typeof obj;

keyof

获取接口的所有键,返回键的联合类型

type Test = {
  a;
  b;
};

// 'a' | 'b'
type Res = keyof Test;

in

对联合类型进行遍历

type Test = {
  a;
  b;
};

// type Res = {
//     a: string;
//     b: string;
// }
type Res = {
  [K in keyof Test]: string;
};

需要注意的是只可以在 type 声明的类型下使用,如interface声明下会抛出A mapped type may not declare properties or methods.

A computed property name in an interface must refer to an expression whose type is a literal type or a ‘unique symbol’ type. The left-hand side of an arithmetic operation must be of type ‘any’, ‘number’, ‘bigint’ or an enum type. The right-hand side of an ‘in’ expression must not be a primitive.

[]

索引访问,类似于 js 的元语法

type Test1 = {
  a;
  b;
};
type Test2 = [1, 2];

// any
type Res1 = Test["a"];
// 2
type Res2 = Test2[1];

这类语法可以逃脱掉类型检查的限制如调用一个没有明确声明的全局变量属性window[’undefinedVar’]而不会获得报错,但相应的在使用的时候也不会获得任何类型提示

断言as or <>和双重断言

interface Foo {
  bar: number;
  bas: string;
}

const foo = {} as Foo;

然而,如下例子中的代码将会报错,尽管使用者已经使用了类型断言:

function test(a: number): void {}

// Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.(2352)
// Bad
// test('' as number);
// Good
test('' as unknown as number);

TypeScript 是怎么确定单个断言是否足够?

上面提到过ts的逆变和协变,当 S 类型是 T 类型的子集,或者 T 类型是 S 类型的子集时,S 能被成功断言成 T。这是为了在进行类型断言时提供额外的安全性,完全毫无根据的断言是危险的,如果你想这么做,你可以借助 any或者unknown进行双重断言。

Class & interface

接口(Interfaces)可以用于对「对象的形状(Shape)」进行描述

实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现

  • 类实现接口
  • 接口继承接口
  • 接口继承类(继承类型)

声明 & 模块


配置

tsconfig.json 是 ts 的编译器(tsc)将 ts 编译为 js 的配置文件,在开发和编译阶段提供支持(语法检查,代码依赖等)

使用 tsconfig.json

如果一个目录下存在一个 tsconfig.json 文件,那么它意味着这个目录是 TypeScript 项目的根目录。 tsconfig.json 文件中指定了用来编译这个项目的根文件和编译选项。 一个项目可以通过以下方式之一来编译:

  • 不带任何输入文件的情况下调用 tsc,编译器会从当前目录开始去查找 tsconfig.json 文件,逐级向上搜索父目录
  • 不带任何输入文件的情况下调用 tsc,且使用命令行参数–project(或-p)指定一个包含 tsconfig.json 文件的目录

当命令行上指定了输入文件时,tsconfig.json 文件会被忽略

参数:

  • compilation

声明

声明文件

以 .d.ts 结尾的文件用来给 ts 提供类型定义的文件 如果一个文件有扩展名 .d.ts,这意味着每个根级别的声明都必须以 declare 关键字作为前缀。这有利于让开发者清楚的知道,在这里 TypeScript 将不会把它编译成任何代码,同时开发者需要确保这些在编译时存在。

如何使用?

  • 首先会寻找 package json 中 types 或 typings 指定的文件
  • 然后寻找包的根目录下的 index.d.ts
  • TS 官方维护的@typesDefinitelyTyped

扩展原生对象

默认的一些原生对象上是不存在一些自定义挂载的属性,所以不可以直接赋值,可以输用方括号赋值语句,但是读操作时也必须用 [] ,并且没有类型提示。

可以通过类型合并

declare interface Window {}

// or
declare global {
  interface Window {}
}

扩展第三方库

import Vue from "vue";

declare module "vue/types/vue" {
  interface Vue {}
}

处理其他扩展名文件

declare module "*.css" {
  const content: any;
  export default content;
}

声明合并

如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型

interface Person {
    age: number
}

interface Person {
    name: string
}

// Good
const man: Person = { age: 100, name: 'foo' }

模块

  • commonjs

    对象

const $ = require('jquery')

declare module '$' {
  export function forEach(callback: () => any): void
}
declare module 'koa' {
  function random_name(): any
  export=random_name
}
  • es
export declare var age: number
//orType
declare var name: string
export { name }
// or
export default name
  1. global——不使用 import 或 export 的声明文件将被视为 global。顶级声明是全局导出的
  2. module——具有至少一个 export 声明的声明文件将被视为模块。只有 export 声明会被导出,不会定义任何 global
  3. 隐式 export——没有 export 声明,但使用 import 的声明文件将触发已定义但尚未说明的行为。也就是将顶级声明视为命名的 export 声明,并且不会定义 global

TS 的模块分为全局模块和文件模块,默认情况下,我们所写的代码是位于全局模块下的

// a.ts
const age = 18;

如果在另一个文件中使用 age,ts 的检查是正常的(全局)

// b.ts
console.log(age);

将当前模块变为局部的文件模块只需要当前文件存在任意的 export 或者 import 语句即可

TypeScript 团队似乎并不喜欢第三种模式,因此请尽可能避免使用第三种模式

模块解析

共有两种可用的模块解析策略:Node 和 Classic。 你可以使用 --moduleResolution 标记来指定使用哪种模块解析策略。若未指定,那么在使用了 --module AMD | System | ES2015 时的默认值为 Classic,其它情况时则为 Node。

有一个对 module 的非相对导入 import { b } from "module",它是在/root/src/folder/A.ts 文件里,会以如下的方式来定位"module":

Node 的模块解析通过分别查找/root/src、/root、/三种路径下的 node_modules

[/root/src/|/root/|/]node_modules/module.ts
[/root/src/|/root/|/]node_modules/module.tsx
[/root/src/|/root/|/]node_modules/module.d.ts
[/root/src/|/root/|/]node_modules/module/package.json (如果指定了"types"属性)
[/root/src/|/root/|/]node_modules/module/index.ts
[/root/src/|/root/|/]node_modules/module/index.tsx
[/root/src/|/root/|/]node_modules/module/index.d.ts

Classic 的寻址方式 这种策略在以前是 TypeScript 默认的解析策略。 现在,它存在的理由主要是为了向后兼容。

/root/src/folder/module.ts
/root/src/folder/module.d.ts
/root/src/module.ts
/root/src/module.d.ts
/root/module.ts
/root/module.d.ts
/module.ts
/module.d.ts

相关链接:

  • Node
  • Classic

泛型


泛指的类型,关键目的是在成员之间(类以及类成员或者函数的入参、返回值等)提供有意义的约束

interface Animal {
  type: T
}

const Jessica: Animal<'people'> = {
  type: 'people'
}

const Wangcai: Animal<'dog'> = {
  type: 'dog'
}

具体泛型的使用配合下面大量的示例

Conditional Types

TypeScript 2.8 introduces conditional types which add the ability to express non-uniform type mappings. A conditional type selects one of two possible types based on a condition expressed as a type relationship test:

T extends U ? X : Y

The type above means when T is assignable to U the type is X, otherwise the type is Y.

Let’s move by a simple case


interface Human {
  name: string
  age: number
  secondLanguage: string
  lover?: Human
}

const Jessica: Human = {
  name: 'jessica',
  age: 21,
  secondLanguage: 'Frence'
}

但不是每个人都有第二语言

const Lucia: Human = { // Type error secondLanguage is required
  name: 'jessica',
  age: 21,
}

但是如果要重新写一个又显的冗余重复

两种方案

  1. 写一个函数支持将指定属性去除(支持批量)返回去除后的新类型声明
  2. 写一个函数支持将指定的属性变为可选(支持批量)

去除指定属性

思路是获取去除的这个属性的 key,首先有

type TEST = {
  [P in K]: T[P]
}

这个泛型 K 其实就是泛型 T 的键值,即 P extends keyof T

type TEST = {
  [P in K]: T[P]
}

接下来需要定义一个函数来移除泛型 K 中指定的那个属性 key

type EXCLUDE = T extends U ? never : T;

最后定义支持去除指定 key 的函数 OMIT

type OMIT = {
  [P in EXCLUDE]: T[P]
}

再利用这个函数来声明新的 Human 类型

type Human_exclude = OMIT

const Lucia: Human = { // pass
  name: 'jessica',
  age: 21,
}

将指定属性定义为可选

但是死板的去除指定属性某些场景可能不满足需求(后面可能会动态的想 TS 对象添加某个 k)

如果我们将指定的属性(language)定义为可选,那么同样满足需求

方法通用上面的移除:

  1. 定义去除key所对应的可选类型
  2. 定义去除指定的属性后的类型
  3. 选取1和2的交叉类型
type type OPTIONAL_K = {
  [P in K]?: T[P];
} &
  OMIT;

const Lucia: Human = { // pass
  name: 'jessica',
  age: 21,
}

Lucia.luanguage = 'en' // pass

关于 infer


表示在 extends 条件语句中待推断的类型变量

infer

type ParamType = T extends (param: infer P) => any ? P : T;

infer P 表示待推断的函数参数

如果 T 能赋值给 (param: infer P) => any,则结果是 (param: infer P) => any 类型中的参数的类型 P,否则返回为 T


PICK

type PICK = {
  [P in K]: T[P];
};

type A = PICK;

PARTIAL

type PARTIAL = {
  [P in keyof T]?: T[P];
};

type F = PARTIAL;

REQUIRED

type REQUIRED = {
  [P in keyof T]-?: T[P];
};

type G = REQUIRED;

REQUIRED_K

type REQUIRED_K = {
  [P in K]-?: T[P];
} &
  OMIT;

type E = REQUIRED_K;

READONLY_RECURSIVE

type SIG = {
  key: {
    key: {
      key: any
    }
  }
}

type READONLY_RECURSIVE = {
  readonly [P in keyof T]: T[P] extends {[index: string]: any} ? READONLY_RECURSIVE : T[P];
};

const test: READONLY_RECURSIVE = {
  key: {
    key: {
      key: 123
    }
  }
}

test.key.key = 123 // err: Cannot assign to 'key' because it is a read-only property.ts(2540)

RETURN & CONSTRUCTOR_P

type RETURN any> = T extends () => infer R ? R : never;

type CONSTRUCTOR_P any> = T extends new (
  ...args: infer P
) => any
  ? P
  : any[];

function bark() {
  return "string";
}

type Eat = () => number;

type BarkReturn = RETURN; // string
type EatReturn = RETURN; // number

class HumanBeing {
  constructor(name: string, age: number) {}
}

type HB = CONSTRUCTOR_P; // [string, number]

PROMISE_LIKE

type PROMISE_LIKE = {
  then(
    resolve?: (value?: T) => T1 | PROMISE_LIKE | undefined | null,
    reject?: (value?: any) => T2 | PROMISE_LIKE | undefined | null
  ): PROMISE_LIKE;
};

PROMISE

type PROMISE = {
  then(
    resolve?: (value?: T) => T1 | PROMISE_LIKE | undefined | null,
    reject?: (value?: any) => T2 | PROMISE_LIKE | undefined | null
  ): PROMISE_LIKE;

  catch(value?: any): T1 | PROMISE_LIKE | undefined | null;
};

内置类型

当你安装 TypeScript 时,会顺带安装一个 lib.d.ts 声明文件。这个文件包含 JavaScript 运行时以及 DOM 中存在各种常见的环境声明。

  • 它自动包含在 TypeScript 项目的编译上下文中;
  • 它能让你快速开始书写经过类型检查的 JavaScript 代码。

可以通过指定 --noLib 的编译器命令行标志(或者在 tsconfig.json 中指定选项 noLib: true)从上下文中排除此文件。

  • 上面所有示例中除却 REQUIRED_K、OPTIONAL_K 和 READONLY_RECURSIVE 之外都是属于 typescript lib.es5.d.ts 官方库帮我们内置声明好的类型,便于平时一些快捷的使用
interface PromiseLike

interface Promise

type Partial

type Required

type Readonly

type Pick

type Record

type Exclude

type Extract

type Omit

type NonNullable

type Parameters any>

type ConstructorParameters any>

type ReturnType any>

type InstanceType any>

others

此外还内置 es5 其他函数和方法以及数据类型等的声明如果 mac vsc 有安装 typescript 拓展可以借助 vsc 打开,具体路径在/Applications/Visual Studio Code.app/Contents/Resources/app/extensions/node_modules/typescript/lib/lib.es5.d.ts

如何利用 infer 配合协变实现高级类型声明


体操1: LeetCode 的一道 TS 面试题

https://github.com/LeetCode-OpenSource/hire/blob/master/typescript_zh.md

假设有类型

interface Action {
  payload?: T;
  type: string;
}

class EffectModule {
  count = 1;
  message = "hello!";

  delay(input: Promise) {
    return input.then(i => ({
      payload: `hello ${i}!`,
      type: 'delay'
    }));
  }

  setMessage(action: Action) {
    return {
      payload: action.payload!.getMilliseconds(),
      type: "set-message"
    };
  }
}

经过 Connect 函数之后,返回值类型为

type Result {
  delay(input: T): Action;
  setMessage(action: T): Action;
}

从表面来看我们要做的有三点:

  1. delay(input: Promise): Promise 变成了 asyncMethod(input: T): Action
  2. setMessage(action: Action): Action 变成了 syncMethod(action: T): Action
  3. 去除了其他非函数的成员属性

step1:构造转换 1 和 2 的函数也是最关键的一步

type Transform = {
  [K in keyof T]: T[K] extends ((input: Promise) => Promise<{
    payload: infer U;type:string
  }>)
    ? ((input: P) => Action)
    : T[K] extends ((action: Action) => {
        payload: infer U;
        type:string
      })
    ?((action: P) => Action)
    : never;
}

type Temp = Transform
// type Temp = {
//   count: never;
//   message: never;
//   delay: (input: number) => Action;
//   setMessage: (action: Date) => Action;
// }

step2:构造工具函数将 step1 中得到的 never 类型的无关类型去除

  1. 获取所有的 key
  2. 通过元语法批量的联合类型的 key 索引出值联合类型 value
type OmitNever = {
  [K in keyof T]: T[K] extends Function ? never: K
}[keyof T];

type temp = OmitNever
// type temp = "count" | "message"

step3: 借助 Omit 移除 step1 中 step2 的 key

type Connect = Omit, OmitNever>;

type Result = Connect
// type Result = {
//   delay: (input: number) => Action;
//   setMessage: (action: Date) => Action;
// }

体操2: 联合类型转交叉类型:A | B => A & B

type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((
  k: infer I,
) => void)
  ? I
  : never
  1. 如果 extends 左边是联合类型,那么 TS 会把 extends 操作符左边的联合类型拆开做判断,这样就得到了很多个函数的联合。如果第一部分的输入是 A | B 那么输出是 ((k: A) => void) | ((k: B) => void) 而不是 (k: A|B) => void
  2. 如果左边是一个函数,那就把它第一个参数的类型拿出来返回

为什么 ((k: A) => void) | ((k: B) => void) 的参数是 A & B?

因为函数参数是逆变的,我们假设有一个变量能同时传给 (k: A) => void 和 (k: B) => void,那么这个变量的类型应该是 A & B 而不是 A | B

P extends K 意味着所有 K 都可以无条件被 P 替换

一个函数能被 (k: A) => void 和 (k: B) => void 无条件替换,那么那个函数接受的参数必然既是 A 又是 B

  • Q: 根据 conditional type ***((k: A) => void) | ((k: B) => void)*不是应该被分开处理吗?如果分开处理那得到的结果依然会是 A | B,那么又是为什么能够得出 A & B 呢?

conditional-type

extends 左边联合类型被拆开判断的情况只会出现在左边是一个类型参数的情况:大概就是 type F = T extends any 的这个左边是会被拆开,而 type F = A | B extends any 的左边就不会被拆开。

type Union = {age} | {number}

type Union2 = number | string

type Intersection = UnionToIntersection // {age} & {number}

type Intersection2 = UnionToIntersection // never

体操3: Closing note: 联合类型转 TUPLE:A | B => [A, B]

主体的思路是递归的将联合类型的每一项取出来放入元组同时移除这一项,最后将递归结束后的元组返回;同时也要写一些基本的辅助函数:

  • 借助协变,联合类型转交叉类型
  • 每次递归将放入元组的那个类型从原集合获取
  • 每次递归获取放入元组的单个类型的 prepend 函数
  • 移除每次递归推入元组的那个类型
  • 借助函数入参 reset 通过 infer 推断出元祖

step1: 将 Union 类型转为由函数类型组成的交叉类型

// union to intersection of functions
// 42 =====> (42) => void =====> ((a: U, ...r: T) infer R
type UnionToIoF =
  (U extends any ? (k: (x: U) => void) => void : never) extends
  ((k: infer I) => void) ? I : never

step2: 借助特性每次获取最后一个元素

type UnionPop = UnionToIoF extends (a: infer A) => void ? A : never;

step3: 将获取的类型推入元组

type Prepend =
    ((a: U, ...r: T) => void) extends (...r: infer R) => void ? R : never;

step4: 移除推入的类型

// 借助内置类型 Exclude
type Exclude = T extends U ? never : T;

step5: 最后一步借助上面的工具函数写转换的递归

type UnionToTupleRecursively = {
    0: Result;
    1: UnionToTupleRecursively>, Prepend, Result>>
}[[Union] extends [never] ? 0 : 1];

Q: 此处为何要借助 {}[] 的形式来作为递归的终止条件而不是直接使用三目呢?

type aliases are not like interfaces. interfaces are named types, where as type aliases are just aliases. internally as well they are treated differently, the compiler aggressively flatten types aliases to their declarations.

type alias 不允许调用自身 这里使用索引方式 {}[]

Ts 4.1.3 版本后取消此限制

// wrong
type UnionToTupleRecursively = [Union] extends [never] ? Result : UnionToTupleRecursively>, Prepend, Result>>

Below is the complete code.

type UnionToIoF =
    (U extends any ? (k: (x: U) => void) => void : never) extends
    ((k: infer I) => void) ? I : never

// return last element from Union
type UnionPop = UnionToIoF extends (a: infer A) => void ? A : never;

// prepend an element to a tuple.
type Prepend<U, T extends any[]> =
    ((a: U, ...r: T) => void) extends (...r: infer R) => void ? R : never;

type UnionToTupleRecursively<Union, Result extends any[]> = {
    0: Result;
    1: UnionToTupleRecursively<Exclude<Union, UnionPop<Union>>, Prepend<UnionPop<Union>, Result>>
}[[Union] extends [never] ? 0 : 1];

type UnionToTuple = UnionToTupleRecursively<U, []>;

// support union size of 43 at most
type Union43 = 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;

type A_ = UnionPop<Union43>
// type A_ = 43
type B_ = UnionToIoF<Union43>
// type B_ = ((x: 1) => void) & ((x: 2) => void) & ((x: 3) => void) & ((x: 4) => void) & ((x: 5) => void) & ((x: 6) => void) & ((x: 7) => void) & ((x: 8) => void) & ((x: 9) => void) & ((x: 10) => void) & ((x: 11) => void) & ((x: 12) => void) & ((x: 13) => void) & ((x: 14) => void) & ((x: 15) => void) & ((x: 16) => void) & ... 26 more ... & ((x: 43) => void)

type UnionTest = string | number | 'sss' | {age:123}
type TupleTest = UnionToTuple<UnionTest>;
// type TupleTest = [a: string, a: number, a: {
//   age: 123;
// }]

type Tuple = UnionToTuple<Union43>;
// type Tuple = [a: 1, a: 2, a: 3, a: 4, a: 5, a: 6, a: 7, a: 8, a: 9, a: 10, a: 11, a: 12, a: 13, a: 14, a: 15, a: 16, a: 17, a: 18, a: 19, a: 20, a: 21, a: 22, a: 23, a: 24, a: 25, a: 26, a: 27, a: 28, a: 29, a: 30, a: 31, a: 32, a: 33, a: 34, a: 35, a: 36, a: 37, a: 38, a: 39, a: 40, a: 41, a: 42, a: 43]

一个有意思的是批量处理联合类型时支持的最大长度为 43(没确定是否和版本有关),否则会抛出 Type instantiation is excessively deep and possibly infinite.ts(2589) 的异常

Ts 4.1.3 版本后取消此限制

Extends


除却本次分享之外 TS 还有很多其他相关的很多特性包括

  • 类型保护 typeof/instanceof/in
  • Flow type 自动更新
  • Freshness 严格的字面量类型检查
  • 异常处理 Error/RangeError/RangeError…
  • 兼容

以及更多高阶运用

编译原理

  • AST
  • Scanner(scanner.ts
  • Parser(parser.ts
  • Binder(binder.ts
  • Checker(checker.ts

最后

做一个合格的Typescript工程师,感受体操的魅力!

从type-challenges开始:

https://github.com/type-challenges/type-challenges

你可能感兴趣的:(typescript,前端)