类型体操的9种类型运算、4种类型套路总结

Connor 火币交易所 2022-10-11 115 0

今天给大家分享的主题是一起来做类型体操王莽币种类

主要分为 4 个部分进行介绍:

类型体操的背景王莽币种类,通过背景了解为什么要在项目中加入类型体操;

王莽币种类了解类型体操的主要类型、运算逻辑、和类型套路;

类型体操实践王莽币种类,解析 Type 内置高级类型,手写 ParseQueryString 复杂类型;

小结,综上分享,沉淀结论王莽币种类

在背景章节介绍的是什么是类型王莽币种类,什么是类型安全,怎么实现类型安全,什么是类型体操?

以了解类型体操的意义王莽币种类

1. 什么是类型王莽币种类

了解什么是类型之前王莽币种类,先来介绍两个概念:

不同类型变量占据的内存大小不同

不同类型变量占据的内存大小不同

boolean 类型的变量会分配 4 个字节的内存,而 number 类型的变量则会分配 8 个字节的内存,给变量声明了不同的类型就代表了会占据不同的内存空间王莽币种类

不同类型变量可做的操作不同

不同类型变量可做的操作不同

number 类型可以做加减乘除等运算,boolean 就不可以,复合类型中不同类型的对象可用的方法不同,比如 Date 和 RegExp,变量的类型不同代表可以对该变量做的操作就不同王莽币种类

展开全文

综上,可以得到一个简单的结论就是,类型就是编程语言提供对不同内容的抽象定义王莽币种类

2. 什么是类型安全王莽币种类

了解了类型的概念后王莽币种类,那么,什么是类型安全呢?

一个简单的定义就是,类型安全就是只做该类型允许的操作王莽币种类。比如对于 boolean 类型,不允许加减乘除运算,只允许赋值 true、false。

当我们能做到类型安全时,可以大量的减少代码中潜在的问题,大量提高代码质量王莽币种类

3. 怎么实现类型安全王莽币种类

那么王莽币种类,怎么做到类型安全?

这里介绍两种类型检查机制,分别是动态类型检查和静态类型检查王莽币种类

3.1 动态类型检查

Java 就是典型的动态类型检查,它在编译时,没有类型信息,到运行时才检查,导致很多隐藏 bug王莽币种类

3.2 静态类型检查

Type 作为 Java 的超集,采用的是静态类型检查,在编译时就有类型信息,检查类型问题,减少运行时的潜在问题王莽币种类

4. 什么是类型体操

上面介绍了类型的一些定义,都是大家熟悉的一些关于类型的背景介绍,这一章节回归到本次分享的主题概念,类型体操王莽币种类

了解类型体操前,先介绍 3 种类型系统王莽币种类

4.1 简单类型系统

简单类型系统,它只基于声明的类型做检查,比如一个加法函数,可以加整数也可以加小数,但在简单类型系统中,需要声明 2 个函数来做这件事情王莽币种类

intadd(inta, intb){

returna + b

doubleadd( doublea, doubleb) {

returna + b

4.2 泛型类型系统

泛型类型系统,它支持类型参数,通过给参数传参,可以动态定义类型,让类型更加灵活王莽币种类

T add<T>(T a, T b) {

returna + b

add( 1, 2)

add( 1.1, 2.2)

但是在一些需要类型参数逻辑运算的场景就不适用了,比如一个返回对象某个属性值的函数类型王莽币种类

functiongetPropValue<T>(obj: T, key) {

returnobj[key]

4.3 类型编程系统

类型编程系统,它不仅支持类型参数,还能给类型参数做各种逻辑运算,比如上面提到的返回对象某个属性值的函数类型,可以通过 keyof、T[K] 来逻辑运算得到函数类型王莽币种类

functiongetPropValue<

Textendsobject,

KeyextendskeyofT

>(obj: T, key: Key): T[Key] {

returnobj[key]

总结上述,类型体操就是类型编程,对类型参数做各种逻辑运算,以产生新的类型王莽币种类

之所以称之为体操,是因为它的复杂度,右侧是一个解析参数的函数类型,里面用到了很多复杂的逻辑运算,等先介绍了类型编程的运算方法后,再来解析这个类型的实现王莽币种类

二、王莽币种类了解类型体操—

熟悉完类型体操的概念后,再来继续了解类型体操有哪些类型,支持哪些运算逻辑,有哪些运算套路王莽币种类

1. 有哪些类型

类型体操的主要类型列举在图中王莽币种类。Type 复用了 JS 的基础类型和复合类型,并新增元组(Tuple)、接口(Interface)、枚举(Enum)等类型,这些类型在日常开发过程中类型声明应该都很常用,不做赘述。

// 元组(Tuple)就是元素个数和类型固定的数组类型

type Tuple = [number, string];

// 接口(Interface)可以描述函数、对象、构造器的结构:

interface IPerson {

name: string;

age: number;

classPersonimplementsIPerson{

name: string;

age: number;

constobj: IPerson = {

name: 'aa',

age: 18

// 枚举(Enum)是一系列值的复合:

enum Transpiler {

Babel = 'babel',

Postcss = 'postcss',

Terser = 'terser',

Prettier = 'prettier',

TypeCompiler = 'tsc'

consttranspiler = Transpiler.TypeCompiler;

2. 运算逻辑

重点介绍的是类型编程支持的运算逻辑王莽币种类

Type 支持条件、推导、联合、交叉、对联合类型做映射等 9 种运算逻辑王莽币种类

条件:T extends U ? X : Y

条件:T extends U ? X : Y

条件判断和 js 逻辑相同,都是如果满足条件就返回 a 否则返回 b王莽币种类

// 条件:extends ? :

// 如果 T 是 2 的子类型,那么类型是 true,否则类型是 false王莽币种类

type isTwo<T> = T extends 2? true: false;

// false

type res = isTwo< 1>;

约束:extends

约束:extends

通过约束语法 extends 限制类型王莽币种类

// 通过 T extends Length 约束了 T 的类型,必须是包含 length 属性,且 length 的类型必须是 number王莽币种类

interface Length {

length: number

functionfn1<TextendsLength>(arg: T): number{

returnarg.length

推导:infer

推导:infer

推导则是类似 js 的正则匹配,都满足公式条件时,可以提取公式中的变量,直接返回或者再次加工都可以王莽币种类

// 推导:infer

// 提取元组类型的第一个元素:

// extends 约束类型参数只能是数组类型,因为不知道数组元素的具体类型,所以用 unknown王莽币种类

// extends 判断类型参数 T 是不是 [infer F, ...infer R] 的子类型王莽币种类,如果是就返回 F 变量,如果不是就不返回

type First<T extends unknown[]> = T extends [infer F, ...infer R] ? F : never;

// 1

type res2 = First<[ 1, 2, 3]>;

联合:|

联合:|

联合代表可以是几个类型之一王莽币种类

type Union = 1| 2| 3

交叉:&

交叉:&

交叉代表对类型做合并王莽币种类

type ObjType = { a: number } & { c: boolean }

索引查询:keyof T

索引查询:keyof T

keyof 用于获取某种类型的所有键,其返回值是联合类型王莽币种类

// const a: 'name' | 'age' = 'name'

consta: keyof {

name: string,

age: number

} = 'name'

索引访问:T[K]

索引访问:T[K]

T[K] 用于访问索引,得到索引对应的值的联合类型王莽币种类

interface I3 {

name: string,

age: number

type T6 = I3[keyof I3] // string | number

索引遍历: in

索引遍历: in

in 用于遍历联合类型王莽币种类

constobj = {

name: 'tj',

age: 11

type T5 = {

[P inkeyof typeofobj]: any

name: any,

age: any

索引重映射: as

索引重映射: as

as 用于修改映射类型的 key王莽币种类

// 通过索引查询 keyof王莽币种类,索引访问 t[k],索引遍历 in,索引重映射 as,返回全新的 key、value 构成的新的映射类型

type MapType<T> = {

Key inkeyof T

as`${Key & string}${Key & string}${Key & string}`

]: [T[Key], T[Key], T[Key]]

// aaa: [1, 1, 1];

// bbb: [2, 2, 2];

type res3 = MapType<{ a: 1, b: 2}>

3. 运算套路

根据上面介绍的 9 种运算逻辑,我总结了 4 个类型套路王莽币种类

模式匹配做提取;

重新构造做变换;

递归复用做循环;

数组长度做计数王莽币种类

模式匹配做提取;

重新构造做变换;

递归复用做循环;

数组长度做计数王莽币种类

第一个类型套路是模式匹配做提取王莽币种类

模式匹配做提取的意思是通过类型 extends 一个模式类型,把需要提取的部分放到通过 infer 声明的局部变量里王莽币种类

举个例子,用模式匹配提取函数参数类型王莽币种类

type GetParameters<Func extends Function> =

Func extends (...args: infer Args) => unknown ? Args : never;

type ParametersResult = GetParameters< (name: string, age: number) =>string>

首先用 extends 限制类型参数必须是 Function 类型王莽币种类

然后用 extends 为 参数类型匹配公式,当满足公式时,提取公式中的变量 Args王莽币种类

实现函数参数类型的提取王莽币种类

3.2 重新构造做变换

第二个类型套路是重新构造做变换王莽币种类

重新构造做变换的意思是想要变化就需要重新构造新的类型,并且可以在构造新类型的过程中对原类型做一些过滤和变换王莽币种类

比如实现一个字符串类型的重新构造王莽币种类

type CapitalizeStr<Str extends string> =

Str extends `${infer First}${infer Rest}`

? `${Uppercase<First>}${Rest}`: Str;

type CapitalizeResult = CapitalizeStr< 'tang'>

首先限制参数类型必须是字符串类型王莽币种类

然后用 extends 为参数类型匹配公式,提取公式中的变量 First Rest,并通过 Uppercase 封装王莽币种类

实现了首字母大写的字符串字面量类型王莽币种类

3.3 递归复用做循环

第三个类型套路是递归复用做循环王莽币种类

Type 本身不支持循环,但是可以通过递归完成不确定数量的类型编程,达到循环的效果王莽币种类

比如通过递归实现数组类型反转王莽币种类

type ReverseArr<Arr extends unknown[]> =

Arr extends [infer First, ...infer Rest]

? [...ReverseArr<Rest>, First]

: Arr;

type ReverseArrResult = ReverseArr<[ 1, 2, 3, 4, 5]>

首先限制参数必须是数组类型王莽币种类

然后用 extends 匹配公式,如果满足条件,则调用自身,否则直接返回王莽币种类

实现了一个数组反转类型王莽币种类

3.4 数组长度做计数

第四个类型套路是数组长度做计数王莽币种类

类型编程本身是不支持做加减乘除运算的,但是可以通过递归构造指定长度的数组,然后取数组长度的方式来完成数值的加减乘除王莽币种类

比如通过数组长度实现类型编程的加法运算王莽币种类

type BuildArray<

Length extends number,

Ele = unknown,

Arr extends unknown[] = []

> = Arr[ 'length'] extends Length

? Arr

: BuildArray<Length, Ele, [...Arr, Ele]>;

type Add<Num1 extends number, Num2 extends number> =

[...BuildArray<Num1>, ...BuildArray<Num2>][ 'length'];

type AddResult = Add< 32, 25>

首先通过递归创建一个可以生成任意长度的数组类型

然后创建一个加法类型,通过数组的长度来实现加法运算王莽币种类

三、类型体操实践—

分享的第三部分是类型体操实践王莽币种类

前面分享了类型体操的概念及常用的运算逻辑王莽币种类

下面我们就用这些运算逻辑来解析 Type 内置的高级类型王莽币种类

1. 解析 Type 内置高级类型

partial 把索引变为可选

partial 把索引变为可选

通过 in 操作符遍历索引,为所有索引添加 ?前缀实现把索引变为可选的新的映射类型王莽币种类

type TPartial<T> = {

[P inkeyof T]?: T[P];

type PartialRes = TPartial<{ name: 'aa', age: 18}>

Required 把索引变为必选

Required 把索引变为必选

通过 in 操作符遍历索引,为所有索引删除 ?前缀实现把索引变为必选的新的映射类型王莽币种类

type TRequired<T> = {

[P inkeyof T]-?: T[P]

type RequiredRes = TRequired<{ name?: 'aa', age?: 18}>

Readonly 把索引变为只读

Readonly 把索引变为只读

通过 in 操作符遍历索引,为所有索引添加 readonly 前缀实现把索引变为只读的新的映射类型王莽币种类

type TReadonly<T> = {

readonly [P inkeyof T]: T[P]

type ReadonlyRes = TReadonly<{ name?: 'aa', age?: 18}>

Pick 保留过滤索引

Pick 保留过滤索引

首先限制第二个参数必须是对象的 key 值,然后通过 in 操作符遍历第二个参数,生成新的映射类型实现王莽币种类

type TPick<T, K extends keyof T> = {

[P inK]: T[P]

type PickRes = TPick<{ name?: 'aa', age?: 18}, 'name'>

Record 创建映射类型

Record 创建映射类型

通过 in 操作符遍历联合类型 K,创建新的映射类型王莽币种类

type TRecord<K extends keyof any, T> = {

[P inK]: T

type RecordRes = TRecord< 'aa'| 'bb', string>

Exclude 删除联合类型的一部分

Exclude 删除联合类型的一部分

通过 extends 操作符,判断参数 1 能否赋值给参数 2,如果可以则返回 never,以此删除联合类型的一部分王莽币种类

type TExclude<T, U> = T extends U ? never : T

type ExcludeRes = TExclude< 'aa'| 'bb', 'aa'>

Extract 保留联合类型的一部分

Extract 保留联合类型的一部分

和 Exclude 逻辑相反,判断参数 1 能否赋值给参数 2,如果不可以则返回 never,以此保留联合类型的一部分王莽币种类

type TExtract<T, U> = T extends U ? T : never

type ExtractRes = TExtract< 'aa'| 'bb', 'aa'>

Omit 删除过滤索引

Omit 删除过滤索引

通过高级类型 Pick、Exclude 组合,删除过滤索引王莽币种类

type TOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

type OmitRes = TOmit<{ name: 'aa', age: 18}, 'name'>

Awaited 用于获取 Promise 的 valueType

Awaited 用于获取 Promise 的 valueType

通过递归来获取未知层级的 Promise 的 value 类型王莽币种类

type TAwaited<T> =

T extends null| undefined

? T

: T extends object & { then(onfulfilled: infer F): any }

? F extends ( (value: infer V, ...args: any) =>any)

? Awaited<V>

: never

: T;

type AwaitedRes = TAwaited< Promise< Promise< Promise<string>>>>

还有非常多高级类型,实现思路和上面介绍的类型套路大多一致,这里不一一赘述王莽币种类

2. 解析 ParseQueryString 复杂类型

重点解析的是在背景章节介绍类型体操复杂度,举例说明的解析字符串参数的函数类型王莽币种类

如图示 demo 所示,这个函数是用于将指定字符串格式解析为对象格式王莽币种类

functionparseQueryString1(queryStr) {

if(!queryStr || !queryStr.length) {

return{}

constqueryObj = {}

constitems = queryStr.split( '&')

items.forEach( (item) =>{

const[key, value] = item.split( '=')

if(queryObj[key]) {

if( Array.isArray(queryObj[key])) {

queryObj[key].push(value)

} else{

queryObj[key] = [queryObj[key], value]

} else{

queryObj[key] = value

returnqueryObj

比如获取字符串 a=1&b=2 中 a 的值王莽币种类

常用的类型声明方式如下图所示:

functionparseQueryString1(queryStr: string): Record<string, any> {

if(!queryStr || !queryStr.length) {

return{}

constqueryObj = {}

constitems = queryStr.split( '&')

items.forEach( (item) =>{

const[key, value] = item.split( '=')

if(queryObj[key]) {

if( Array.isArray(queryObj[key])) {

queryObj[key].push(value)

} else{

queryObj[key] = [queryObj[key], value]

} else{

queryObj[key] = value

returnqueryObj

参数类型为 string王莽币种类,返回类型为 Record<string, any>,这时看到,res1.a 类型为 any,那么有没有办法,准确的知道 a 的类型是字面量类型 1 呢?

下面就通过类型体操的方式,来重写解析字符串参数的函数类型王莽币种类

首先限制参数类型是 string 类型,然后为参数匹配公式 a&b,如果满足公式,将 a 解析为 key value 的映射类型,将 b 递归 ParseQueryString 类型,继续解析,直到不再满足 a&b 公式王莽币种类

最后,就可以得到一个精准的函数返回类型,res.a = 1王莽币种类

type ParseParam<Param extends string> =

Param extends `${infer Key}=${infer Value}`

[K inKey]: Value

} : Record<string, any>;

type MergeParams<

OneParam extends Record<string, any>,

OtherParam extends Record<string, any>

> = {

readonly [Key inkeyof OneParam | keyof OtherParam]:

Key extends keyof OneParam

? OneParam[Key]

: Key extends keyof OtherParam

? OtherParam[Key]

: never

type ParseQueryString<Str extends string> =

Str extends `${infer Param}&${infer Rest}`

? MergeParams<ParseParam<Param>, ParseQueryString<Rest>>

: ParseParam<Str>;

functionparseQueryString<Strextendsstring>(queryStr: Str): ParseQueryString<Str> {

if(!queryStr || !queryStr.length) {

return{} asany;

constqueryObj = {} asany;

constitems = queryStr.split( '&');

items.forEach( item=>{

const[key, value] = item.split( '=');

if(queryObj[key]) {

if( Array.isArray(queryObj[key])) {

queryObj[key].push(value);

} else{

queryObj[key] = [queryObj[key], value]

} else{

queryObj[key] = value;

returnqueryObj asany;

constres = parseQueryString( 'a=1&b=2&c=3');

console.log(res.a) // type 1

四、小结—

综上分享,从 3 个方面介绍了类型体操王莽币种类

第一点是类型体操背景王莽币种类,了解了什么是类型,什么是类型安全,怎么实现类型安全;

第二点是熟悉类型体操的主要类型、支持的逻辑运算王莽币种类,并总结了 4 个类型套路;

第三点是类型体操实践,解析了 Type 内置高级类型的实现,并手写了一些复杂函数类型王莽币种类

第一点是类型体操背景王莽币种类,了解了什么是类型,什么是类型安全,怎么实现类型安全;

第二点是熟悉类型体操的主要类型、支持的逻辑运算王莽币种类,并总结了 4 个类型套路;

第三点是类型体操实践,解析了 Type 内置高级类型的实现,并手写了一些复杂函数类型王莽币种类

从中我们了解到需要动态生成类型的场景,必然是要用类型编程做一些运算,即使有的场景下可以不用类型编程,但是使用类型编程能够有更精准的类型提示和检查,减少代码中潜在的问题王莽币种类

参考资料+源码—

这里列举了本次分享的参考资料及示例源码王莽币种类,欢迎大家扩展阅读:

[1]

参考资料《Type 类型体操通关秘籍》:

[2]

示例源码:

END

开源社区终点究竟在哪王莽币种类

这里有最新开源资讯、软件更新、技术干货等内容

点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦~

评论