前言
之前看了很多typescript大神的文章,不知道大家有没有跟我一样的感觉,总是感觉他们总结的吧,总是缺点啥,但是又不知道是什么,后来我在github上找到一个叫type-challenges,我简单看了一下简单题和中等题,发现很多不就是当初面试的面试题吗。。。
惊喜之余,慢慢总结出了一些规律,发现自己一些觉得缺的部分很简单,就是如何把类型和平时的编程语法做转换。
举一个例子,泛型最基本的作用是什么,不就是像函数一样传入参数吗,
type demo<T> = T;
类比:
function demo(T) {
return T
}
好了,是不是我们把遇到的ts训练题通过这么一转化,然后总结一些从ts到js转化的基本套路,是不是typescript就变得简单一些了呢?
联合类型是对象类型的爸爸
对象类型我们指的是:
type Record<K extends keyof any, T> = {
[P in K]: T;
};
// 比如:
const person:Record<string, any> = {
name: 'jack',
age: 18
}
然后第一个类比来了,首先这里我把联合类型看成了数组。
type U = string | number;
// 类比
const U = [string, number]
既然是数组,那么我们就可以遍历它。
那么如何遍历数组呢,目前总结了两个常用的方式
- 跟对象类型有关的话,通过in关键字
- 跟条件判断有关的话,通过extends关键字
首先我们看跟对象类型有关的用法,用一个常见的Pick类型来做解释。这个类型案例如下:
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = Pick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
Pick类型实现如下:
type Pick<T extends keyof Record<string, any>, K extends keyof T> = {
[key in K] : T[key]
};
K extends keyof T,是不是就得到了T的所有key值的联合类型,比如
type Todo = {
title: string
description: string
completed: boolean
}
keyof Todo // 'title' | 'description' | 'completed'
key in K 就可以看做遍历联合类型,类比js代码
const result = {}
// K是联合类型所以类比为数组
// key指的是K[i]意思是数组里的每一项
for(let i = 0; i < K.length; i++){
result[K[i]] = T[K[i]];
}
所以Pick类型转化为js:
function Pick(T, K: keyof T) {
let result = {};
for(let i = 0; i < K.length; i++){
result[K[i]] = T[K[i]];
}
return result;
}
结论一
我们要操作对象类型,那么必须借助遍历联合类型,也就是你工作中遇到写对象类型的ts类型,必须想到联合类型。
extends映射带有条件判断的for循环
好了,接着看extends上的for循环语法:
Exclude是TS内部的一个工具类型,使用效果如下:
案例:
type Result = Exclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
实现如下:
type Exclude<T, K extends T> = T extends K ? never : T
联合类型 extends 的时候,联合类型会被拆散,单独判断,啥意思呢,请看下面。
Exclude<‘a’|’b’|’c’, ‘c’|’b’> 可以转化为如下
'a' | 'b' | 'c' extends 'c' | 'b' ? never : 'a' | 'b' | 'c' =>
('a' extends 'c' | 'b' ? never : 'a') |
('b' extends 'c' | 'b' ? never : 'b') |
('c' extends 'c' | 'b' ? never : 'c') =>
'a' | never | never =>
'a'
‘a’ | never | never这一段类型为啥变为’a’,稍微解释一下,不过任何类型联合上 never 类型,还是原来的类型。
转化为js
function Exclude(T, K: T){
let result = [];
for(let i = 0; i < T.length; i++){
if(T.includes[K[i]]){
result.push(K[i])
} else {
result.push(never)
}
}
return result;
}
结论二
- extends 语法实际上就是for循环里带了一个if判断(或者说三元运算符的判断)
- 联合类型配合extends,可以把联合类型打散,分别进入for循环,然后还被extends语法的if判断返回不同的值
结论运用
好了,接着我们利用上面两种机制,做一个题,实现Omit,Omit类型是个啥效果呢,请看案例:
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = Omit<Todo, 'description' | 'title'>
const todo: TodoPreview = {
completed: false,
}
Omit: 删除,删除给出的key,我们说了联合类型就是数组,所以在我们这里可以认为删除’description’ | ‘title’就是删除[‘description’, ‘title’]
我们分析一下啊,既然要删除对象类型上的某些属性,是不是我们要遍历一下这个对象。
好了遍历对象,必须要借助in来遍历联合类型了。
然后,我们要删除某些key,问题来了,兄弟们,TS里面没有这种语法,比如key为never就删除整个值,如果可以的话,我们可以这么写
type Omit<T, K extends keyof T> = { [key in T extends K ? never : key] : T[key] };
结论三
所以这里记个笔记,就是对象类型不支持在key里面直接删除key,所以我们只能换个思路,不能删除意味着只能遍历,只能看不能删(值是可以删的,比如[key in T] : key extends K ? never : key),never可以删值。
那咋办,我们换个思路,我们如果直接遍历的就是我们想要的结果的联合类型,然后在for循环这个联合类型,是不是也可以。
好了,第一步,先把我们想遍历的数组也就是联合类型求出,
type K<keyof M, P> = M extends P ? nerver : M
上面这段代码不就是Exclude类型吗,所以我们改一下:
Exclude<keyof M, P>
这里很重要的知识点,对象不能够删除属性,也就是key,但是联合类型可以删除,可以增加,所以对于一个对象增加和删除属性,你就要想到怎么用联合类型去转换。
接下里看Omit的实现
type Pick<T, K extends keyof T> = { [key in K] : T[key] };
type Exclude<T, K extends T> = T extends K ? never : T
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
其实Omit转换为js就是
function Omit(T, K:T) {
Pick(T, Exclude(keyof T, K: keyof T))
}
结论四
对象类型的增加属性和删除属性都是借助联合类型去实现的
实战中等难度的题
我们再看一个案例:
type foo = {
name: string;
age: string;
}
type coo = {
age: number;
sex: string
}
type Result = Merge<foo,coo>; // expected to be {name: string, age: number, sex: string}
一个merge运算符,它实际上就是把coo里的属性拿出来,然后再把foo里独有的属性拿出来做了一个合并。
我们来分析一下这个题,首先属性的merge,根据我上面的经验,一般对象的类型好像没啥特殊的功能,很多都要靠着联合类型的遍历和联合类型的类型判断去拓展对象的,好吧,这里我就往那个方向想了。
并且,我们新手初期,都要往这个方向想,联合类型是对象类型拓展的爸爸。
所以merge对象,我就可以看做merge联合类型。
再举个例子,还是用上面的案例,
type A = 'name' | 'age';
type B = 'age' | 'sex';
// 联合,求出了交集
type C = A & B // 'age'
type D = A | B // ‘name’ | 'age' | 'sex'
兄弟们,有没有啥想法了,联合类型可以轻易求出交集和并集,那么加上之前in可以遍历联合类型,这就意味着,我们实现merge的思路变为:
type foo = {
name: string;
age: string;
}
type coo = {
age: number;
sex: string
}
type Result = foo独有的属性对象 & foo和coo交集(属性的值以coo为准) & coo独有的属性对象
所以我们第一步,求出foo独有的属性对象,独有是啥意思,就是 某类型<typeof foo, typeof coo> 能够得到
{
name: string
}
又因为对象的筛选全靠联合类型,所以我们需要通过联合类型的筛选功能。
这个一下子让我想到了extends语法
type A = keyof foo; // 'name' | 'age'
type B = keyof coo // 'age' | 'sex'
type C = A extends B ? A : never;
是不是上面的C就获取到了foo的独有属性了。
这不是Exclude类型吗,用起来
type C = Exclude<keyof foo, keyof coo>
所以foo独有的属性对象为
type PreUnique = {
[key in Exclude<keyof foo, keyof coo>]: foo<key>
};
同理coo独有属性对象为
type EndUnique = {
[key in Exclude<keyof coo, keyof coo>]: coo<key>
};
上面已经知道交集的办法了,所以我们写下来:
type InterSection = {
[p in keyof foo & keyof coo]: coo[p];
}
获取两个对象属性交集的联合类型:
type InterSectionKey = keyof F & keyof S;
获取两个对象的交集, 并且类型以第二个为准
获取两个对象中前一个对象独有的属性的联合类型
type PreUniqueKey<F, S> = keyof Omit<F, keyof S>;
获取两个对象中前一个对象独有的属性的对象
type EndUnique<F, S> = {
[key in keyof Omit<F, keyof S>]: F<key>
};
所以这个merge函数就出来了
type Merge<F, S> = {
[p in Exclude<keyof F, keyof S>]:F[p];
} & {
[p in keyof F & keyof S]:S[p];
} & {
[p in Exclude<keyof S, keyof F>]:S[p];
}
是不是很简单,我们继续总结一下:
- 两个对象类型的交集,差集,在typescript无法依靠对象本身去操作啥,只有靠联合类型的交集和差集,来帮助对象类型实现交集和差集
立竿见影的效果你马上看到(中等题)
diff函数
求两个对象的差集
type Foo = {
a: string;
b: number;
}
type Bar = {
a: string;
c: boolean
}
type Result1 = Diff<Foo,Bar> // { b: number, c: boolean }
type Result2 = Diff<Bar,Foo> // { b: number, c: boolean }
这道题的链接:https://github.com/type-challenges/type-challenges/blob/main/questions/00645-medium-diff/README.zh-CN.md
有兴趣的同学点击接受挑战就可以自己尝试解决一下了。
大家按照我之前的思路来做一下这个题呗,是中等难度。
思路:
对象类型的处理其实就是联合类型的处理,这里求差集,我们转为求联合类型中Bar独有的部分,这个之前是不是已经实现了,你自己不妨试试。
继续立竿见影
你不妨试着挑战这些题,应该很快就能做出来!
简单题 实现 Readonly
https://github.com/type-challenges/type-challenges/blob/main/questions/00007-easy-readonly/README.zh-CN.md
中等题 实现 Readonly 2
https://github.com/type-challenges/type-challenges/blob/main/questions/00008-medium-readonly-2/README.zh-CN.md
好了本文结束,后面接着做题继续总结ts转js的规律
暂无评论内容