• TypeScript映射类型和更好的字面量类型推断


    TypeScript 2.1 引入了映射类型,这是对类型系统的一个强大的补充。本质上,映射类型允许w咱们通过映射属性类型从现有类型创建新类型。根据咱们指定的规则转换现有类型的每个属性。转换后的属性组成新的类型。

    使用映射类型,可以捕获类型系统中类似 Object.freeze() 等方法的效果。冻结对象后,就不能再添加、更改或删除其中的属性。来看看如何在不使用映射类型的情况下在类型系统中对其进行编码:

    interface Point {
      x: number;
      y: number;
    }
    
    interface FrozenPoint {
      readonly x: number;
      readonly y: number;
    }
    
    function freezePoint(p: Point): FrozenPoint {
      return Object.freeze(p);
    }
    
    const origin = freezePoint({ x: 0, y: 0 });
    
    // Error! Cannot assign to 'x' because it
    // is a constant or a read-only property.
    origin.x = 42;
    

    咱们定义了一个包含 x 和 y 两个属性的 Point 接口,咱们还定义了另一个接口FrozenPoint,它与 Point相同,只是它的所有属性都被使用 readonly 定义为只读属性。

    freezePoint 函数接受一个 Point 作为参数并冻结该参数,接着,向调用者返回相同的对象。然而,该对象的类型已更改为FrozenPoint,因此其属性被静态类型化为只读。这就是为什么当试图将 42 赋值给 x属性时,TypeScript 会出错。在运行时,分配要么抛出一个类型错误(严格模式),要么静默失败(非严格模式)。

    虽然上面的示例可以正确地编译和工作,但它有两大缺点

    1. 需要两个接口。除了 Point 类型之外,还必须定义 FrozenPoint 类型,这样才能将 readonly 修饰符添加到两个属性中。当咱们更改 Point 时,还必须更改FrozenPoint,这很容易出错,也很烦人。
    2. 需要 freezePoint 函数。对于希望在应用程序中冻结的每种类型的对象,咱们就必须定义一个包装器函数,该函数接受该类型的对象并返回冻结类型的对象。没有映射类型,咱们就不能以通用的方式静态地使用 Object.freeze()。

    使用映射类型构建 Object.freeze()

    来看看 Object.freeze()是如何在 lib.d.ts 文件中定义的:

    /**
      * Prevents the modification of existing property attributes and values, and prevents the addition of new properties.
      * @param o Object on which to lock the attributes.
      */
    freeze<T>(o: T): Readonly<T>;
    

    该方法的返回类型为Readonly<T>,这是一个映射类型,它的定义如下:

    type Readonly<T> = {
      readonly [P in keyof T]: T[P]
    };
    
    

    这个语法一开始可能会让人望而生畏,咱们来一步一步分析它:

    • 用一个名为 T 的类型参数定义了一个泛型 Readonly。
    • 在方括号中,使用了 keyof 操作符。keyof T 将 T 类型的所有属性名表示为字符串字面量类型的联合。
    • 方括号中的 in 关键字表示我们正在处理映射类型。[P in keyof T]: T[P]表示将 T类型的每个属性 P 的类型转换为 T[P]。如果没有readonly修饰符,这将是一个身份转换。
    • 类型 T[P] 是一个查找类型,它表示类型 T 的属性 P 的类型。
    • 最后,readonly 修饰符指定每个属性都应该转换为只读属性。

    因为 Readonly<T> 类型是泛型的,所以咱们为T提供的每种类型都正确地入了Object.freeze() 中。

    const origin = Object.freeze({ x: 0, y: 0 });
    
    // Error! Cannot assign to 'x' because it
    // is a constant or a read-only property.
    origin.x = 42;
    

    映射类型的语法更直观解释

    这次咱们使用 Point 类型为例来粗略解释类型映射如何工作。请注意,以下只是出于解释目的,并不能准确反映TypeScript使用的解析算法。

    从类型别名开始:

    type ReadonlyPoint = Readonly<Point>;
    

    现在,咱们可以在 Readonly<T> 中为泛型类型 T 的替换 Point 类型:

    type ReadonyPoint = {
      readonly [P in keyof Point]: Point[P]
    };
    

    现在咱们知道 T 是 Point,可以确定keyof Point表示的字符串字面量类型的并集:

    type ReadonlyPoint = {
      readonly [P in "x" | "y"]: Point[p]
    };
    

    类型 P 表示每个属性 x 和 y,咱们把它们作为单独的属性来写,去掉映射的类型语法

    
    type ReadonlyPoint = {
      readonly x: Point["x"];
      readonly y: Point["y"];
    };   
       

    最后,咱们可以解析这两种查找类型,并将它们替换为具体的 x 和 y 类型,这两种类型都是 number。

    type ReadonlyPoint = {
      readonly x: number;
      readonly y: number;
    };
    

    最后,得到的 ReadonlyPoint 类型与咱们手动创建的 FrozenPoint 类型相同。

    更多映射类型的示例

    上面已经看到 lib.d.ts 文件中内置的 Readonly <T> 类型。此外,TypeScript 定义了其他映射类型,这些映射类型在各种情况下都非常有用。如下:

    /**
     * Make all properties in T optional
     */
    type Partial<T> = {
      [P in keyof T]?: T[P]
    };
    
    /**
     * From T pick a set of properties K
     */
    type Pick<T, K extends keyof T> = {
      [P in K]: T[P]
    };
    
    /**
     * Construct a type with a set of properties K of type T
     */
    type Record<K extends string, T> = {
      [P in K]: T
    };
    

    这里还有两个关于映射类型的例子,如果需要的话,可以自己编写:

    /**
     * Make all properties in T nullable
     */
    type Nullable<T> = {
      [P in keyof T]: T[P] | null
    };
    
    /**
     * Turn all properties of T into strings
     */
    type Stringify<T> = {
      [P in keyof T]: string
    };
    

    映射类型和联合的组合也是很有趣:

    type X = Readonly<Nullable<Stringify<Point>>>;
    // type X = {
    //     readonly x: string | null;
    //     readonly y: string | null;
    // };

    映射类型的实际用例

    实战中经常可以看到映射类型,来看看 react 和 Lodash :

    • react:组件的 setState 方法允许咱们更新整个状态或其中的一个子集。咱们可以更新任意多个属性,这使得setState方法成为 Partial<T> 的一个很好的用例。
    • Lodash:pick 函数从一个对象中选择一组属性。该方法返回一个新对象,该对象只包含咱们选择的属性。可以使用 Pick<T> 对该行为进行构建,正如其名称所示。

    更好的字面量类型推断

    字符串、数字和布尔字面量类型(如:"abc",1和true)之前仅在存在显式类型注释时才被推断。从 TypeScript 2.1 开始,字面量类型总是推断为默认值。在 TypeScript 2.0 中,类型系统扩展了几个新的字面量类型:

    • boolean 字面量类型
    • 数字字面量
    • 枚举字面量

    不带类型注解的 const 变量或 readonly 属性的类型推断为字面量初始化的类型。已经初始化且不带类型注解的 let 变量、var 变量、形参或非 readonly 属性的类型推断为初始值的扩展字面量类型。字符串字面量扩展类型是 string,数字字面量扩展类型是number,true 或 false 的字面量类型是 boolean,还有枚举字面量扩展类型是枚举。

    更好的 const 变量推断

    咱们从局部变量和 var 关键字开始。当 TypeScript 看到下面的变量声明时,它会推断baseUrl变量的类型是 string :

    var baseUrl = "https://example.com/";
    // 推断类型: string
    

    用 let 关键字声明的变量也是如此

    let baseUrl = "https://example.com/";
    // 推断类型: string
    

    这两个变量都推断为string类型,因为它们可以随时更改。它们是用一个字面量字符串值初始化的,但是以后可以修改它们。

    但是,如果使用const关键字声明变量并使用字符串字面量进行初始化,则推断的类型不再是 string,而是字面量类型:

    const baseUrl = "https://example.com/";
    // 推断类型: "https://example.com/"
    

    由于常量字符串变量的值永远不会改变,因此推断出的类型会更加的具体。 baseUrl 变量无法保存 "https://example.com/" 以外的任何其他值。

    字面量类型推断也适用于其他原始类型。如果用直接的数值或布尔值初始化常量,推断出的还是字面量类型:

    const HTTPS_PORT = 443;
    // 推断类型: 443
    
    const rememberMe = true;
    // 推断类型: true
    

    类似地,当初始化器是枚举值时,推断出的也是字面量类型:

    enum FlexDirection {
      Row,
      Column
    }
    
    const direction = FlexDirection.Column;
    // 推断类型: FlexDirection.Column
    

    注意,direction 类型为 FlexDirection.Column,它是枚举字面量类型。如果使用let或var 关键字来声明 direction 变量,那么它的推断类型应该是 FlexDirection。

    更好的只读属性推断

    与局部 const 变量类似,带有字面量初始化的只读属性也被推断为字面量类型:

    class ApiClient {
      private readonly baseUrl = "https://api.example.com/";
      // 推断类型: "https://api.example.com/"
    
      get(endpoint: string) {
        // ...
      }
    }
    

    只读类属性只能立即初始化,也可以在构造函数中初始化。试图更改其他位置的值会导致编译时错误。因此,推断只读类属性的字面量类型是合理的,因为它的值不会改变。

    当然,TypeScript 不知道在运行时发生了什么:用 readonly 标记的属性可以在任何时候被一些js 代码改变。readonly 修饰符只限制从 TypeScript 代码中对属性的访问,在运行时就无能为力。也就是说,它会被编译时删除掉,不会出现在生成的 js 代码中。

    广州品牌设计公司https://www.houdianzi.com PPT模板下载大全https://redbox.wode007.com

    推断字面量类型的有用性

    你可能会问自己,为什么推断 const 变量和 readonly 属性为字面量类型是有用的。考虑下面的代码:

    const HTTP_GET = "GET"; // 推断类型: "GET"
    const HTTP_POST = "POST"; // 推断类型: "POST"
    
    function get(url: string, method: "GET" | "POST") {
      // ...
    }
    
    get("https://example.com/", HTTP_GET);
    

    如果推断 HTTP_GET 常量的类型是 string 而不是 “GET”,则会出现编译时错误,因为无法将HTTP_GET 作为第二个参数传递给get函数:

    Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'
    

    当然,如果相应的参数只允许两个特定的字符串值,则不允许将任意字符串作为函数参数传递。但是,当为两个常量推断字面量类型“GET”和“POST”时,一切就都解决了。

  • 相关阅读:
    js遍历table和gridview
    斑马条码打印机通过js post 打印
    两个数据库通过DataTable实现差异传输
    Python2.X 和 Python3.X的区别
    Python核心编程(2)—— 基础(续)
    Python核心编程—— 起步(续)
    a标签下的div,在浏览器, 怎么会跑到a标签外面?
    功能测试
    Markdown初使用
    UI分层中使用PageFactory
  • 原文地址:https://www.cnblogs.com/qianxiaox/p/14097105.html
Copyright © 2020-2023  润新知