Open wenzi0github opened 2 years ago
TS 类型本身就是一个很复杂的、独立的语言,不仅仅是 JS 的增强和类型注释。
Pick, Omit一般是操作对象属性;而 Extract, Exclude 一般是操作用type声明的联合类型。也不知道我理解的对不对?
如何限制子组件的类型,如中
export const Case: React.FC<CaseProps> = props => {
return <>{props.children}</>;
};
export const When = ({ children }: { children: ReturnType<typeof Case> | ReturnType<typeof Case>[] }) => {};
React中的children如何用 function 来实现,然后传递参数?
interface TabItemProps {
label: string;
children: React.ReactNode | ((props: { loading: boolean; result: any }) => React.ReactNode);
}
export const TabItem = ({ label, children, request }: TabItemProps) => {
const [loading, setLoading] = useState(true);
const [result, setResult] = useState<any>(null);
useEffect(() => {
if (typeof request === 'function') {
setLoading(true);
Promise.resolve(request()).then((res: any) => {
setLoading(false);
setResult(res);
});
}
}, [request]);
return (
<View className="tab-item" data-label={label}>
{typeof children === 'function' ? children({ loading, result }) : <>{children}</>}
</View>
);
};
Typescript 类型编程,从入门到念头通达
超杰_ 高级前端进阶 2022-08-29 09:00 发表于安徽
前言
探索经历
我不知道我不知道
我曾经以为 Tyepscript 只是在 Javascript 基础上加一些类型注释,是 JavaScript 的增强版而已,属于有手就会。
我知道我不知道
直到我用到了 Prisma 这个 NodeJS ORM 工具,其生成的类型定义,可以根据你的参数,完美应对关联查询、部分查询等各种场景,完全吊打其他 NodeJS ORM。出于好奇,我看了以下源码:图片
这还是我认识的 Typescript 吗???
我只知道我知道的
后面浏览 Github Trending[1] 时发现了 type-challenges[2] 这个项目(俗称类型体操)。
图片 微信截图_20220806230248.png 然后就开始了我的 TS 编程挑战,但在刷的过程中,我发现已经做过的,似乎感觉会了,但到下一题还是写不出来,甚至过几天重刷还会忘记怎么写。
图片 154390DB.png
我不知道我知道的
然后我停止刷题,开始思考他们到底有什么规律,我似乎顿悟了:
TS 类型本身就是一个很复杂的、独立的语言,不仅仅是 JS 的增强和类型注释。
然后我就尝试着从语言的层面理解 TS 类型,瞬间豁然开朗,仿佛进入了桃花源,那些类型挑战不过是这些基础知识的应用而已,再也不用死记硬背。
图片 0070XTeOgy1gtv57eag5fj606l05y0sq02.jpg
学习建议
要有一定的 TS 基础,起码有一两个项目用过 TS; 先去刷 type-challenges[3],接受毒打,然后自己总结,然后再看本文,本文每节知识后面都对应着能解决挑战,在拿着知识的冲锋枪去挑战它; 学习本文时,一定要打开宇宙第一的 VSCode,创建一个 TS 文件,将示例代码拷贝进去看看效果 如果还没做完以上准备,可以先收藏一下本文,毕竟收藏了就是学会了。
学习目标
大纲
前面我们说了 TS 类型自己就是一门复杂、独立的语言,那么从我们语言的角度设计这门指南:
手册指南
类型变量定义
类型变量的方式有三种,分别为 type、interface、enum,他们都相当于 JS 中的 const,一旦定义就不可改变,三者的区别是:
例如:
类型数据和值
在官方文档中第一篇就介绍了 TS 的基础类型包括了:
但是这里要问一下大家,除了这些难道就没有其他的值了吗???
大家请看下面的例子:
从上面的例子大家也可以得出一个结论:
类型的父子关系
类型是有父子关系的,子类型的值可以赋值给父类型,但是父类型的值是不能够赋值给子类型的。例如:
这一特性对于后面要讲的泛型和条件判断有着至关重要的作用,我们先简单看一下类型中的条件语句:
了解了父子类型的基本概念后,我们还需要掌握在 类型数据和值 中提到的各种 TS 类型之间的父子关系,为后面学习泛型、条件、递归等打下基础。
1、具体值是基础类型的子类型
2、联合类型中的部分是整体的子类型
3、never 类型是所有类型的子类型
4、对象判断子类型,需要逐个属性比较
在进行比较时,首先 MyButtonProps 的 size 和 type 都是 ButtonProps 中对应属性的子类型,虽然 MyButtonProps 比 ButtonProps 多了个 size ,但其不参与比较。
5、undefined 在 tsconfig strictNullChecks 为 true 的情况下是 void 和 any 类型子类型,为 false 的情况下则除 never 的子类型
6、undefined 在 tsconfig strictNullChecks 为 true 的情况下是 any 类型子类型,为 false 的情况下则除 never 的子类型
父子关系与联合类型
当子类型与父类型组成联合类型时,实际效果等于父类型。例如:
变量取属性
我们知道在 JS 中对象是可以通过 . 操作符,而在 TS 类型中,也能进行相似的操作。例如:
⚠️ 注意,基础类型是可以取到原型的定义的,所以并非无属性。
获取类型所有属性 key
想要知道对象有哪些属性,可以使用 keyof 关键词。例如:
⚠️ 箭头函数类型和空对象没有 key。例如:
条件语句
TS 类型编程中并没有其他语言中的 if/else 语法,而是使用了三元运算符 X extends Y ? expr1 : expr2。
类型中的函数(泛型)
泛型基础和定义
TS 泛型就像 JS 的函数一样,可以根据输出的类型,决定返回的类型。我们看一个简单的例子:
在 foo 函数作用是,你给他什么值,它就返回什么值;Foo 泛型则是你给他什么类型,它返回什么类型。
除了上面的定义方式,还可以使用 interface 定义。例如:
泛型约束
我们写 JS 函数的时候,为了代码的健壮性,通常会对输入参数进行校验,泛型中通过 extends 关键字也实现了类似的功能。例如:
泛型参数默认值
我们知道 ES6 后函数支持参数默认值,同样的,在 TS 类型编程中,泛型也有默认值的能力。例如:
泛型与条件判断
上面的示例中,我们只列举了简单的场景,当配合条件语句的时候,泛型的灵活性就更大了。例如:
学完本小节,你可以试着挑战:
泛型与条件与类型推断变量
如果以上介绍的内容对你来说虽然既陌生又熟悉,接下来我们引入的一个关键词你可能从未听过,即 infer。
infer 可以在
X extends Y ? expr1 : expr2
的 Y 中使用类型变量,并且这个类型变量,可以在后续的 expr1 中使用。例如我们需要得到函数的返回值的类型可以如下操作:
其中 R 既类型推断变量。
extends 是一个大忙人:
App extends Component
;type ToUpper<S extends string> = xxx
;type ReturnType<T> = T extends () => infer R ? R : never'
;学完本小节,你能完成的挑战:
内置泛型工具
Typescript 给我们内置了一些极其有用的泛型工具,我们本小节挑一些简单说明:
对象类型的操作
属性修饰
对象的属性是可以有修饰符的,目前有两种修饰符,分别是 readonly 关键字对应的可选属性 和 ?: 对应的可选属性。例如:
属性修饰与父子关系
父子类型主要讨论属性的存在与否,所以:
对象类型的遍历
我们知道在 JS 中可以使用 for/in 遍历对象的属性,在 TS 类型编程中也有类似的方式,不过更加简洁。
例如我们把所有的属性都加上 readonly 修饰符:
上述示例中有以下几点:
学完本小结,你可以试着挑战:
00004-easy-pick[8] 00007-easy-readonly[9] 00003-medium-omit[10] 00008-medium-readonly-2[11] 00009-medium-deep-readonly[12]
元组类型的操作
只读修饰符 & 父子关系
在元组中也可以像对象那样在元组前面加上 readonly 代表元组的每一项都是只读的。例如:
如果两个类型元素完全相同 的前提下,只读的和非只读是有父子关系的:非只读是只读的子类型。具体没查到原因,不过可以理解自我催眠为 readonly 表示的更信息更多。
学完本节你可以搞定:
00018-easy-tuple-length
元组的解构
元组的解构和 JS 数组的解构十分相似。假设我们需要将两个元组类型合并成一个,我们可以如下操作:
readonly 的元组转为非 readonly,我们可以使用解构完成。例如:
因为 readonly 是针对整个元组而言的,所以通过解构,我们就将每个元素取出来了,重新赋值给另一个类型变量就解决这个问题了。
学完本节,你可以完成如下挑战:
00014-easy-first[13] 00533-easy-concat[14] 03057-easy-push[15] 03060-easy-unshift[16] 00015-medium-last[17] 00016-medium-pop[18] 00191-medium-append-argument[19]
元组的遍历
有些情况下,我们需要对每个元素进行判断和处理,此时就需要使用元组的遍历,元组的遍历有两种方式:
递归方式遍历
我们以 多维元组拍平为一维元组 为例,来看看怎么用递归的思想实现。
相信能看懂 JS 逻辑的人都能看懂 TS 逻辑,两者几乎一致:
最终返回的类型就是通过递归拍平的元组类型了。
学完本小结,你可以解决:
00898-easy-includes[20] 00459-medium-flatten[21] 00949-medium-anyof[22]
对象类型遍历方式
我们再用示例说明如何使用对象类型遍历方式处理元组。
总结:
学完本节,你可以挑战:
00020-medium-promise-all[23] 00527-medium-append-to-object[24] 00599-medium-merge[25]
元组与索引与联合类型
元组其实就是个数有限、类型固定的数组类型。所以前面也讲过,其可以使用数字作为下标来访问的,例如:
如果这个索引是 number 会发生什么呢?
因为 number 代表了可能是 0 也可能是 1 或者 2,所以这些可能性组成的集合就是联合类型。
学完本节你应该可以挑战:
00011-easy-tuple-to-object[26] 00010-medium-tuple-to-union[27]
字符串操作
字符串的相关操作主要体现在两方面:
字符串类型推导和解构
字符串类型推导和解构,是将一个完整字符串分解为几个部分,然后对各个部分我们可以进行各种处理。
这里需要注意的是,在拆分的时候需要注意是否含有字符串字面量作为分割符,有和没有的情况,分割后的变量含义并不相同。
推导类型中有字符串字面量的情况
我们需要实现一个将字符串类型中 _ 去除的功能,其可以为:
我们从上面例子可以得到结论:
当推断类型中有字符串字面量作为边界时,如上例的 _,其解构的左边 LeftWords 是左侧所有字符串的代表,右边 RightWords 是右侧所有字符串的代表,并且可以代表空字符串。
学完本节,你可以挑战:
00106-medium-trimleft[28] 00108-medium-trim[29] 00116-medium-replace[30] 00119-medium-replaceall[31] 00529-medium-absolute[32]
推导类型中无字符串字面量的情况
假设我们要实现 TS 类型的首字母大写的效果,我们可以这样写:
我们从上面例子可以得到结论:
当推断类型中没有字符串字面量作为边界时,第一个变量作为第一个字符,第二个变量代表剩下的字符,可以为空字符串。当然如果有三个变量,${A}${B}${C},则第一个变量 A 代表第一个字符,B 代表第二个字符串,C 代表剩下的字符。
学完本节,你可以挑战:
00110-medium-capitalize[33] 00531-medium-string-to-union[34]
字符串字面量类型的遍历
字符串字面量类型的遍历,核心是使用递归思想以及上面提到的字符串的解构,这在后面很多转换中都很重要。
这里我们使用字符串类型转元组类型小试牛刀:
我们这里分析一下:
00298-medium-length-of-string[35] 00612-medium-kebabcase[36] 00645-medium-diff[37]
联合类型的操作
联合类型与泛型推导
联合类型代表着几种可能性的集合,它在泛型推导中和其他类型都不一样,你可以把他理解为它在做泛型推到时,并不是一次性判断,而是将每一项单独判读并返回,然后再将这些返回进行联合。
说起来有点绕,我们看下面的例子就明白了:
如我们上面说的,例子中并不是将 'a' | 'b' | 'c' 一次性判断的,而是:
为了更加清楚的明白,我们再举一个例子:
根据前面说的,它会将联合类型的每个成员拿去比较,最后返回。所以其判断步骤为:
学完本小结,你可以解决:
00043-easy-exclude[38] 00062-medium-type-lookup[39] 00296-medium-permutation[40]
其他
从 JS 值转为 TS 值
我们知道 TS 是有类型推导的,即便是一个没定义类型的 JS 变量也是有其类型定义的,此时我们是可以通过 typeof 完成从 JS 到 TS 的转化的。例如:
应用场景:
这种情况多用在,我们需要时使用开源库的类型时(比如泛型传参),它又没做类型定义或者没导出的场景,例如 redux-toolkit 文档示例[41]。
更精准的类型推测
有人就会好奇,为什么 name 会被推导为 string,而不是 "jack" 呢?
默认情况下 TS 对对象或者数组的推导是尽可能宽泛的,想要让其具体,需要使用到 as const 语法,让其尽可能精准的推测。例如:
要注意,每个元素都是 readonly 的哦。
总结与回顾
经过奋斗,大家终于来到了终点,我们以始为终,先看看最初定下的目标有没有实现:
就我个人而言,在学习和挑战 type-challenges 过程中是对 TS 类型有了更深入的了解,那么最后我们再看一下开头提到的 Prisma 类型定义到底说的什么意思。
>
到这里,终于算是结束了,散会,撒花 ✿✿ ヽ(°▽°)ノ ✿
关于本文 作者:超杰_ https://juejin.cn/post/7132490947320872974