乐趣区

关于javascript:TypeScript-类型编程

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"

    当咱们获得对象类型里蕴含 stringnumber的索引签名时,keyof也会把其中的 stringnumber取出到 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"];

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

退出移动版