Skip to content

内置高级类型

Awaited<Type>

Partial<Type> 索引变为可选

Required<Type> 索引去除可选

Readonly<Type> 索引变为只读

Record<Keys, Type> 映射一组相同类型的索引

Pick<Type, Keys> 提取指定索引

Omit<Type, Keys> 排除指定索引

Pick 功能相反

Exclude<UnionType, ExcludedMembers> 排除

实现

type Exclude<T, U> = T extends U ? never : T;

使用

type T0 = Exclude<"a" | "b" | "c", "a">;
     
type T0 = "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
     
type T1 = "c"
type T2 = Exclude<string | number | (() => void), Function>;
     
type T2 = string | number
 
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; x: number }
  | { kind: "triangle"; x: number; y: number };
 
type T3 = Exclude<Shape, { kind: "circle" }>

Extract<Type, Union> 提取

type T0 = Extract<"a" | "b" | "c", "a" | "f">;
     
type T0 = "a"
type T1 = Extract<string | number | (() => void), Function>;
     
type T1 = () => void
 
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; x: number }
  | { kind: "triangle"; x: number; y: number };
 
type T2 = Extract<Shape, { kind: "circle" }>

NonNullable<Type>

type T0 = NonNullable<string | number | undefined>;  // type T0 = string 
type T1 = NonNullable<string[] | null | undefined>;  // type T1 = string[]

Parameters<Type>

declare function f1 (a: number, b: string): void;

type T0 = Parameters<typeof f1>; // type T0 = [a: number, b: string]

type T1 = Parameters<(a: number) => void>; // type T1 = [a: number]

type T3 = Parameters<any>; // type T3 = unknown[];

type T4 = Parameters<never>; // type T4 = never;

ReturnType<Type>

declare function f1 (): {a: number, b: string}

type T0 = ReturnType<typeof f1>; // type T0 = {a: number, b: string}

type T1 = ReturnType<() => string>; // type T1 = string;

ConstructorParameters<Type>

class C {
  constructor (a: number, b: string) {}
}

type T0 = ConstructorParameters<typeof C>;  // type T0 = [a: number, b: string]

type T1 = ConstructorParameters<any>; // type T1 = unknown[]

ThisType<Type>

ThisParameterType<Type>

OmitThisParameter<Type>

InstanceType<Type>

Uppercase<StringType>

Lowercase<StringType>

Capitalize<StringType>

Uncapitalize<StringType>

关键字

PropertyKey

// 空对象类型
type EmptyObject = {
  // type PropertyKey = string | number | symbol;
  [K in PropertyKey]: never
}

const shouldPass: EmptyObject = {};
const showFail: EmptyObject = {
  prop: 'TS' // 不能将类型“string”分配给类型“never”。ts(2322)
}

infer

type Func = (...args: any[]) => any;
type FunctionReturnType<T extends Func> = T extends (
  ...args: any[]
) => infer R
  ? R
  : never;

infer,意为推断,如 infer R 中 R 就表示 待推断的类型infer 只能在 条件类型 中使用,因为我们实际上仍然需要类型结构是一致的,比如上例中类型信息需要是一个 函数类型 结构,我们才能提取出它的返回值类型。如果连函数类型都不是,那我只会给你一个 never 。

这里的类型结构当然并 不局限函数类型 结构,还可以是数组

type Swap<T extends any[]> = T extends [infer A, infer B] ? [B, A] : T;

type SwapResult1 = Swap<[1, 2]>; // 符合元组结构,首尾元素替换[2, 1]
type SwapResult2 = Swap<[1, 2, 3]>; // 不符合结构,没有发生替换,仍是 [1, 2, 3]

除了数组,infer 结构也可以是接口:

extends

条件类型中使用 extends 判断类型的 兼容性,而非判断类型的 全等性

T extends U 意味着 T 是 U子类型,但在属性组成联合类型中却相反,U 的属性联合类型是 T 的属性联合类型的子类型

type T0 = any extends string ? 1 : 2;     // 1 | 2
type T3 = any extends any ? 1 : 2;        // 1

type T4 = never extends string ? 1 : 2;   // 1
type T5 = never extends never ? 1 : 2;    // 1

type Special<T> = T extends never ? 1 : 2;  // type T0 = never
type T6 = Special<never>;  // type T0 = never
type T0 = 1 | 2 extends 1 | 2 | 3 | 4 ? true : false;

interface Person {
  name: string;
  age: number;
}

interface Male {
  name: string;
  age: number;
  gender: boolean;
}

type T1 = Male extends Male ? true : false;
type T0 = string | number extends string ? true : false; // type T0 = false;

as

interface Example {
  a: string;
  b: string | number;
  c: () => void;
  d: {};
}

type ConditionalPick<T, U> = {
  [K in keyof T as (T[K] extends U ? K : never)]: T[K]
}

type StringKeyOnly = ConditionalPick<Example, string>

typeof

typeof 操作符用于获取 变量typeof 后面只能跟变量,不能是类型) 的类型

is

它被称为类型谓词,用来判断一个变量属于某个接口或类型。如果需要封装一个类型判断函数,你应该第一时间想到它。

function isString(s: unknown): boolean {
  return typeof s === 'string'
}

function toUpper (x: unknown) {
  if (isString(x)) {
    x.toUppercase(); // 类型“unknown”上不存在属性“toUppercase”。ts(2339)
  }
}

由于 函数嵌套 TypeScript 不能进行正确的类型判断,所以上述代码会抛出一个错误提示

function isString(s: unknown): s is string {
  return typeof s === 'string'
}

function toUpper (x: unknown) {
  if (isString(x)) {
    x.toUpperCase()
  }
}

通过 is 关键字将类型范围缩小为 string 类型,这也是一种代码健壮性的约束规范。

const isNumber = (val: unknown): val is number => typeof val === 'number'
const isString = (val: unknown): val is string => typeof val === 'string'
const isFunction = (val: unknown): val is Function => typeof val === 'function'

InstanceType

基础类型

原始类型

any

unknown

只能赋值给 unknownany 类型

never

在类型流的分析中,一旦一个 返回值 类型为 never 的函数被调用,那么下方的代码都会被视为 无效 的代码(即无法执行到):

function justThrow(): never {
  throw new Error()
}

function foo (input:number){
  if(input > 1){
    justThrow();
    // 等同于 return 语句后的代码,即 Dead Code
    const name = "linbudu";
  }
}

细节:never 是所欧

细节一:碰上联合类型的时候会排除自己

type One = never | 1; // type One = 1

细节二:使用 never 作为泛型参数的时候产生了分布式条件类型,生成的类型不论条件类型返回值如何都是 never

type IsNumber<T> = T extends number ? true : false;
type stillNever = IsNumber<never>; // type stillNever = never

细节三:判断是否是 never 类型,记得阻止条件分发机制

type IsNever<T> = [T] extends [never] ? true : false

anyunknown 的区别

1、unknown 类型可以 被赋值 任意类型,但只能 赋值anyunknown 类型;any 类型可以 被赋值 任意类型,也可以 赋值 给任意类型

object 和 Object 区别

首先是 Object 的使用。被 JavaScript 原型链折磨过的同学应该记得,原型链的顶端是 Object 以及 Function,这也就意味着所有的原始类型与对象类型最终都指向 Object,在 TypeScript 中就表现为 Object 包含了所有的类型:

// 对于 undefined、null、void 0 ,需要关闭 strictNullChecks
const tmp1: Object = undefined;
const tmp2: Object = null;
const tmp3: Object = void 0;

const tmp4: Object = 'linbudu';
const tmp5: Object = 599;
const tmp6: Object = { name: 'linbudu' };
const tmp7: Object = () => {};
const tmp8: Object = [];

在任何情况下,你都不应该使用这些装箱类型(Boolean、Number、String、Symbol)。

object 的引入就是为了解决对 Object 类型的错误使用,它代表所有非原始类型的类型,即数组、对象与函数类型这些:

// 对于 undefined、null、void 0 ,需要关闭 strictNullChecks
const tmp17: object = undefined;
const tmp18: object = null;
const tmp19: object = void 0;

const tmp20: object = 'linbudu';  // X 不成立,值为原始类型
const tmp21: object = 599; // X 不成立,值为原始类型

const tmp22: object = { name: 'linbudu' };
const tmp23: object = () => {};
const tmp24: object = [];

object 和 Record<any, any>的区别

let t0:object = {};
t0.name; // 类型“object”上不存在属性“name”。ts(2339)

object 定义是一个对象类型,不能自动获取定义在对象上的属性和方法

let t0:Record<any, any> = {};
t0.name; // works

这里的 Record<any, any> 也就是 key 为 string 类型,value 为任意类型,可以代替 object 来用。更加语义话一点:

type T0 = Record<any, any>;
// 等同于
type T1 = {
  [k: string]: any
}

字面量类型

let str = 'name';       // let str: string;
const str = 'name';     // const str: "name"

使用 const 声明的变量,其类型会从值推导出最精确的字面量类型。而 const 定义的对象类型则只会推导至符合其属性结构的接口。

要解答这个现象,需要你回想 let 和 const 声明的意义。我们知道,使用 let 声明的变量是可以再次赋值的,在 TypeScript 中要求赋值类型始终与原类型一致(如果声明了的话)。因此对于 let 声明,只需要推导至这个值从属的类型即可。而 const 声明的原始类型变量将不再可变,因此类型可以直接一步到位收窄到最精确的字面量类型,但对象类型变量仍可变(但同样会要求其属性值类型保持一致)。

函数类型

function f1 (name: string): number {
  return name.length
}

const f2 = function (name: string): number {
  return name.length
}

const f3 = (name: string): number => {
  return name.length
}

const f4: (name: string) => number = (name) => {
  return name.length
}

const f5: (name: string) => number = function (name) {
  return name.length
}

type Func6 = (name: string) => number

const f6: Func6 = (name) => {
  return name.length
}

type 与 interface

更推荐的方式是,interface 用来描述对象、类的结构,而类型别名用来将一个函数签名、一组联合类型、一个工具类型等等抽离成一个完整独立的类型。但大部分场景下接口结构都可以被类型别名所取代,因此,只要你觉得统一使用类型别名让你觉得更整齐,也没什么问题

函数重载

在某些逻辑较复杂的情况下,函数可能有多组入参类型和返回值类型:

function func(foo: number, bar?: boolean): string | number {
  if (bar) {
    return String(foo);
  } else {
    return foo * 599;
  }
}

在这个实例中,函数的返回类型基于其入参 bar 的值,并且从其内部逻辑中我们知道,当 bartrue,返回值为 string 类型,否则为 number 类型。而这里的类型签名完全没有体现这一点,我们只知道它的返回值是这么个联合类型

要想实现与入参关联的返回值类型,我们可以使用 TypeScript 提供的函数重载签名(Overload Signature),将以上的例子使用重载改写:

function func(foo: number, bar: true): string;
function func(foo: number, bar?: false): number;
function func(foo: number, bar?: boolean): string | number {
  if (bar) {
    return String(foo);
  } else {
    return foo * 599;
  }
}

const res1 = func(599); // number
const res2 = func(599, true); // string
const res3 = func(599, false); // number

这里有一个需要注意的地方,拥有多个重载声明的函数在被调用时,是按照重载的声明顺序往下查找的。因此在第一个重载声明中,为了与逻辑中保持一致,即在 bartrue 时返回 string 类型,这里我们需要将第一个重载声明的 bar 声明为必选的字面量类型。

你可以试着为第一个重载声明的 bar 参数也加上可选符号,然后就会发现第一个函数调用错误地匹配到了第一个重载声明。

不能声明文件 中使用 函数重载,必须要有默认导出。否则会报错(函数实现重复。ts(2393)

访问修饰符

  • public 此类成员在类、类的实例、子类中都能被访问
  • private 此类成员仅能在类的内部被访问
// Utils 类内部全部都是静态成员,我们不希望有人去实例化这个类。就可以使用私有构造函数来阻止它被错误地实例化
class Utils {
  public static version = '1.0.0'
  private constructor () {}
  public static getName () {}
}
  • protected 此类成员仅能在类与子类中被访问

操作修饰符

  • readonly

静态成员

class Foo {
  static staticHandler () {}
  public instanceHandler () {}
}

类的 内部成员 无法通过 this 访问静态成员,需要通过 Foo.staticHandler 这种形式进行访问。

我们可以查看编译到 ES5 及以下 target 的 JavaScript 代码(ES6 以上就原生支持静态成员了),来进一步了解它们的区别:

var Foo = /** @class */ (function () {
  function Foo() {
  }
  Foo.staticHandler = function () { };
  Foo.prototype.instanceHandler = function () { };
  return Foo;
}());

从中我们可以看到,静态成员直接被挂载在函数体上,而实例成员挂载在原型上,这就是二者的最重要差异:静态成员不会被实例继承,它始终只属于当前定义的这个类(以及其子类)。而原型对象上的实例成员则会沿着原型链进行传递,也就是能够被继承。

而对于 静态成员实例成员使用时机,其实并不需要非常刻意地划分。比如我会用类 + 静态成员来收敛变量与 utils 方法:

class Utils {
  public static identifier = "linbudu";

  public static makeUHappy() {
    Utils.studyWithU();
    // ...
  }

  public static studyWithU() { }
}

Utils.makeUHappy();

override 重写

子类覆盖父类的方法时,并不能确保子类的这一方法能覆盖父类方法,万一父类中 不存 这方法 给这方法 改了名?所以,Typescript 4.3 新增了 override 关键字,来 确保 子类尝试 覆盖 的方法一定 在父类中 存在

class Base {
  printWithLove () {}
}

class Derived extends Base {
  override print () {}
}

这里 ts 将会给出错误,因为 尝试覆盖的方法并未在父类中声明

abstract 抽象类与抽象方法

抽象类使用 abstract 关键字声明:

abstract class Male {
  abstract name:string;
  abstract eat (): string;
  abstract get age (): number
}

注意,抽象类中的成员也需要使用 abstract 关键字才能被视为抽象类成员,如这里的抽象方法。我们可以实现(implements)一个抽象类:

class Men extends Male {
  name: string = 'zhangsan';
  eat(): string {
    return '吃饭'
  }
  get age(): number {
    return 23
  }
}

此时,我们必须 完全实现 这个抽象类的每一个 抽象成员。需要注意的是,在 TypeScript 中无法声明 静态抽象成员

对于抽象类,它的本质就是 描述类的结构。看到结构,你是否又想到了 interface?是的。interface 不仅可以 声明函数结构,也可以 声明类的结构

interface Male {
  name: string;
  eat (): string;
  get age (): number;
}

class Men implements Male {
  name: string = 'zhangsan';
  eat(): string {
    return '吃饭'
  }
  get age(): number {
    return 23
  }
}

在这里,我们让 去实现了一个接口。这里接口的作用和抽象类一样,都是描述这个 类的结构。除此以外,我们还可以使用 Newable Interface 来描述一个类的结构(类似于描述函数结构的 Callable Interface):

class Foo { 
  constructor (public name: string) {
    this.name = name
  }
  getName () {
    return this.name
  }
}

interface FooStruct {
  new(name:string): Foo
}

declare const NewableFoo: FooStruct;

const foo = new NewableFoo('zhangsan');

foo.getName()

抽象类 和 接口的区别

抽象类 是对 类本质抽象,表达的是 is a 的关系。比如:male is a Human。抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现。

接口是对 行为 的抽象,表达的是 like a 的关系。比如:Baoma like a plane(它有飞的功能一样可以飞),但其本质上 is a Car。接口的核心是定义行为,即实现类可以做什么,至于实现类主体是谁、是如何实现的,接口并不关心。

断言

双重断言

如果在使用类型断言时,原类型断言类型 之间差异过大,也就是指鹿为马太过离谱,离谱到了指鹿为霸王龙的程度,TypeScript 会给你一个类型报错:

const str: string = 'zhangsan';

// 从 X 类型 到 Y 类型的断言可能是错误的,blabla
(str as { handler: () => {} }).handler()

此时它会提醒你先断言到 unknown 类型,再断言到预期类型,就像这样:

const str: string = 'zhangsan';
(str as unknown as { handler: () => {} }).handler();

这是因为你的 断言类型原类型 的差异太大,需要先断言到一个 通用类,即 any / unknown。这一通用类型包含了所有可能的类型,因此断言到它和从它断言到另一个类型差异不大。

非空断言

类型工具

类型别名

索引类型

由于 JavaScript 中,对于 obj[prop] 形式的访问会将数字索引访问转换为字符串索引访问,也就是说, obj[599]obj['599'] 的效果是一致的。因此,在字符串索引签名类型中我们仍然可以声明数字类型的键。类似的,symbol 类型也是如此:

const foo: AllStringTypes = {
  "linbudu": "599",
  599: "linbudu",
  [Symbol("ddd")]: 'symbol',
}

索引类型查询

刚才我们已经提到了索引类型查询,也就是 keyof 操作符。严谨地说,它可以将对象中的所有键转换为对应字面量类型,然后再组合成联合类型。注意,这里并不会将数字类型的键名转换为字符串类型字面量,而是仍然保持为数字类型字面量。

interface Foo {
  zhangsan: 'xxx',
  599: 2
}

type FooKeys = keyof Foo; // "zhangsan" | 599
// 在 VS Code 中悬浮鼠标只能看到 'keyof Foo'
// 看不到其中的实际值,你可以这么做:
type FooKeys = keyof Foo & {}; // "zhangsan" | 599

除了应用在已知的对象类型结构上以外,你还可以直接 keyof any 来生产一个联合类型,它会由所有可用作对象键值的类型组成:string | number | symbol。也就是说,它是由无数字面量类型组成的,由此我们可以知道, keyof 的产物必定是一个联合类型。

索引类型访问

JavaScript 中我们可以通过 obj[expression] 的方式来动态访问一个对象属性(即计算属性),expression 表达式会先被执行,然后使用返回值来访问属性。而 TypeScript 中我们也可以通过类似的方式,只不过这里的 expression 要换成类型。接下来,我们来看个例子:

interface NumberRecord {
  [key: string]: number;
}

type PropType = NumberRecord[string]; // number

这里,我们使用 string 这个类型来访问 NumberRecord。由于其内部声明了数字类型的索引签名,这里访问到的结果即是 number 类型。注意,其访问方式与返回值均是类型。

更直观的例子是通过字面量类型来进行索引类型访问:

interface Foo {
  propA: number;
  propB: boolean;
}

type PropAType = Foo['propA']; // number
type PropBType = Foo['propB']; // boolean

看起来这里就是普通的值访问,但实际上这里的 'propA''propB' 都是字符串字面量类型,而不是一个 JavaScript 字符串值。索引类型查询的本质其实就是,通过键的字面量类型('propA')访问这个键对应的键值类型(number)。

看到这你肯定会想到,上面的 keyof 操作符能一次性获取这个对象所有的键的字面量类型,是否能用在这里?当然,这可是 TypeScript 啊。

interface Foo {
  propA: number;
  propB: boolean;
  propC: string;
  propD: string;
}

type PropTypeUnion = Foo[keyof Foo]; // string | number | boolean

使用 字面量联合类型 进行 索引类型 访问时,其结果就是将 联合类型 每个分支 对应的类型进行访问后的结果,重新组装成联合类型。索引类型查询、索引类型访问通常会和映射类型一起搭配使用,前两者负责访问键,而 映射类型 在其基础上访问键值类型,我们在下面一个部分就会讲到。

注意,在未声明索引签名类型的情况下,我们不能使用 NumberRecord[string] 这种原始类型的访问方式,而只能通过键名的字面量类型来进行访问。

interface Foo {
  propA: number;
}

// 类型“Foo”没有匹配的类型“string”的索引签名。
type PropAType = Foo[string]; 

索引类型的最佳拍档之一就是映射类型,同时映射类型也是类型编程中常用的一个手段。

类型守卫

is

基于 in 与 instanceof 的类型保护

类型断言守卫

asserts

结构化类型系统

在 TypeScript 中,你可能遇见过以下这样“看起来不太对,但竟然能正常运行”的代码:

class Cat {
  eat() { }
}

class Dog {
  eat() { }
}

function feedCat(cat: Cat) { }

feedCat(new Dog())

这是因为,TypeScript 比较两个类型 并非 通过 类型的名称,而是比较这两个类型上 实际拥有的属性与方法。也就是说,这里实际上是比较 Cat 类型上的属性是否都存在于 Dog 类型上。

标称类型系统

装箱类型

基础类型包装类型子类型


分布式条件类型

条件分布式起作用的条件:

  • 类型参数需要是一个联合类型
  • 参数需要通过泛型参数的方式传入
  • 条件类型中的泛型参数不能被包裹
type isNever<T> = T extends never ? true : false;
type T0 = isNever<never>; // type T0 = never;

泛型参数添加包裹,让分布式条件类型失效

type isNever<T> = [T] extends [never] ? true : false;
type T0 = isNever<never>; // type T0 = true;

T[number] 用来获取 元组 的元素类型联合,T['length'] 用来获取元组的元素长度

type T0 = ['a', 'b', 'c'];
type T1 = T0['length']; // 3
type T2 = T0[number]; // 'a' | 'b' | 'c'

类型转换

枚举转联合类型

enum Position {
  inner,
  outer
}

type Keys = keyof typeof Position; // type Keys = "inner" | "outer"

Typescript 中需要使用 typeof Position 来取实际的枚举类型(不然就是Number的子类型)。可以理解 枚举 有可能是一个 值类型

上下文类型

void 返回值类型下的特殊情况

上下文类型同样会推导并约束函数的返回值类型,但存在这么个特殊的情况,当内置函数类型的返回值类型为 void 时:

type CustomHandler = (name: string, age: number) => void;

const handler1: CustomHandler = (name, age) => true
const handler2: CustomHandler = (name, age) => '123'
const handler3: CustomHandler = (name, age) => null
const handler4: CustomHandler = (name, age) => undefined

上下文类型对于 void 返回值类型的函数,并不会真的要求它啥都不能返回。然而,虽然这些函数实现可以返回任意类型的值,但对于调用结果的类型,仍然是 void:

const result1 = handler1('linbudu', 599); // void
const result2 = handler2('linbudu', 599); // void
const result3 = handler3('linbudu', 599); // void
const result4 = handler4('linbudu', 599); // void

我们实际上就是在将 更少参数的函数 赋值给 具有更多参数的函数 类型!再看一个更明显的例子:

在上下文类型中,我们实现的表达式可以只使用 更少的参数,而 不能使用更多,这还是因为上下文类型基于位置的匹配,一旦参数个数超过定义的数量,那就没法进行匹配了

function handler (arg: string) {
  console.log(arg)
}

handler('123', 123); // 应有 1 个参数,但获得 2 个。ts(2554)

只能将更少参数的 函数 赋值给 具有更多参数的 函数,才不会触发错误提示

function handler(arg: string) {
  console.log(arg);
}

function useHandler(callback: (arg1: string, arg2: number) => void) {
  callback('linbudu', 599);
}

useHandler(handler);

TSConfig 中的 StrictFunctionTypes

在默认情况下,对函数参数的检查采用 双变( bivariant ) ,即逆变与协变都被认为是可接受的。如果启用 strictFunctionTypes 选项将 启用函数参数类型逆变检查

// method 声明
interface T1 {
  func(arg: string): number;
}

// property 声明
interface T2 {
  func: (arg: string) => number;
}

进行如此约束的原因即,对于 property 声明,才能在 开启严格函数类型检查 的情况下享受到基于 逆变的参数类型检查。而对于 method 声明(以及构造函数声明),其 无法 享受到这一更严格的检查。

展平交叉类型

type MarkPropsAsOptional<
  T extends object,
  K extends keyof T = keyof T         // 在不传入第二个泛型参数时,即 T 的全量属性 
> = Partial<Pick<T, K>> & Omit<T, K>

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

type T0 = MarkPropsAsOptional<Person, 'name' | 'age'>; // type T0 = Partial<Pick<Person, "name" | "age">> & Omit<Person, "name" | "age">

// 使用 Pick 展平交叉类型
type T1 = Pick<T0, keyof T0>; // type T1 = { name?: string; age?: number; gender: boolean; }

集合工具类型

// 并集
type Concurrence<A, B> = A | B;

// 交集
type Intersection<A, B> = A extends B ? A : never;

// 差集
type Difference<A, B> = A extends B ? never : A;

// 补集
type Complement<A, B extends A> = Difference<A, B>;

互斥工具类型

interface VIP {
  vipExpires: number;
}

interface CommonUser {
  promotionUsed: boolean;
}

type Without<T, U> = {
  [K in Exclude<keyof T, keyof U>]?: never
}

type XOR<T, U> = (Without<T, U> & U) | (Without<U, T> & T)

const T0: XOR<VIP, CommonUser> = {
  vipExpires: 1
}

const T1: XOR<VIP, CommonUser> = {      // error
  vipExpires: 1,
  promotionUsed: true
}

const T2: XOR<VIP, CommonUser> = {
  promotionUsed: true
}

空对象 和 {name: string, age: number} 二选一

type XORStruct = XOR<{}, {name: string, age: number}>

const T1: XORStruct = {};
const T2: XORStruct = { name: 'sa' }; // error
const T3: XORStruct = {
  name: 'sa',
  age: 1
}

模式匹配工具类型

生态

tsd 断言库

vue-tsc

ts-node

namespace 与 module

官方建议在现在代码中使用 module 替代 namespace

三斜线 reference 和 import 区别

使用三斜线 reference 引入,有常用两种方式 path和types

  • types 一般引入外部依赖的声明时使用
  • path 一般引入自己写的声明时使用

引入非模块类的声明文件。非模块的声明文件引入后为全局类型,可以直接使用

import