TS进阶
进阶
面向对象编程思想
-
TS的伪重载
TS可以在函数前面声明重载签名 ,使得在调用函数时推断出函数的返回类型,但是由于它不能重复实现同一个函数,而仅仅是对其的签名进行重载,因此称为伪重载1
2
3
4
5
6
7
8
9
10
11
12
13
14function 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 * 20;
}
}
const res1 = func(599); // number
const res2 = func(599, true); // string
const res3 = func(599, false); // number -
修饰符
- public:此类成员在类、类的实例、子类中都能被访问。
- private:此类成员仅能在类的内部被访问。
- protected:此类成员仅能在类与子类中被访问,可以将类和类的实例当成两种概念,即一旦实例化完毕,那就和类没关系了,即 不允许再访问受保护的成员 。
-
基类和派生类
**确保在派生类中复写的方法在基类中存在 使用override修饰符 **修饰要复写的方法1
2
3
4
5
6
7
8
9
10
11// 基类和派生类
class Base {
print() {
}
}
class Derived extends Base {
override print() {
}
} -
单一功能原则
一个类应该仅具有一种职责这也意味着只存在一种原因使得需要修改类的代码。如对于一个数据实体的操作,其读操作和写操作也应当被视为两种不同的职责,并被分配到两个类中。更进一步,对实体的业务逻辑和对实体的入库逻辑也都应该被拆分开来
-
开放封闭原则
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
41enum LoginType {
WeChat,
TaoBao,
TikTok,
}
abstract class LoginHandler {
abstract handler(): void
}
class WeChatLoginHandler implements LoginHandler {
handler(): void {
console.log(LoginType[0]);
}
}
class TikTokLoginHandler implements LoginHandler {
handler(): void {
console.log(LoginType[1]);
}
}
class TaoBaoLoginHandler implements LoginHandler {
handler(): void {
console.log(LoginType[2]);
}
}
class Login {
private static handleMap: Record<LoginType, LoginHandler> = {
[LoginType.WeChat]: new WeChatLoginHandler(),
[LoginType.TikTok]: new TikTokLoginHandler(),
[LoginType.TaoBao]: new TaoBaoLoginHandler(),
}
public static handler(type: LoginType) {
Login.handleMap[type].handler()
}
}
Login.handler(LoginType.WeChat) -
里氏替换原则
一个派生类可以在程序的任何一处对其基类进行替换即子类完全继承了父类的一切,对父类进行了功能地扩展(而非收窄)
-
接口分离原则
类的实现方应当只需要实现自己需要的那部分接口比如微信登录支持指纹识别,支付宝支持指纹识别和人脸识别,这个时候微信登录的实现类应该不需要实现人脸识别方法才对。这也就意味着我们提供的抽象类应当按照功能维度拆分成粒度更小的组成
-
依赖倒置原则
这是 实现开闭原则的基础 ,它的核心思想即是 对功能的实现应该依赖于抽象层 ,即不同的逻辑通过实现不同的抽象类。还是登录的例子,我们的登录提供方法应该基于共同的登录抽象类实现(LoginHandler),最终调用方法也基于这个抽象类,而不是在一个高阶登录方法中去依赖多个低阶登录提供方
-
反射 在程序运行时去检查以及修改程序行为 。
断言
-
确保联合类型变量被妥善处理
- 对联合类型的每种类型进行处理,如果对联合类型新增了一种类型Function,同样会被判定为never,通过下面的方法正确处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25declare const strOrNumOrBool: string | number | boolean;
// 不严谨的处理
if (typeof strOrNumOrBool === "string") {
console.log("str!");
} else if (typeof strOrNumOrBool === "number") {
console.log("num!");
} else if (typeof strOrNumOrBool === "boolean") {
console.log("bool!");
} else {
throw new Error(`Unknown input type: ${strOrNumOrBool}`);
}
// 通过never的判断正确处理
if (typeof strOrNumOrBool === "string") {
// 一定是字符串!
strOrNumOrBool.charAt(1);
} else if (typeof strOrNumOrBool === "number") {
strOrNumOrBool.toFixed();
} else if (typeof strOrNumOrBool === "boolean") {
strOrNumOrBool === true;
} else {
const _exhaustiveCheck: never = strOrNumOrBool;
throw new Error(`Unknown input type: ${_exhaustiveCheck}`);
} -
非空断言
1
2
3
4
5const element = document.querySelector("#id"); // const element: Element | null
const element = document.querySelector("#id")!; // const element: Element
const target = [1, 2, 3, 599].find(item => item === 599); // const target: number | undefind
const target = [1, 2, 3, 599].find(item => item === 599)!; // const target: number -
类型断言
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 两种书写断言的方式
interface IStruct {
foo: string;
bar: {
barPropA: string;
barPropB: number;
barMethod: () => void;
baz: {
handler: () => Promise<void>;
};
};
}
const struct = {}
struct as IStruct
const istruct = <IStruct>{}
类型工具
-
基本使用
1
2
3
4
5
6
7
8
9
10
11
12typeMaybeNull<T> = T | null;
functionfun(input: MaybeNull<{ handler: () => {} }>) {
input?.handler()
}
// 函数泛型
typeMaybeArray<T> = T | T[];
function ensureArray<T>(input: MaybeArray<T>): T[] {
returnArray.isArray(input) ? input : [input];
} -
索引类型
- 索引签名类型
1
2
3
4
5
6interface AllStringTypes {
[key: string]: string;
}
type PropType1 = AllStringTypes['linbudu']; // string
type PropType2 = AllStringTypes['599']; // string- 索引类型查询 keyof
它可以将对象中的所有键转换为对应字面量类型,然后再组合成联合类型 - 索引类型访问
1
2
3
4
5interface Foo {
propA: number;
}
typePropAType = Foo['propA']; // type PropAType = number
-
类型守卫
1
2
3
4
5
6
7
8let res = 123;
function assertIsNumber(val: any): asserts val is number {
if (typeof val !== 'number') {
throw new Error('Not a number!');
}
}
assertIsNumber(res);
res.toFixed();
内置工具类型及实现
-
修饰工具类型
- Partial
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15type MyParital<T> = {
[K in keyof T]+?: T[K]
}
interface TestType {
name: string,
age: number,
male: boolean
}
const res_Parital: TestType = {
name: "",
age: 18,
male: true
} - Require
1
2
3
4
5type Required<T> = {
[P in keyof T]-?: T[P];
};
// -?表示如果有问号就去掉问号 - Readonly
1
2
3type Readonly<T> = {
+readonly [K in keyof T]: T[K]
} - MarkPropsAsOptional 部分修饰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17interface Res {
code: 10000 | 10001 | 50000;
status: "success" | "failure";
data: any;
}
type MarkPropsAsOptional<T extends object, U extends keyof T> = Partial<Pick<T, U>> & Omit<T, U>
type Flutten<T> = {
[K in keyof T]: T[K]
}
type res_MarkProps = Flutten<MarkPropsAsOptional<Res, "code" | 'data'>>
// type res_MarkProps = {
// code?: 10000 | 10001 | 50000;
// data?: any;
// status: "success" | "failure";
// }
- Partial
-
结构工具类型
- Record
1
2
3type Record<K extends keyof any, T> = {
[P in K]: T
} - Pick
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 从对象身上提取属性的类型
typePick<T, K extends keyof T> = {
[P in K]: T[P]
}
interface Foo {
name: string;
age: number;
hello: (this: Foo) => string;
}
type PickedFoo = Pick<Foo, "name" | "hello">
// type PickedFoo = {
// name: string;
// hello: (this: Foo) => string;
// } - Omit
1
2
3
4
5
6
7
8
9
10// 剔除T在U中有的类型 --> 差集
typeExclude<T, U> = T extends U ? never : T;
type Tmp1 = Exclude<1, 2>; // 1
type Tmp2 = Exclude<1 | 2, 2>; // 1
type Tmp3 = Exclude<1 | 2 | 3, 2 | 3>; // 1
type Tmp4 = Exclude<1 | 2 | 3, 2 | 4>; // 1 | 3
// 剔除对象身上对应属性类型
typeOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
- Record
-
集合工具类型
1
2
3
4
5
6
7
8
9
10
11
12// 并集
exporttypeConcurrence<A, B> = A | B;
// 交集
exporttypeIntersection<A, B> = A extends B ? A : never;
// 差集
exporttypeDifference<A, B> = A extends B ? never : A;
// 补集
exporttypeComplement<A, B extends A> = Difference<A, B>;
//补集基于差集实现,我们只需要约束集合 B 为集合 A 的子集即可 -
提取工具类型
Typescript 类型的模式匹配是通过 extends 对类型参数做匹配,结果保存到通过 infer 声明的局部类型变量里,如果匹配就能从该局部变量里拿到提取出的类型。1
2
3type p = Promise<'test'>
type getValueType<P> = P extends Promise<infer T> ? T : never
type res_Type = getValueType<p> // type res_Type = "test"-
提取数组第一个 / 最后一个元素的类型
1
2
3
4
5type arr = [number, string, boolean]
type GetFirstType<T extends unknown[]> = T extends [infer F,...unknown[]] ? F : never
type GetLastType<T extends unknown[]> = T extends [...unknown[],infer F] ? F : never
type res_First = GetFirstType<arr> // type res_First = number
type res_Last = GetLastType<arr> // type res_Last = boolean -
提取数组剩余元素类型
1
2type GetRestType<T extends unknown[]> = T extends [...infer F, unknown] ? F : never
type res_Rest = GetRestType<arr> // type res_Rest = [number, string]- 替换字符串 replaceStr
1
2
3
4
5
6
7
8
9
10
11
12// 匹配字符串开头结尾
let str: `#${string}$` = '#str$'
// 替换字符串
type ReplaceStrType<
Str extends string,
From extends string,
To extends string
> = Str extends `${infer PreFix}${From}${infer EndFix}` ? `${PreFix}${To}${EndFix}` : never
type str = ReplaceStrType<'my name is xxx', 'xxx', 'TypeScript'>
// type str = "my name is TypeScript"
- 替换字符串 replaceStr
-
去除字符串两边的空白 Trim
1
2
3
4// 递归删除空格 换行符等
type TrimLeft<Str extends string> = Str extends `${' ' | '\n' | '\t'}${infer Rest}` ? TrimLeft<Rest> : Str
type TrimRight<Str extends string> = Str extends `${infer Rest}${' ' | '\n' | '\t'}` ? TrimRight<Rest> : Str
type res_Trim = TrimRight<TrimLeft<' 123 '>> // type res_Trim = "123"- 函数提取参数,返回值类型
1
2
3
4
5
6
7
8
9// 提取参数
type GetParams<Func extends Function> = Func extends (...args: infer Args) => unknown ? Args : never
type res_Func = GetParams<(name: string, age: number) => boolean>
// type res_Func = [name: string, age: number]
// 提取返回值
type GetReturns<Func extends Function> = Func extends (...args: any[]) => infer Return ? Return : never
type res_Return = GetReturns<(name: string, age: number) => boolean>
// type res_Return = boolean
- 函数提取参数,返回值类型
-
规定this指向 提取this类型
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
31class Foo {
name: string;
constructor() {
this.name = "dong";
}
hello(this: Foo) {
return 'hello, I\'m ' + this.name;
}
}
const foo = new Foo()
const obj = { a: 1 }
foo.hello.apply(obj)
// 类型“{ a: number; }”的参数不能赋给类型“Foo”的参数。
// 类型“{ a: number; }”缺少类型“Foo”中的以下属性: name, hello
// tsconfig.json
{
"compilerOptions": {
"target": "ES2019", // 目标语言的版本
"preserveConstEnums": true, // 保留 const 和 enum 声明
"sourceMap": true, // 生成目标文件的sourceMap文件
"strictBindCallApply": true
}
}
// 提取this类型
type GetThisType<Func extends Function> = Func extends (this: infer T, ...args: any[]) => unknown ? T : unknown
type res_This = GetThisType<typeof foo.hello> // type res_This = Foo -
构造器的类型推断
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
44
45
46
47
48
49// 定义class接口
interface Point {
x: number;
y: number;
}
interface PointConstructor {
new(x: number, y: number)
}
class Point implements Point {
constructor(public x: number, public y: number) {
}
sum(): number {
return this.x + this.y
}
}
function myPoint(
Constructor: PointConstructor,
x: number,
y: number
): Point {
return new Constructor(x, y)
}
const p: Point = myPoint(Point, 1, 2)
console.log(p.sum()); // 3
// 构造器
interface Point {
x: number;
y: number;
}
interface PointConstructor {
new(x: number, y: number): Point
}
// 提取返回类型
type GetInstanceType<Constructor extends new (...args: any[]) => unknown> = Constructor extends new (...args: any[]) => infer Instance ? Instance : any
type res_Instance = GetInstanceType<PointConstructor> // type res_Instance = Point
// 提取参数类型
type GetParamsType<Constructor extends new (...args: any[]) => unknown> = Constructor extends new (...args: infer Rest) => any ? Rest : any
type res_Params = GetParamsType<PointConstructor> // res_Params = [x: number, y: number] -
应用 提取Ref => PropsWithRef
1
2type GetRefProps<Props> = 'ref' extends keyof Props ? Props extends { ref?: infer Value } ? Value : never : never // 'ref' extends keyof Props 将Props遍历成联合类型 再查看里面是否右ref属性
type res_Ref = GetRefProps<{ ref?: undefined, props: string }> // type res_Ref = Point -
翻转键值
1
2
3
4// 反转键名与键值
type Reverse<T extends Record<string, unknown>> = T extends Record<infer K, infer V> ? Record<V & string, K> : never
type res_Reverse = Reverse<{ "key": "value" }>; // { "value": "key" }
-
常用自定义工具类型
- IRes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 给实际的响应数据预留坑位
type IRes<Data> = {
code: number,
error?: string,
data: Data
}
interface IUserRes {
name: string;
homepage: string;
avatar: string;
}
interface IPaginationRes<TItem = unknown> {
data: TItem[];
page: number;
totalCount: number;
hasNextPage: boolean;
}
declare const res_List: IRes<IPaginationRes<IUserRes>>
const { data, code } = res_List
const { name, avatar, homepage } = data.data[0]
鸭子类型
- 结构化类型系统 只会比较结构是一致 如果结构一致 则认为输入的就是这个类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class Dog {
say() { }
}
class Cat {
say() { }
}
function animal(cat: Cat) {
}
animal(new Dog())
// 无报错
class Dog {
say() { }
}
class Cat {
say() { }
eat() { }
}
function animal(cat: Cat) { }
animal(new Dog())
// 类型“Dog”的参数不能赋给类型“Cat”的参数。
// 类型 "Dog" 中缺少属性 "eat",但类型 "Cat" 中需要该属性 - 标称类型系统 通过类型 / 逻辑来模拟标称类型系统
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Tag<T extends string>{
protected __tag__: T
}
type Normial<T, U extends string> = T & Tag<U>
export type CNY = Normial<number, 'CNY'>;
export type USD = Normial<number, 'USD'>;
const CNYCount = 100 as CNY;
const USDCount = 100 as USD;
function addCNY(source: CNY, input: CNY) {
return (source + input) as CNY;
}
addCNY(CNYCount, CNYCount);
// 报错了!
addCNY(CNYCount, USDCount); - 类型层级 Top Type & Bottom Type
函数类型的协变与逆变
在tsconfig.json开启strictFunctionTypes的情况下,TS会开启逆变。其作用在两个函数进行类型比较的情况下,对于参数的比较遵循类型系统的逆向,即: 函数类型的参数类型使用子类型逆变的方式确定是否成立,而返回值类型使用子类型协变的方式确定 。
不开strictFunctionTypes的情况下,TS默认双变。
1 | |
TS工程化
-
类型检查指令
- ts-ignore
- ts-except-error 二者均为忽略下一行的错误 但是ts-except-error更严谨
- ts-check & ts-nocheck
- ……
-
类型声明
通过额外的类型声明文件,在核心代码文件以外去提供对类型的进一步补全 。类型声明文件,即.d.ts结尾的文件,它会自动地被 TS 加载到环境中,实现对应部分代码的类型补全。- 无类型定义的npm包
1
2
3
4
5
6
7// index.ts
import foo from 'pkg';
const res = foo.handler();
// index.d.ts
declare module 'pkg' {
const handler: () => boolean;
} - 非代码文件(png svg md module.scss等)
1
2
3
4
5
6
7
8
9
10// index.ts
import raw from './note.md';
const content = raw.replace('NOTE', `NOTE${new Date().getDay()}`);
// declare.d.ts
declare module '*.md' {
const raw: string;
export default raw;
}
- 无类型定义的npm包
-
命名空间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17export namespace RealCurrency {
export class WeChatPaySDK {}
export class ALiPaySDK {}
export class MeiTuanPaySDK {}
export class CreditCardPaySDK {}
}
export namespace VirtualCurrency {
export class QQCoinPaySDK {}
export class BitCoinPaySDK {}
export class ETHPaySDK {}
}
