共计 3544 个字符,预计需要花费 9 分钟才能阅读完成。
Overview
到目前为止,用 TS 也写了不少业务了,大部分时候其实只有有 类
以及 类型
的概念,在用 TS 实现业务的过程中也就不会有太大的问题。或者有写“C++”或“Java”的教训,咱们很快能从“JS”无痛切换到 TS。然而写久了就会发现,“TS”最有意思的中央不是单纯的类型定义以及类型束缚,或者说咱们应用“TS”自身是为了达到类型束缚的目标,类型定义只是咱们的一个伎俩;它的 类型编程
才是在理论应用过程中最香的中央。所以我对 TS 类型编程
做了一些摸索。
TS 编译 JSX
在 TS + React
的我的项目里,咱们通常会让 TS 的编译器 TSC
帮忙咱们实现 JSX 的转换,在 tsconfig.json
的配置 compilerOptions: {jsx: ""}
里,咱们大略会有如下抉择:
Mode | Input | Output | Output File Extension |
---|---|---|---|
preserve | <div /> | <div /> | .jsx |
react | <div /> | React.createElement(“div”) | .js |
react-native | <div /> | <div /> | .js |
react-jsx | <div /> | _jsx(“div”, {}, void 0) | .js |
这里次要列出两个罕用的选项的区别:
-
preserve
这一模式会保留 JSX 标签,把 TS 编译成 JS,因而咱们编译实现后并不能间接运行,还须要另外对保留下来的 jsx 进行解决。如果是用Webpack
打包,则配置如下:rules: [ { test: /\.tsx?$/, use: [ { loader: "babel-loader", options: {presets: ["@babel/preset-react"], }, }, {loader: "ts-loader",}, ], }, ],
react
这一模式会间接把 jsx 标签编译成 React 解决,最初失去的是能够间接运行的 js 文件,在Webpack
里只须要配置ts-loader
就行了,它会调用TSC
进行编译。
TS 类型编程
为了渐进式的了解 TS 的类型编程,咱们首先须要理解它的一些类型操作符,而后在理解 TS 内置的一些Type utility
。基于这些咱们再去摸索如何应用 TS 实现更加灵便的类型工具。
泛型
泛型是 TS 外面 类型编程 的根底,它也是进步咱们类型定义的 复用性 的根底。
function cloneObj<T>(arg: T): T {return arg;}
比方 cloneObj 就能被复用到各种对象的拷贝上,在类型定义里泛型能够看成是个 插槽
;而在类型编程里,泛型就能够看成是 变量
, 咱们能够再类型编程中应用这个变量和一些类型操作符去编写类型工具。
类型操作符
-
keyof
这个操作符次要是会取出对象类型里的所有key
而后失去一个联结类型
。type Animal = {name: string; age: number}; type AnumalKey = keyof Animal; // "name" | "age"
当咱们获得对象类型里蕴含
string
或number
的索引签名时,keyof
也会把其中的string
和number
取出到union type
中。type Thing = {[key: number]: unknown } type ThingKey = keyof Thing; // number type MultiThing = {[key: string]: unknown } type MultiThingKey = keyof MultiThing; // string | number // 这里咱们失去 string | number 是因为在 js 里对象的键会被默认转换成 string,也就是说 obj["1"]和 obj[1]获得的后果是一样的。type Obj = {name: string; [key: number]: unknown } type ObjKey = keyof Obj; // number | "name"
-
typeof
typeof
是推断一个变量的类型的操作符,在类型编程里,咱们的输出就是类型。const foo = () => 'foo'; type Foo = typeof foo; // () => string type FooReturn = ReturnType<typeof foo>; // string
-
索引类型
就像在 JS 里咱们能通过索引获得对应的值一样,在 TS 里咱们也能够通过索引获得对应的类型,同时它也能够联合keyof
应用,成果更佳。type Animal = {name: string; age: number; fly: boolean} type Fly = Animal["fly"]; // boolean; type AnimalVal = Animal[keyof Animal]; // string | number | boolean
-
映射类型(Mapped Type)
映射类型是通过in
操作符遍历类型的 key 失去的新的类型。配合+
,-
操作符能够对readonly
,?
关键字进行减少或剔除解决。type Animal = {name: string; age: number} type Mapped = {[K in keyof Animal]: boolean; }; // {name: boolean; age;boolean}
咱们对
Mapped
再形象一下,退出泛型
,如下:type Mapped<T> = {[K in keyof T]: boolean; }
到这里是不是曾经有
类型编程
那味了!
咱们再来退出+
,-
操作符:type MappedToPartial<T> = {[K in keyof T]?: boolean } type MappedToRequired<T> = {[K in keyof T]-?: boolean; }
-
条件类型(? :)
同样和 JS 里的条件操作符一样,? :
也能够再 TS 里应用,只不过它的操作对象是类型。它通常和extends
一起应用,毕竟类型里是没有===, >, <
这些操作符去做判断逻辑的,extends
就起到了对应的作用。type Flatten<T> = T extends any[] ? T[number] : T; // Extracts out the element type. type Str = Flatten<string[]>; // string // Leaves the type alone. type Num = Flatten<number>; // number
还是持续说
Flatten
,如果咱们这样写:Flatten<string[] | number[]>
,失去的后果是什么呢?到这咱们不得不提到一个概念:类型散发
-
类型散发
对于联结类型
作为条件类型
的操作数时,当联结类型单个的被用为裸类型
时,TS 会把联结类型散发成一次一次的推断:type Example1 = Flatten<string[] | number[]>; // string | number
当联结类型被用为
包裹类型
时,TS 不会对联结类型进行散发推断:type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never; // 'StrOrNumArr' is no longer a union. type StrOrNumArr = ToArrayNonDist<string | number>; // (string | number)[] /* 咱们要辨别 string[] | number[] 和 (string | number)[]的区别 前者是指由 string 数组 和 number 数组 组成的联结类型 后者是指一个数组里能够有 string 和 number 两种元素 */
官网文档对
类型散发
的解释:Typically, distributivity is the desired behavior. To avoid that behavior, you can surround each side of the extends keyword with square brackets.
-
-
infer
infer
是待推断的意思,在 TS 里它能够用来定义一个待推断类型变量。比方咱们从一个 redux 的 reducer 外面提取 state 和 action 的类型:type ExtractReducer<T> = T extends (state: infer S, action: infer A) => infer S ? {state: S, action: A} : never; type RootType = ExtractReducer<typeof rootReducer>; type RootState = RootType["state"]; type RootAction = RootType["action"];
这些基本上就是咱们在 类型编程
中会用到的一些货色了,下面也有提到相干的类型编程的例子,然而咱们还有些更深刻的例子没有去剖析,比方 类型编程怎么实现递归
, 这其实只是个解题思路的问题,基于以上的这些操作符,只有有递归的思维,也很容易在类型编程中实现的。