Overview

到目前为止,用TS也写了不少业务了,大部分时候其实只有有 以及 类型 的概念,在用TS实现业务的过程中也就不会有太大的问题。或者有写 “C++” 或 “Java” 的教训,咱们很快能从 “JS” 无痛切换到 TS。然而写久了就会发现,“TS” 最有意思的中央不是单纯的类型定义以及类型束缚,或者说咱们应用 “TS” 自身是为了达到类型束缚的目标,类型定义只是咱们的一个伎俩;它的 类型编程 才是在理论应用过程中最香的中央。所以我对 TS类型编程 做了一些摸索。

TS 编译 JSX

TS + React 的我的项目里,咱们通常会让TS的编译器 TSC帮忙咱们实现JSX的转换,在tsconfig.json的配置compilerOptions: { jsx: "" }里,咱们大略会有如下抉择:

ModeInputOutputOutput 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"

    当咱们获得对象类型里蕴含stringnumber的索引签名时,keyof也会把其中的stringnumber取出到union type中。

    type Thing = { [key: number]: unknown }type ThingKey = keyof Thing; // numbertype 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; // () => stringtype 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"];

这些基本上就是咱们在类型编程中会用到的一些货色了,下面也有提到相干的类型编程的例子,然而咱们还有些更深刻的例子没有去剖析,比方类型编程怎么实现递归,这其实只是个解题思路的问题,基于以上的这些操作符,只有有递归的思维,也很容易在类型编程中实现的。