盒子
文章目录
  1. Q1. 泛型 + 使用的参考标准?
  2. Q2. 什么叫泛型约束
    1. 2.1 确保属性存在
    2. 2.2 检查对象上的键是否存在
  3. Q3:泛型默认类型
  4. Q4:泛型条件类型
  5. Q5:泛型工具
    1. 5.1 Partial
    2. 5.2 Record
    3. 5.3 Pick
    4. 5.4 Exclude
    5. 5.5 ReturnType
  6. Q6. 使用泛型创建对象
    1. 6.1 构造签名
    2. 6.1 构造函数类型
    3. 6.2 构造函数类型的应用
    4. 6.3 使用泛型创建对象

TypeScript 每天起床一早操

每天起床练体操,理论 + 实践 助力小学生茁壮成长!TypeScript 小学生的进击之路:

Q1. 泛型 + 使用的参考标准?

  1. 当你的「函数」、「接口」、「类」将处理多种数据类型时;
  2. 当「函数」、「接口」、「类」在多个地方使用该数据类型时。

Q2. 什么叫泛型约束

我们可能限制「每个类型变量接受的类型数量」,这就是泛型约束的作用。

2.1 确保属性存在

我们需要做的就是让「类型变量」 extends 一个含有我们所需属性的接口(可以思考一下?为什么是接口呢?type 行不行….)

答案是可以的,但是什么时候继承 interface,什么时候继承 type 呢?

使用逗号来实现多个继承:<T extends Length, Type2, Type3>

切换 ⇩
interface Length {
length: number
}

function identity<T extends Length>(arg: T): T {
console.log(arg.length) // 可以获取length属性
return arg
}

2.2 检查对象上的键是否存在

TypeScript 2.1 版本引入了 keyof 操作符,该操作符可以用于获取某种类型的「所有键」,其返回类型是「联合类型」

切换 ⇩
interface Person {
name: string
age: number
location: string
}

type K1 = keyof Person // "name" | "age" | "location"
type K2 = keyof Person[] // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person } // string | number
console.log(K1)
'K1' only refers to a type, but is being used as a value here.
console.log(Person)

'Person' only refers to a type, but is being used as a value here.
说明类型是不能作为值打印出来
enum Difficulty {
Easy,
Intermediate,
Hard
}

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}

let tsInfo = {
name: "Typescript",
supersetOf: "Javascript",
difficulty: Difficulty.Intermediate
}

let difficulty: Difficulty =
getProperty(tsInfo, 'difficulty') // OK

let supersetOf: string =
getProperty(tsInfo, 'superset_of') // Error

Q3:泛型默认类型

interface A<T = string> {
name: T
}

const strA: A = { name: "Semlinker" }
const numB: A<number> = { name: 101 }
// 要默认参数干啥...

Q4:泛型条件类型

T extends U ? X : Y

以上表达式的意思是:若 T 能够赋值给 U,那么类型是 X,否则为 Y。在条件类型表达式中,我们通常还会结合 infer 关键字,实现类型抽取:

interface Dictionary<T = any> {
[key: string]: T
}

type StrDict = Dictionary<string>

type DictMember<T> = T extends Dictionary<infer V> ? V : never
type StrDictMember = DictMember<StrDict> // string

Q5:泛型工具

为了方便开发者 TypeScript 内置了一些常用的工具类型,比如 Partial、Required、Readonly、Record 和 ReturnType 等。

5.1 Partial

Partial 的作用就是将某个类型里的属性全部变为可选项 ?。

5.2 Record

切换 ⇩
interface PageInfo {
title: string
}
type Page = 'Home' | 'About' | 'Contact'
const x: Record<Page, PageInfo> = {
about: {
title: 'ahout'
},
concact: {
title: 'concact'
},
home: {
title: 'homeß'
}
}
// 1. 提取通用的 property 出来,这里是 title
// 2. 保证 key 值,这里是 Page

5.3 Pick

将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。

切换 ⇩
interface Todo {
title: string
description: string
completed: boolean
}

type what = "title" | "completed"

type TodoPreview = Pick<Todo, what>

const todo: TodoPreview = {
title: "Clean room",
completed: false
};

5.4 Exclude

将某个类型中属于另一个的类型移除掉

切换 ⇩
type T0 = Exclude<"a" | "b" | "c", "a"> // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b"> // "c"
type T2 = Exclude<string | number | (() => void), Function> // string | number

5.5 ReturnType

获取函数 T 的返回类型

Q6. 使用泛型创建对象

6.1 构造签名

在 TypeScript 接口中,你可以使用 new 关键字来描述一个构造函数:

切换 ⇩
class FirstClass {
id: number | undefined
}

class SecondClass {
name: string | undefined;
}

class GenericCreator<T> {
create(): T {
return new T()
}
}

const creator1 = new GenericCreator<FirstClass>()
const firstClass: FirstClass = creator1.create()

const creator2 = new GenericCreator<SecondClass>()
const secondClass: SecondClass = creator2.create()

// 抛错:'T' only refers to a type, but is being used as a value here.
// T 只是代表一种类型,但却作为值使用

在 TypeScript 接口中,你可以使用 new 关键字来描述一个构造函数:

interface Point {
new (x: number, y: number): Point
}

以上接口中的 new (x: number, y: number) 我们称之为「构造签名」,其语法如下:

ConstructSignature:  new TypeParametersopt ( ParameterListopt) TypeAnnotationopt

在上述的构造签名中,TypeParametersopt 、ParameterListopt 和 TypeAnnotationopt 分别表示:可选的类型参数、可选的参数列表和可选的类型注解。与该语法相对应的几种常见的使用形式如下:

new C   // 可选的类型参数
new C ( ... ) // 可选的参数列表
new C < ... > ( ... ) // 可选的类型注解

6.1 构造函数类型

  1. 包含一个或多个构造签名的对象类型被称为构造函数类型;
  2. 构造函数类型可以使用「构造函数类型字面量」或包含「构造签名」的对象类型字面量来编写。

构造函数类型字面量 👇:

new < T1, T2, ... > ( p1, p2, ... ) => R

与「对象类型字面量」是等价的:

{ new < T1, T2, ... > ( p1, p2, ... ) : R }

举个实际的示例:

// 「构造函数类型字面量」
new (x: number, y: number) => Point

等价于:「对象类型字面量」

{ new (x: number, y: number): Point }

6.2 构造函数类型的应用

切换 ⇩
interface Point {
new (x: number, y: number): Point
x: number
y: number
}

class Point2D implements Point {
readonly x: number
readonly y: number

constructor(x: number, y: number) {
this.x = x
this.y = y
}
}

const point: Point = new Point2D(1, 2)
// 抛错:
// Class 'Point2D' incorrectly implements interface 'Point'.
// Type 'Point2D' provides no match for the signature 'new (x: number, y: number): Point'.

要解决这个问题,我们就需要把对前面定义的 Point 接口进行分离,即把接口的属性和构造函数类型进行分离:

interface Point {
x: number;
y: number;
}

interface PointConstructor {
new (x: number, y: number): Point;
}

完成接口拆分之后,除了前面已经定义的 Point2D 类之外,我们又定义了一个 newPoint 工厂函数,该函数用于根据传入的 PointConstructor 类型的构造函数,来创建对应的 Point 对象。

切换 ⇩
class Point2D implements Point {
readonly x: number
readonly y: number;

constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}

function newPoint(
pointConstructor: PointConstructor,
x: number,
y: number
): Point {
return new pointConstructor(x, y);
}

const point: Point = newPoint(Point2D, 1, 2)

6.3 使用泛型创建对象

class GenericCreator<T> {
create<T>(c: { new (): T }): T {
return new c();
}
}

在以上代码中,我们重新定义了 create 成员方法,根据该方法的签名,我们可以知道该方法接收一个参数,其类型是构造函数类型,且该构造函数不包含任何参数,调用该构造函数后,会返回类型 T 的实例。

如果构造函数含有参数的话,比如包含一个 number 类型的参数时,我们可以这样定义 create 方法:

create(c: { new(a: number): T; }, num: number): T {
return new c(num);
}

更新完 GenericCreator 泛型类,我们就可以使用下面的方式来创建 FirstClass 和 SecondClass 类的实例:

const creator1 = new GenericCreator<FirstClass>();
const firstClass: FirstClass = creator1.create(FirstClass);

const creator2 = new GenericCreator<SecondClass>();
const secondClass: SecondClass = creator2.create(SecondClass);