关于 TypeScript 的一些小小知识点(你值得拥有✌️)

202 阅读15分钟

「这是我参与2022首次更文挑战的第32天,活动详情查看:2022首次更文挑战

一、关于 TypeScript

JavaScript的超集,添加了静态类型检查和一些尚未正式发布的ECMAScript特性,最终会被编译成JavaScript执行。

二、基础

1.基础类型

www.tslang.cn/docs/handbo…

为了让程序有价值,我们需要能够处理最简单的数据单元:数字,字符串,结构体,布尔值等。 TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用。

boolean、number、string、void、undefined、null、symbol、bigint、any、unknown、never、数组、元组、对象、枚举。

enum Fruits {
	apple = 1,
	watermelon = 2,
	peach = 3
}

(枚举类型能更加清晰地描述常量)

2.接口

type与interface的区别

  1. type确实不能使用extends语法,但是type可以通过&(交叉类型)来达到同样的效果。

  2. 用联合类型生成的type,不能被interface extends xxx,也不能使用class来implements。

  3. type不会声明合并。

  4. interface内部不能使用映射类型语法。

interface A {
	a1: number;
}

interface B extends A { }

type A1 = number;

// 报错
type B1 extends A1 {}

3.函数

www.tslang.cn/docs/handbo…

4.原理

现在我们项目中的ts代码会经过babel编译,与tsc相比,babel不做类型检查,会直接去掉类型信息。因此在部分语法上有细微的区别。

  1. const enum:const enum 是在编译期间把 enum 的引用替换成具体的值,需要解析类型信息,而 babel 并不会解析,所以不支持。

  2. 不支持 namespace 的跨文件合并,不支持导出非 const 的值。

www.babeljs.cn/

www.typescriptlang.org/

三、进阶

1.泛型

官方介绍:软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以在自己的数据类型来使用组件。

实际用途:增加类型定义的复用性。

// js
function Func(arg) {
  return arg;
}
 
// 使用基础类型
function Func(arg: any): any {
  return arg;
}
 
// 泛型
function Func<T>(arg: T): T {
  return arg;
}
 
type Func = <T>(arg: T) => T;

2.联合类型 & 交叉类型

  1. 联合类型(Union Types) 联合类型是将变量命名为多个可能类型。
interface Brid {
	fly();
	layEggs();
}

interface Fish {
	swim();
	layEggs();
}

function getSmallPet(): Fish | Brid {
	return 
}

let pet = getSmallPet();

// 类型“Brid | Fish”上不存在属性“swim”。
// 类型“Brid”上不存在属性“swim”

pet.swim();
pet.layEggs();

联合类型使用时,如果需要调用变量的属性或方法,必须是联合的多个类型中共有的方法,否则会报错。

  1. 交叉类型(Intersection Types) 交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。 例如, Person & Serializable & Loggable同时是 Person Serializable Loggable。 就是说这个类型的对象同时拥有了这三种类型的成员。
function extend<T, U>(first: T, second: U): T & U {
	let result = <T & U>{}

	for (let id in first) {
		(<any>result)[id] = (<any>first)[id];
	}

	for (let id in second) {
		if (!result.hasOwnProperty(id)) {
			(<any>result)[id] = (<any>second)[id];
		}
	}

	return result
}

class AdvancedTypesClass {
	constructor(public name: string) { }
}

interface LoggerInterface {
	log(): void;
}

class AdvancedTypesLoggerClass implements LoggerInterface {
	log(): void {
		console.log('console logging');
	}
}

var logger = new AdvancedTypesLoggerClass();

var extend1 = extend(new AdvancedTypesClass("string"), new AdvancedTypesLoggerClass());
var e = extend1.name;
console.log(e);     // string
extend1.log();      // console logging

3.类型断言

类型断言(Type Assertion)可以用来手动指定一个值的类型。

语法:Value as Type<Type>Value。在tsx中必须使用前者。

用途:

  1. 将一个联合类型断言为其中一个类型

    interface Cat {
    	name: string;
    	run(): void;
    }
    interface Fish {
    	name: string;
    	swim(): void;
    }
    
    
    // error
    function isFish(animal: Cat | Fish) {
    	if (typeof (animal as Fish).swim === 'function') {
    		return true;
    	}
    	return false;
    }
    
    
    // 编译不会报错,运行会报错
    function swim(animal: Cat | Fish) {
    	(animal as Fish).swim();
    }
    
    
    const tom: Cat = {
    	name: 'Tom',
    	run() { console.log('run') }
    };
    swim(tom);
    
    // right
    function isFish(animal: Cat | Fish) {
    	if (typeof (animal as Fish).swim === 'function') {
    		return true;
    	}
    	return false;
    }
    
  2. 继承断言

    class ApiError extends Error {
      code: number = 0;
    }
    class HttpError extends Error {
      statusCode: number = 200;
    }
    
    function isApiError(error: Error) {
      if (typeof (error as ApiError).code === 'number') {
        return true;
      }
      return false;
    }
    
  3. 断言为any

    (window as any).foo = 1;
    
  4. 断言为具体类型

    function getCacheData(key: string): any {
      return (window as any).cache[key];
    }
    
    interface Cat {
      name: string;
      run(): void;
    }
    
    const tom = getCacheData('tom') as Cat;
    tom.run();
    

断言原则:

  • 联合类型可以被断言为其中一个类型
  • 父类可以被断言为子类
  • 任何类型都可以被断言为 any
  • any 可以被断言为任何类型
  • 要使得 A 能够被断言为 B,只需要 A 兼容 BB 兼容 A 即可

使用泛型

function getCacheData<T>(key: string): T {
  return (window as any).cache[key];
}

interface Cat {
  name: string;
  run(): void;
}

const tom = getCacheData<Cat>('tom');
tom.run();

4.类型保护

TypeScript里有类型保护机制。要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个类型谓词:

function isString(test: any): boolean {
  return typeof test === "string";
}

function isString(test: any): test is string {
  return typeof test === "string";
}

function example(foo: any) {
  if (isString(foo)) {
    console.log("it is a string" + foo);
    console.log(foo.length); // string function
    // 如下代码编译时会出错,运行时也会出错,因为 foo 是 string 不存在toExponential方法
    console.log(foo.toExponential(2));
  }
  // 编译不会出错,但是运行时出错
  console.log(foo.toExponential(2));
}
example("hello world");

5.命名空间

命名空间是多个命名的容器。

namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean;
  }

  const lettersRegexp = /^[A-Za-z]+$/;
  const numberRegexp = /^[0-9]+$/;

  export class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
      return lettersRegexp.test(s);
    }
  }

  export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
      return s.length === 5 && numberRegexp.test(s);
    }
  }
}

let strings = ["Hello", "98052", "101"];

let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

for (let s of strings) {
  for (let name in validators) {
    console.log(`"${s}" - ${validators[name].isAcceptable(s) ? "matches" : "does not match"} ${name}`);
  }
}

6.装饰器

官方介绍:装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

实际用途:减少高阶组件的嵌套、增加函数的复用性。

  1. 装饰器工厂:声明一个装饰器工厂函数,返回一个表达式。
function color(value: string) { // 这是一个装饰器工厂
  return function (target) { //  这是装饰器
    // do something with "target" and "value"...
  }
}

装饰器组合:多个装饰器应用在同一个声明上

function f() {
  console.log("f(): evaluated");
  return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("f(): called");
  }
}

function g() {
  console.log("g(): evaluated");
  return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("g(): called");
  }
}

class C {
  @f()
  @g()
  method() { }
}

// f(): evaluated
// g(): evaluated
// g(): called
// f(): called

类中不同声明上的装饰器将按以下规定的顺序应用:

  1. 参数装饰器,然后依次是方法装饰器访问符装饰器,或属性装饰器应用到每个实例成员。

  2. 参数装饰器,然后依次是方法装饰器访问符装饰器,或属性装饰器应用到每个静态成员。

  3. 参数装饰器应用到构造函数。

  4. 类装饰器应用到类。

  5. 类装饰器

类装饰器修改类属性

类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。

function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return "Hello, " + this.greeting;
  }
}

类装饰器重载构造函数

如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。

function classDecorator<T extends { new(...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    newProperty = "new property";
    hello = "override";
  }
}

@classDecorator
class Greeter {
  property = "property";
  hello: string;
  constructor(m: string) {
    this.hello = m;
  }
}

类装饰器替换目标类

类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。

interface IProps { }

export function withHeader(str: string = 'header') {
  return function (Cmp: typeof React.Component): any {
    return function (props: IProps) {
      return (
        <div>
        <div className= "header" > { str } < /div>
        < Cmp {...props } />
          < /div>
      );
    };
  };
}

@withHeader('测试')
export class GetQualityOrder extends Component {
  constructor(props: IProps) {
    super(props);
  }

  render() {
    return <div>content < /div>;
  }
}
  1. 类方法装饰器

方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。方法装饰器不能用在声明文件( .d.ts),重载或者任何外部上下文(比如declare的类)中。

方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字。
  3. 成员的属性描述符
function enumerable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = value;
  };
}

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }

  @enumerable(false)
  greet() {
    return "Hello, " + this.greeting;
  }
}
  1. 访问器装饰器 访问器装饰器声明在一个访问器的声明之前(紧靠着访问器声明)。 访问器装饰器应用于访问器的 属性描述符并且可以用来监视,修改或替换一个访问器的定义。 访问器装饰器不能用在声明文件中(.d.ts),或者任何外部上下文(比如 declare的类)里。
class Myclass {
  private _a: number;
  private _b: number;
  constructor(a: number, b: number) {
    this._a = a;
    this._b = b;
  }

  @configurable(true)            // 可以修改和删除
  get a() { return this._a; }

  @configurable(false)           // 不可以修改和删除
  get b() { return this._b; }

  set a(num: number) {
    this._a = num;
  }

  set b(num: number) {
    this._b = num;
  }

}

function configurable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.configurable = value;
  };
}

console.log(delete Myclass.prototype.a); // true
console.log(delete Myclass.prototype.b); // false

TypeScript不允许同时装饰一个成员的get和set访问器。取而代之的是,一个成员的所有装饰的必须应用在文档顺序的第一个访问器上。这是因为,在装饰器应用于一个属性描述符时,它联合了get和set访问器,而不是分开声明的。

不允许同时修改get和set

// wrong
class Myclass {
  private _a: number;
  constructor(a: number) {
    this._a = a;
  }

  @configurable(true)            // a 的 Getter先出现,则装饰器写于此
  get a() { return this._a; }

  @configurable(true)            // 错误写法
  set a(num: number) {
    this._a = num;
  }
}

// right
class Myclass {
  private _a: number;
  constructor(a: number) {
    this._a = a;
  }

  @configurable(true)            // a 的 Getter先出现,则装饰器写于此
  get a() { return this._a; }

  set a(num: number) {            // 此处无需再用装饰器装饰
    this._a = num;
  }
}

function configurable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.configurable = value;
  };
}
  1. 属性装饰器 属性装饰器声明在一个属性声明之前(紧靠着属性声明)。 属性装饰器不能用在声明文件中(.d.ts),或者任何外部上下文(比如 declare的类)里。

    属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:

    1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
    2. 成员的名字。

实例成员

class MyClass {
  constructor() {
  }

  @methodDecoratorName
  myMethod() {
    return "I am method named myMethod"
  }
}

function methodDecoratorName(target, propertyKey, descriptor) {
  console.log('target:', target);                               // target指向方法所属类的原型
  console.log('target.myMethod():', target.myMethod());         // 可以在这使用该方法
  console.log('propertyKey:', propertyKey);                     // 该方法的方法名
  console.log('descriptor:', descriptor);                       // 该方法的属性描述符
}

let cls = new MyClass()

// target: { myMethod: [Function (anonymous)] }
// target.myMethod(): I am method named myMethod
// propertyKey: myMethod
// descriptor: {
//   value: [Function (anonymous)],
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

静态成员

class MyClass {
  constructor() {
  }

  @methodDecoratorName
  static myMethod() {
    return "I am a static member"
  }
}

function methodDecoratorName(target, propertyKey, descriptor) {
  console.log('target:', target);
  console.log('target.myMethod():', target.myMethod());
  console.log('propertyKey:', propertyKey);
  console.log('descriptor:', descriptor);
}

let cls = new MyClass()

// target: [Function: MyClass] { myMethod: [Function (anonymous)] }
// target.myMethod(): I am a static member
// propertyKey: myMethod
// descriptor: {
//   value: [Function (anonymous)],
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

底层

实例对象传入的target为MyClass.property,静态对象传入的target为MyClass

// 实例对象
var MyClass = /** @class */ (function () {
  function MyClass() {
  }
  MyClass.prototype.myMethod = function () {
    return "I am a static member";
  };
  __decorate([
    methodDecoratorName
  ], MyClass.prototype, "myMethod", null);
  return MyClass;
}());


// 静态对象
var MyClass = /** @class */ (function () {
  function MyClass() {
  }
  MyClass.myMethod = function () {
    return "I am a static member";
  };
  __decorate([
    methodDecoratorName
  ], MyClass, "myMethod", null);
  return MyClass;
}());

四、高级

类型推导

基础类型推导

TypeScript里,在有些没有明确指出类型的地方,类型推论会帮助提供类型。

let a = 3;
// let a: number
 
function hello(person: string) {
    return "Hello, " + person
}
// function hello(person: string): string
联合类型推导

如果有多个类型将联合推导

let x = [0, 1, null];
// let x: (number | null)[]

没有类型能共用时

let arr = [
  { name: "peter", type: "student" },
  { name: "tom", age: 18 },
];
/*
let arr: ({
  name: string;
  type: string;
  age?: undefined;
} | {
  name: string;
  age: number;
  type?: undefined;
})[]
*/

这种时候类型推导不一定能够得到我们想要结果,就需要我们明确指出类型:

interface Arr {
  name: string;
  type?: string;
  age?: number;
}

let arr: Arr[] = [
  { name: "peter", type: "student" },
  { name: "tom", age: 17 },
];
上下文类型

TypeScript类型推论也可能按照相反的方向进行。 这被叫做“按上下文归类”。

let a = 3;
a = ""; // Error: 不能将类型“string”分配给类型“number”。

function test(value: string) {
  console.log(value);
}
test(1); // Error: 类型“number”的参数不能赋给类型“string”的参数。
typeof 操作符

在 TypeScript 中, typeof 操作符可以用来获取一个变量或对象的类型。

const sem = { name: "semlinker", age: 30 };
type Sem = typeof sem;
/*
type Sem = {
    name: string;
    age: number;
}
*/
const 断言

TypeScript 3.4 引入了一种新的字面量构造方式,也称为 const 断言。

let foo = {
  name: "foo" as const,
  contents: "contents",
};
/*
let foo: {
  name: "foo";
  contents: string;
}
*/
let goo = {
  name: "goo",
  contents: "contents",
} as const;
/*
let goo: {
  readonly name: "goo";
  readonly contents: "contents";
}
*/

高级类型

可索引类型
interface SomeArray {
  [index: number]: any;
}
const someArray: SomeArray = [1, 2, "123", true,];

interface SomeObject {
  [index: string]: any;
}
const someObject: SomeObject = {
  name: "peter",
  age: 18,
  //...
};
索引类型

使用索引类型,编译器就能够检查使用了动态属性名的代码。 例如,一个常见的JavaScript模式是从对象中选取属性的子集。

function pick(o, names) {
  return names.map((n) => o[n]);
}
// function pick(o: any, names: any): any
  • extends

有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。也可理解为赋值。


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

function testExtends<T extends TestExtends>(obj: T) {
  console.log(obj.age);
  console.log(obj.name);
}

testExtends({ name: "taotao", age: 18, type: "person" });
testExtends({ age: 18 }); // Error 类型 "{ age: number; }" 中缺少属性 "name",但类型 "TestExtends" 中需要该属性。
  • keyof

keyof 操作符可以用来一个对象中的所有 key 值, 类似于 Object.keys()

const obj = {
  name: "liqi",
  age: 18,
  type: "person",
};

type K1 = keyof typeof obj; // type K1 = "name" | "age" | "type"
/*
typeof obj = {
  name: string;
  age: number;
  type: string;
}
keyof typeof obj = "name" | "age" | "type"
*/

我们再回来看例子

function pick(o, names) {
  return names.map((n) => o[n]);
}
  • 第一个参数 o 泛型 T
  • 第二个参数 names 是一个由参数 o 的属性组成的数组,声明泛型 K,类型为 K extends keyof Tnames 的类型为 K[]
  • 返回值我们通过类型访问符 T[K] 便可以取得对应属性值的类型,他们的数组 T[K][] 正是返回值的类型。
function pick<T, K extends keyof T>(o: T, names: K[]): T[K][] {
  return names.map((n) => o[n]);
}

const testPick = {
  name: "taotao",
  age: 18,
};
const result1 = pick(testPick, ["age", "name"]);
// const result1: (string | number)[]

const result2 = pick([], ["map"]);
// const result2: (<U>(callbackfn: (value: any, index: number, array: any[]) => U, thisArg?: any) => U[])[]
映射类型

我们有一个 User 接口,现在有一个需求是把 User 接口中的成员全部变成可选的,我们应该怎么做?难道要重新一个个 : 前面加上 ?

interface User {
  username: string
  id: number
  token: string
  avatar: string
  role: string
}

这个时候映射类型就派上用场了,映射类型的语法是 [K in Keys] in 关键字,类似于 for .. in

type Keys = "option1" | "option2";
type Flags = { [K in Keys]: boolean };
/*
type Flags = {
  option1: boolean;
  option2: boolean;
}
*/

然后我们需要将 keyof T 的属性名称一一映射出来 [K in keyof T],最后加上结果

type partial<T> = { [K in keyof T]?: T[K] };
条件类型

条件类型够表示非统一的类型,以一个条件表达式进行类型关系检测,从而在两种类型中选择其一:

T extends U ? X : Y

类似于JavaScript中的三元条件运算符

type Filter<T, U> = T extends U ? T : never;
type R1 = Filter<string | number | (() => void), Function>;
// type R1 = () => void
type TestFunction = {
    name: string;
    age: number;
    getName: () => void;
};
 
type FindPropertyFunction<T> = {
    [K in keyof T]: T[K] extends Function ? T[K] : never;
};
 
type Test = FindPropertyFunction<TestFunction>;
/*
type Test = {
    name: never;
    age: never;
    getName: () => void;
}
*/
 
/*
进阶
type Test = {
    getName: () => void;
}
*/

never类型表示不会是任何值,即什么都没有,甚至不是null类型

infer关键字

infer 是工具类型和底层库中非常常用的关键字,表示在 extends 条件语句中待推断的类型变量,简单来说可以用 infer 声明一个类型变量并且对它进行使用。

type func = (data: string, num: number) => void
type ParamType<T> = T extends (...param: infer P) => any ? P : T;
 
type Z = ParamType<func>
// type Z = [data: string, num: number]
type Foo = () => {
  id: number;
  name: string;
  form?: string;
};

type ReturnTypes<T> = T extends () => infer P ? P : T;

type R1 = ReturnTypes<Foo>;

/*
type R1 = {
  id: number;
  name: string;
  form?: string | undefined;
}
*/
const List = [
  { name: "liqi", age: 18 },
  { name: "peter", age: 18 },
  { name: "tom", age: 18 },
];

type ArrayType = typeof List;

type ArrayItemType<T> = T extends Array<infer P> ? P : T;

type Item = ArrayItemType<ArrayType>;
// type Item = { name: string; age: number };
常用工具类型

用 JavaScript 编写中大型程序是离不开 lodash 这种工具集的,而用 TypeScript 编程同样离不开类型工具的帮助,类型工具就是类型版的 lodash。

Partial
type Partial<T> = { [U in keyof T]?: T[U] };
DeepPartial*
type DeepPartial<T> = {
  [U in keyof T]?: T[U] extends object
  ? DeepPartial<T[U]>
  : T[U]
};
Required
type Required<T> = { [P in keyof T]-?: T[P] };
ReadOnly*
type ReadOnly<T> = { readonly [P in keyof T]: T[P] }
Mutable
type Mutable<T> = { -readonly [P in keyof T]: T[P] }
Exclude

Exclude 的作用是从 T 中排除出可分配给 U的元素.

type Exclude<T, U> = T extends U ? never : T;
type T = Exclude<1 | 2, 1 | 3> // -> 2
Record
type Records<T extends string, U> = {
  [P in T]: U
}

interface PageInfo {
  title: string;
}

type Page = "home" | "about" | "contact";
type nav = Records<Page, PageInfo>;

/*
type nav = {
  home: PageInfo;
  about: PageInfo;
  contact: PageInfo;
}
*/
Pick
type pick<T, U extends keyof T> = {
  [K in U]: T[K]
}

type A = {
  a: number;
  b: number;
  c: number;
}

type X = pick<A, "a" | "b">;
// type X = { a: number; b: number; }
type TestFunction = {
  name: string;
  age: number;
  getName: () => void;
};

type FindPropertyFunction<T> = {
  [K in keyof T]: T[K] extends Function ? T[K] : never;
};

type Test = FindPropertyFunction<TestFunction>;

/*
type Test = {
  name: never;
  age: never;
  getName: () => void;
}
*/

type FindFunctionProperty<T> = {
  [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

type Value = FindFunctionProperty<TestFunction>
// type Value = "getName"

type pick<T, U extends keyof T> = {
  [K in U]: T[K]
}

type C = pick<TestFunction, Value>
/*
type Test = {
  getName: () => void;
}
*/
Omit
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
type Foo = Omit<{ name: string, age: number }, 'name'>
// -> { age: number }

当然如果你想进一步学习类型工具的设计,建议阅读 utility-types 的源码

五、应用

is 关键字
const arr = [12, null, undefined, 7, 13, "12"];
const list1 = arr.filter((item) => !!item);
// const list1: (number | null | undefined)[]

const list2 = arr.filter((item): item is number => !!item);
// const list2: number[]
可调用类型注解
interface KPhone {
  new(): string;
}

declare const a: KPhone;

const b = new a();
// const b: string
双重断言
const a: string = '';
const b: number = a; // Error
const c: number = a as number; // Error
const d: number = a as any as number;