• home > webfront > ECMAS > typescript >

    TS数据类型(0):一些需要注意的地方笔记+typescript 内置类型

    Author:zhoulujun Date:

    在js中,我们知道数据类型一共有七种。在ts数据类型就复杂多了。本篇就数据类型一些特别需要注意的地方,填坑笔记

    在  《再谈js数据类型与对象数据结构底层实现原理-object array map set》里面总结过,JavaScript有8中数据类型。

    现在到typescript,类型变得更多了

    • 原始类型 (primitive type) :undefined | null | boolean | number | bigint | string | symbol | void

    • 非原始类型(non-primitive type:object

    • 动态类型:any

    • 底类型:never

    • 顶类型:unknown


    任意值

    TS还支持任意值any,也就是说使用它,你可以用来定义任意数据类型——但是实际应用尽量少用,不然代码里面any如星星点灯

    未声明类型的变量

    如果一个变量被声明但是没有定义类型没有被赋值,那么就默认它是一个任意值

    let anyThing;//等价于 ==> let anyThing:any
    anyThing = 7;
    anyThing = true;
    anyThing = "string";

    如果声明了但是没有定义类型没有被赋值,默认是任意值。如果赋值了,就默认其为其数据值相应数据类型


    联合类型

    联合类型就是同时定义多种类型,使用|来进行联合类型定义

    let a: string | number;

    访问联合类型的属性或方法

    TS不知道我们定义的联合属性到底取哪一个时,我们只能访问他们共有属性,否则会报错

    let a: string | number;
    a.length // 报错,因为length不属于共有属性
    a.toString // 正确,因为它们都有原型链中的toString方法

    如果TS知道取哪一个,那么会启动类型推论,定义类型

    let a: string | number;
    a = "string"; //正确编译
    a.length; //正确编译
    a = 1; //正确编译
    a.length; //报错


    并集(Unions)与交集(Intersection)

    • 并集(Unions):能夠符合其中一種型別即可

    • 交集(Intersection)則是要能夠同時符合兩種型別

      • primitive type 通常用 & 會變成 never,因為不可能同時滿足兩個 primitive type

      • object 的話,該物件需要同時有 A type 中的屬性和 B type 中的屬性(少一個都不行)

    /** 對於 primitive type 來說 */
    type UnionPrimitive = string | number; // string | number
    type IntersectionPrimitive = string & number; // never
    
    /** 對於 object type 來說 */
    type Circle = {
      color: string;
      radius: number;
    };
    
    type Rectangle = {
      color: string;
      width: number;
      height: number;
    };
    
    // 只需符合 Circle 的屬性或 Rectangle 的屬性即可
    const foo: Circle | Rectangle = {
      color: 'red',
      radius: 15,
      height: 20,
    };
    
    // 需要同時帶有 Circle 和 Rectangle 中的屬性(缺一不可)
    const bar: Circle & Rectangle = {
      color: 'red',
      radius: 15,
      width: 30,
      height: 20,
    };



    对象类型

    使用接口(Interfaces)来定义对象的类型,以下示例对于对象的形状(Shape)进行描述

    interface Person {
      name: string;
      age: number;
    }

    可选属性

    但是有时候我们又不一定想要完全匹配形状,可以使用可选属性,使用一个?来描述可选属性

    interface Person {
      name: string;
      age?: number;
    }

    任意属性

    有时候我们又需要有任意属性,我并不想定义内部太多的属性名,那么就可以用到任意属性

    interface Person {
      name: string;
      age?: number;
      [propName: string]: string;
    }

    但是需要注意的一点,确定了任意属性后,那么确定的属性和可选的属性都必须是它的子集

    interface Person {
      name: string;
      age?: number; //编译报错,number跟string冲突
      age2?:string;//正确编译
      [propName: string]: string;
    }

    要求确定的属性name和可选属性age都属于它的任意属性的子集,由于number不是string的子集,所以报错了。

    一个接口只能有一个任意属性,如果要同时有多个类型,那么可以用联合属性

    只读属性

    如果我们希望一个属性允许只读,不允许编辑,那么可以使用readonly来定义只读属性。

    interface Person {
      readonly name: string;
      age?: number;
      [propName: string]: number | string;
    }

    如果想要对此属性重新赋值就会报错

    动态属性

    interface ChinaMobile {
      name: string;
      website: string;
    }
    
    interface ChinaMobileList {
      // 动态属性
      [phone: string]: ChinaMobile
    }

    数据如下:

    const list:ChinaMobileList={
      '10086': {
        name: '中国移动',
        website: 'http://www.10086.cn',
      },
      '10010': {
        name: '中国联通',
        website: 'http://www.10010.com',
      },
    }


    数组类型

    在TS中,如果要对数组进行定义,可以使用“类型+[]”的形式来表

    let array: number[] = [1, 2, 3];
    let array: string[] = ["1", "2", "3"];

    伪数组

    伪数组的定义方式不跟数组一样

    function sum() {
        let args: {
            [index: number]: number;
            length: number;
            callee: Function;
        } = arguments;
    }

    在这个例子中,我们除了约束当索引的类型是数字时,值的类型必须是数字之外,也约束了它还有 length 和 callee 两个属性

    事实上常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等:

    function sum() {
        let args: IArguments = arguments;
    }


    typescript 内置类型

    简历推荐阅读:《TS数据类型(1):从Utility Types发微

    Partial

    将类型 T 的所有属性标记为可选属性

    type Partial<T> = {
        [P in keyof T]?: T[P];
    };

    使用场景:

    // 账号属性
    interface AccountInfo {
        name: string 
        email: string 
        age: number 
        vip: 0|1 // 1 是vip ,0 是非vip
    }
    
    // 当我们需要渲染一个账号表格时,我们需要定义
    const accountList: AccountInfo[] = []
    
    // 但当我们需要查询过滤账号信息,需要通过表单,
    // 但明显我们可能并不一定需要用到所有属性进行搜索,此时可以定义
    const model: Partial<AccountInfo> = {
      name: '',
      vip: undefind
    }

    Required

    与 Partial 相反,Required 将类型 T 的所有属性标记为必选属性

    type Required<T> = {
        [P in keyof T]-?: T[P];
    };

    Readonly

    将所有属性标记为 readonly, 即不能修改

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

    Pick<T, K>

    从 T 中过滤出属性 K

    type Pick<T, K extends keyof T> = {
        [P in K]: T[P];
    };

    使用场景:

    interface AccountInfo {
      name: string 
      email: string 
      age: number 
      vip?: 0|1 // 1 是vip ,0 是非vip
    }
    
    type CoreInfo = Pick<AccountInfo, 'name' | 'email'>
    /* 
    { 
      name: string
      email: stirng
    }
    */

    Record<K, T>

    标记对象的 key value类型

    type Record<K extends keyof any, T> = {
        [P in K]: T;
    };

    使用场景:

    // 定义 学号(key)-账号信息(value) 的对象
    const accountMap: Record<number, AccountInfo> = {
      10001: {
        name: 'xx',
        email: 'xxxxx',
        // ...
      }    
    }
    const user: Record<'name'|'email', string> = {
        name: '', 
        email: ''
    }
    // 复杂点的类型推断
    function mapObject<K extends string | number, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>
    
    const names = { foo: "hello", bar: "world", baz: "bye" };
    // 此处推断 K, T 值为 string , U 为 number
    const lengths = mapObject(names, s => s.length);  // { foo: number, bar: number, baz: number }

    Exclude<T, U>,Omit<T, K>

    移除 T 中的 U 属性

    type Exclude<T, U> = T extends U ? never : T;

    使用场景:

    // 'a' | 'd'
    type A = Exclude<'a'|'b'|'c'|'d' ,'b'|'c'|'e' >

    乍一看好像这个没啥卵用,但是,我们通过一番操作,之后就可以得到 Pick 的反操作:

    type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
    
    type NonCoreInfo = Omit<AccountInfo, 'name' | 'email'>
    /*
    {
      age: number 
      vip: 0|1,
    }
    */

    Extract<T, U>

    Exclude 的反操作,取 T,U两者的交集属性

    type Extract<T, U> = T extends U ? T : never;

    使用 demo:

    // 'b'|'c'type A = Extract<'a'|'b'|'c'|'d' ,'b'|'c'|'e' >  
    复制代码

    这个看起来没啥用,实际上还真没啥卵用,应该是我才疏学浅,还没发掘到其用途。

    NonNullable

    排除类型 T 的 null | undefined 属性

    type NonNullable<T> = T extends null | undefined ? never : T;复制代码

    使用 demo

    type A = string | number | undefined 
    type B = NonNullable<A> // string | numberfunction f2<T extends string | undefined>(x: T, y: NonNullable<T>) {    let s1: string = x;  // Error, x 可能为 undefined    let s2: string = y;  // Ok
    }复制代码

    Parameters

    获取一个函数的所有参数类型

    // 此处使用 infer P 将参数定为待推断类型
    // T 符合函数特征时,返回参数类型,否则返回 nevertype Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;复制代码

    使用demo:

    interface IFunc {
      (person: IPerson, count: number): boolean
    }type P = Parameters<IFunc> // [IPerson, number]
    
    const person01: P[0] = {
      // ...
    }复制代码

    另一种使用场景是,快速获取未知函数的参数类型

    import {somefun} from 'somelib'
    // 从其他库导入的一个函数,获取其参数类型
    type SomeFuncParams = Parameters<typeof somefun>
    
    // 内置函数
    // [any, number?, number?]
    type FillParams = Parameters<typeof Array.prototype.fill>

    ConstructorParameters

    类似于 Parameters<T>, ConstructorParameters 获取一个类的构造函数参数

    type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;

    使用 demo:

    // string | number | Date 
    type DateConstrParams = ConstructorParameters<typeof Date>

    ReturnType

    获取函数类型 T 的返回类型

    type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

    使用方式和 Parameters<T> 类似,不再赘述

    InstanceType

    获取一个类的返回类型

    type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;

    使用方式和 ConstructorParameters<T> 类似,不再赘述




    参考文章:

    还可以这么玩?超实用 Typescript 内置类型与自定义类型 https://juejin.cn/post/6844903860692074504



    转载本站文章《TS数据类型(0):一些需要注意的地方笔记+typescript 内置类型》,
    请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/typescript/2020_0114_8649.html