美文网首页
TypeScript 版本特性及技巧

TypeScript 版本特性及技巧

作者: 风之化身呀 | 来源:发表于2020-09-02 21:50 被阅读0次

    一、版本特性

    4.1

    • 模板字符串中可以使用字符串类型
    type World = "world";
    type Greeting = `hello ${World}`;
    
    // example1
    type VerticalAlignment = "top" | "middle" | "bottom";
    type HorizontalAlignment = "left" | "center" | "right";
    declare function setAlignment(value: `${VerticalAlignment}-${HorizontalAlignment}`): void;
    
    // example2
    let person = makeWatchedObject({
      firstName: "Homer",
      age: 42, // give-or-take
      location: "Springfield",
    });
    person.on("firstNameChanged", () => {
      console.log(`firstName was changed!`);
    });
    
    type PropEventSource<T> = {
        on(eventName: `${string & keyof T}Changed`, callback: () => void): void;
    };
    declare function makeWatchedObject<T>(obj: T): T & PropEventSource<T>;
    
    • Key Remapping in Mapped Types
    // 新语法 k in keyof T as NewKeyType
    type MappedTypeWithNewKeys<T> = {
        [K in keyof T as NewKeyType]: T[K]
    }
    // example1
    type Getters<T> = {
        [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
    };
    interface Person {
        name: string;
        age: number;
        location: string;
    }
    
    type LazyPerson = Getters<Person>;
    // equal to
    type LazyPerson = {
      getName: () => string;
      getAge: () => number;
      getLocation: () => string;
    }
    
    // example2
    type RemoveKindField<T> = {
        [K in keyof T as Exclude<K, "kind">]: T[K]
    };
    interface Circle {
        kind: "circle";
        radius: number;
    }
    type KindlessCircle = RemoveKindField<Circle>;
    
    • Recursive Conditional Types
    type ElementType<T> = T extends ReadonlyArray<infer U> ? ElementType<U> : T;
    
    function deepFlatten<T extends readonly unknown[]>(x: T): ElementType<T>[] {
      throw "not implemented";
    }
    
    // All of these return the type 'number[]':
    deepFlatten([1, 2, 3]);
    deepFlatten([[1], [2, 3]]);
    deepFlatten([[1], [[2]], [[[3]]]]);
    
    • --noUncheckedIndexedAccess 默认关闭,不随 --strictNullChecks 打开
    interface Options {
      path: string;
      permissions: number;
      [propName: string]: string | number;
    }
    
    const opt:Options
    opt.xxx  string|number|undefined
    
    // 影响 for 循环,不影响for-of,forEach
    function screamLines(strs: string[]) {
      // This will have issues
      for (let i = 0; i < strs.length; i++) {
        console.log(strs[i].toUpperCase());
    Object is possibly 'undefined'.
      }
    }
    
    • paths without baseUrl
    • checkJs Implies allowJs
    • Breaking Changes
    // resolve’s Parameters Are No Longer Optional in Promises
    new Promise((resolve) => {
      resolve() // error
    });
    
    // fix 
    new Promise<void>((resolve) => {
      resolve() // work
    });
    

    4.0

    • 可变元祖类型
      1、元祖类型可以使用扩展运算符了,之前只能对数组类型用
      2、扩展元算符可以用在任意位置了,之前只能用在最后一个参数
    type IT = [string,boolean]
    function tail<T extends any[]>(arr: readonly [any, ...IT]) {
      const [_ignored, ...rest] = arr;
      return rest;
    }
    
    type Strings = [string, string];
    type Numbers = [number, number];
    type StrStrNumNumBool = [...Strings, ...Numbers, boolean];
    
    // 不限参数长度
    type Strings = [string, string];
    type Numbers = number[];
    type Unbounded = [...Strings, ...Numbers, boolean];
    //   ==> type Unbounded = [string, string, ...(number | boolean)[]]
    
    • 标记的元祖类型
      下面这种写法
    function foo(...args: [string, number]): void {
      // ...
    }
    

    是这种提示:



    而这种写法的提示语意上更为明确

    type IRange = [start: string, end: number];
    function foo(...args: IRange): void {
      // ...
    }
    foo()
    
    • 构造函数的类属性推断
      现在类属性在构造函数中被初始化后会被推断出类型,之前一直是 any
    • 短路分配运算符
      新加了&&=,||=,和??=
    if(!a){
       a=b
    }
    ===>
    a||=b
    
    • catch 的 error 参数类型由 any 转为了 unknown
    • 启动时的部分编译功能
      4.0 支持在 VSCode 启动时优先编译当前工作区打开的文件,而不是等所有的项目都编译一遍才开始提供类型等信息

    3.9

    • 修复 Promise.all 类型推断错误问题
    interface Lion {
      roar(): void;
    }
    interface Seal {
      singKissFromARose(): void;
    }
    
    async function visitZoo(
      lionExhibit: Promise<Lion>,
      sealExhibit: Promise<Seal | undefined>
    ) {
      let [lion, seal] = await Promise.all([lionExhibit, sealExhibit]);
      lion.roar(); // uh oh
      // lion is possibly 'undefined'.
    }
    
    • 提供 ts-expect-error 注释
      与 ts-ignore 的区别在于,如果下一行没有 ts 错误,ts-ignore 啥也不干,但 ts-expect-error 本身会报错
    • 条件表达式中的未调用函数检查
      条件表达式中只写函数名而没加括号会报错
    • 解析可选链接和非null断言中的差异
    // before, this transform is a bug
    foo?.bar!.baz;  =>  (foo?.bar).baz;  
    // now
    foo?.bar!.baz;  =>  foo?.bar.baz;  
    
    • 交叉类型和可选属性的更严格检查
    interface A {
      a: number; // notice this is 'number'
    }
    interface B {
      b: string;
    }
    interface C {
      a?: boolean; // notice this is 'boolean'
      b: string;
    }
    declare let x: A & B;
    declare let y: C;
    // before is ok , now is error
    y = x;
    
    • getter/setter 不再可枚举
    • 类型参数any不再扩展any
    function foo<T extends any>(arg: T) {
      arg.spfjgerijghoied; // before is ok , now is  error!
    }
    

    3.x

    • 支持 reference 配置
    • ReadonlyArray 类型
      数组内元素不能增加、修改和删除
    function foo(arr: ReadonlyArray<string>) {
      arr.slice(); // okay
      arr.push("hello!"); // error!
    }
    

    也可写成 readonly string[],同时也支持 tuples 类型:readonly [string,number]

    • const 断言
      as const 可以将一些类型变为只读:
    // Type '"hello"'
    let x = "hello" as const;
    
    // Type 'readonly [10, 20]'
    let y = [10, 20] as const;
    
    // Type '{ readonly text: "hello" }'
    let z = { text: "hello" } as const;
    
    • 3.5 引入 Omit 类型
    type Person = {
      name: string;
      age: number;
      location: string;
    };
    
    type QuantumPerson = Omit<Person, "location">;
    
    // equivalent to
    type QuantumPerson = {
      name: string;
      age: number;
    };
    
    • 3.7 引入可选链
    // Before
    if (foo && foo.bar && foo.bar.baz) {
      // ...
    }
    // After
    if (foo?.bar?.baz) {
      // ...
    }
    
    • 3.7 引入空位合并
    let x = foo ?? bar();
    // equal to
    let x = foo !== null && foo !== undefined ? foo : bar();
    

    注意和 || 运算不同,|| 包含了更多 falsy 类的值,如 false,'',0 等

    • 3.8 引入类型导入和导出
    import type { SomeThing } from "./some-module.js";
    export type { SomeThing };
    
    • 3.8 真-私有字段
      之前都是 private,但这只在编译阶段有约束,编译成JS后仍可访问。3.8 的私有字段语法为: #valuableName,编译成JS后无法访问,实现上用到了 WeakMap
    class C {
      #foo = 10;
      cHelper() {
        return this.#foo;
      }
    }
    

    二、小技巧

    • 枚举 key-value 互取
    enum Color {Red = 1, Green = 2, Blue = 4}
    let c: Color = Color.Green; // 2
    let colorName: string = Color[Color.Green];  // 'Green'
    
    • Record
      如果定义一个对象类型,直接 Record<string,unknown>,而不是用 interface,interface写起来麻烦
    • Partial
      将一个已定义好的接口必选属性改为可选属性,类似的还有 Readonly,Required,Omit<T,K>,Pick<T,K>
    • 回调函数类型
    type Cb = ()=>void
    
    • 当为一个默认值定义类型时,可以用 type,而不是 interface。能少写代码
    // bad
    interface IState{
          loading: boolean;
          data:any[];
    }
    const defalutState:IState={
        loading:true,
        data:[]
    }
    // good
    const defalutState = {
        loading:true,
        data:[]
    }
    type IState = typeof defalutState
    
    • 导入其它非 js 类的文件,需要自己声明模块
    declare module "*!text" {
        const content: string;
        export default content;
    }
    // Some do it the other way around.
    declare module "json!*" {
        const value: any;
        export default value;
    }
    
    import fileContent from "./xyz.txt!text";
    import data from "json!http://example.com/data.json";
    
    • 类型冲突时,可以用 reference 指定使用哪个类型文件,比如,@types/node 和 @types/webpack-env 这两个文件都声明了 NodeJS,但有时 ts 找不到 webpack-env 里对 NodeJS 的 Module 接口扩展,导致 module.hot 会报错
    • 巧用查找类型
    interface Person {
      addr: {
        city: string,
        street: string,
        num: number,
      }
    }
    

    当需要使用 addr 的类型时, 可以直接 Person["addr"]

    • 巧用ClassOf
    abstract class Animal extends React.PureComponent {
      /* Common methods here. */
    }
    class Cat extends Animal {}
    class Dog extends Animal {}
    
    // `AnimalComponent` must be a class of Animal.
    const renderAnimal = (AnimalComponent: Animal) => {
      return <AnimalComponent/>; // WRONG!
    }
    

    上面的代码是错的,因为 Animal 是实例类型,不是类本身。应该

    interface ClassOf<T> {
      new (...args: any[]): T;
    }
    const renderAnimal = (AnimalComponent: ClassOf<Animal>) => {
      return <AnimalComponent/>; // Good!
    }
    
    renderAnimal(Cat); // Good!
    renderAnimal(Dog); // Good!
    
    
    • 为第三方库写类型文件

    首先我们需要明确包使用的导出规范,global/umd/commonjs/module 等
    对于 global 导出的包我们使用:

    declare namesapce MyLib {
      class A {}
      
      // 我们可以直接在代码中使用
      // const a = new MyLib.A()
    }
    

    对于 umd/commonjs 导出的包我们使用:

    declare module 'my-lib' {
      namespace MyLib {
        class A {}
        
        class B {}
    
        // 使用时
        // 我们可以使用
        // import * as MyLib from 'my-lib'
        // const a = new MyLib.A();
    
        // 如果开启了 ES Module 融合模式 (esModuleInterop=true)
        // 我们可以使用
        // import { A } from 'my-lib'
        // const a = new A()
      }
      export = MyLib
    }
    

    对于 ES Module 导出的包我们使用:

    declare module 'my-lib' {
      class MyLib {}
      
      export default MyLib
      
      // or other exorts
      export class A {}
      
      // 我们可以使用
      // import MyLib, {A} from 'my-lib'
      // const lib = new MyLib()
      // const a = new A()
    }
    
    • 常量枚举和普通枚举区别
      常量枚举在编译阶段会被删除
    export const enum T{
        a=1
    }
    T.a  // 会被编译为 1 /*a*/
    
    enum T{
        a=1
    }
    T.a 会被编译为
    var ITest;
    (function (ITest) {
        ITest[ITest["a"] = 1] = "a";
    })(ITest || (ITest = {}));
    ITest.a
    
    • 一份较全的 tsconfig.json 配置(编译上下文配置)
    {
      "compilerOptions": {
    
        /* 基本选项 */
        "target": "es5",                       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
        "module": "commonjs",                  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
        "lib": [],                             // 指定要包含在编译中的库文件
        "allowJs": true,                       // 允许编译 javascript 文件
        "checkJs": true,                       // 报告 javascript 文件中的错误
        "jsx": "preserve",                     // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
        "declaration": true,                   // 生成相应的 '.d.ts' 文件
        "sourceMap": true,                     // 生成相应的 '.map' 文件
        "outFile": "./",                       // 将输出文件合并为一个文件
        "outDir": "./",                        // 指定输出目录
        "rootDir": "./",                       // 用来控制输出目录结构 --outDir.
        "removeComments": true,                // 删除编译后的所有的注释
        "noEmit": true,                        // 不生成输出文件
        "importHelpers": true,                 // 从 tslib 导入辅助工具函数
        "isolatedModules": true,               // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).
    
        /* 严格的类型检查选项 */
        "strict": true,                        // 启用所有严格类型检查选项
        "noImplicitAny": true,                 // 在表达式和声明上有隐含的 any类型时报错
        "strictNullChecks": true,              // 启用严格的 null 检查
        "noImplicitThis": true,                // 当 this 表达式值为 any 类型的时候,生成一个错误
        "alwaysStrict": true,                  // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
    
        /* 额外的检查 */
        "noUnusedLocals": true,                // 有未使用的变量时,抛出错误
        "noUnusedParameters": true,            // 有未使用的参数时,抛出错误
        "noImplicitReturns": true,             // 并不是所有函数里的代码都有返回值时,抛出错误
        "noFallthroughCasesInSwitch": true,    // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)
    
        /* 模块解析选项 */
        "moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
        "baseUrl": "./",                       // 用于解析非相对模块名称的基目录
        "paths": {},                           // 模块名到基于 baseUrl 的路径映射的列表
        "rootDirs": [],                        // 根文件夹列表,其组合内容表示项目运行时的结构内容
        "typeRoots": [],                       // 包含类型声明的文件列表
        "types": [],                           // 需要包含的类型声明文件名列表
        "allowSyntheticDefaultImports": true,  // 允许从没有设置默认导出的模块中默认导入。
    
        /* Source Map Options */
        "sourceRoot": "./",                    // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
        "mapRoot": "./",                       // 指定调试器应该找到映射文件而不是生成文件的位置
        "inlineSourceMap": true,               // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
        "inlineSources": true,                 // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
    
        /* 其他选项 */
        "experimentalDecorators": true,        // 启用装饰器
        "emitDecoratorMetadata": true          // 为装饰器提供元数据的支持
    
        /* 编译文件选择 */
        "files":[],
        "include":[],
        "exclude":[],
      }
    }
    

    参考

    相关文章

      网友评论

          本文标题:TypeScript 版本特性及技巧

          本文链接:https://www.haomeiwen.com/subject/wjrbsktx.html