-
Notifications
You must be signed in to change notification settings - Fork 0
Typescript 类型编程,从入门到念头通达 #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
TS 类型本身就是一个很复杂的、独立的语言,不仅仅是 JS 的增强和类型注释。 |
Pick, Omit一般是操作对象属性;而 Extract, Exclude 一般是操作用type声明的联合类型。也不知道我理解的对不对? |
如何限制子组件的类型,如中组件中的children,只能是之类的: 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>
);
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
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 关键词。例如:
条件语句
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
The text was updated successfully, but these errors were encountered: