TypeScript高级类型-实用技巧

TypeScript高级类型-实用技巧

预备知识

  1. TypeScript高级类型-Partial
  2. TypeScript高级类型-条件类型

类型递归

在 TypeScript 中有这样一个内置类型工具 Required<T>,它可以将对象类型 T 上的所有 可选属性 转化为 必填属性

先看一下 Required<T> 是如何定义的

/**
 * Make all properties in T required
 */
type Required<T> = {
    [P in keyof T]-?: T[P];
};

类型转化过程如下:

interface IUser {
  name: string;
  age: number;
  sex?: string;
  department?: string;
  address?: string;
}
  
type requiredUser = Required<IUser>
/* type requiredUser = {
  name: string;
  age: number;
  sex: string;
  department: string;
  address: string;
} */

经过 Required<T> 处理后,可以看到类型 requiredUser 里面的所有属性都被转为了必选属性。

现在假如我们有如下一个具有嵌套的类型,再看一下转化结果

interface ICompany {
  id: string;
  companyName: string;
  companyAddress?: string;
}

interface IUser {
  name: string;
  age: number;
  sex?: string;
  department?: string;
  address?: string;
  company: ICompany; // 嵌套类型中包含可选属性
}


type requiredUser = Required<IUser>
/* type requiredUser = {
    name: string;
    age: number;
    sex: string;
    department: string;
    address: string;
    company: ICompany; // 嵌套类型中的可选类型未被转为必选属性
} */

转化结果发现嵌套类型 ICompany 中的可选属性并未被转化为必选属性,这是因为 Required<T> 只被用于当前类型 T 的转化,而对于内部嵌套的类型并不做处理。

这里想处理深层嵌套属性,就必须用到类型递归

结合 JavaScript 的处理方式,我们可以得到 TypeScript 的处理方式

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

判断属性值类型是否为对象类型,是则递归类型转化操作。

看一下转化结果

type requiredUser = DeepRequired<IUser>
/* type requiredUser = {
    name: string;
    age: number;
    sex: string;
    department: string;
    address: string;
    company: DeepRequired<ICompany>;
} */

注意:

如果 IUser 中的 company可选属性,那么经过 DeepRequired<IUser> 转化后,依然为 company: ICompany; // 嵌套类型中的可选类型未被转为必选属性

经过排查不难发现,当 company 为可选属性,那么其对应的类型为 ICompany | undefined,此时可以得到 ICompany | undefined extends object,并不满足赋值条件,故而得到 false 分支,即(ICompany)。

特殊关键字

其中 +- 这两个关键字用于映射类型中给属性添加修饰符,比如我们再上文中 Required<T> 的定义

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

type RemoveReadonly<T> = {
  -readonly [P in keyof T]: T[P]
}

用到了 -?,它代表将可选属性变为必选,-readonly 代表将只读属性变为非只读。

注释

我们可以使用 JSDoc 的方式来添加注释,借助于 IDE 可以给我们提供更友好的提示

enum EStatus {
  /**
   * 成功状态
   */
  Success,
  /**
   * 失败状态
   */
  Error
}
// 在调用对应状态的时候,会给出友好的文字提示

is 关键字

is 关键字经常用于依赖布尔值的判断来缩小参数的类型范围

比如:

function isString(test: any): test is string{
    return typeof test === 'string';
}

function getStringLength(foo: number | string){
    if(isString(foo)){
        console.log(foo.length); // string function
    }
}

getStringLength 在执行时,当 isString 返回 true 时,可以明确知道 foo 是字符串类型,故而调用 length 属性正常运行。

然而用下面这种方式则会报错

function isString(test: any): boolean{
    return typeof test === 'string';
}

经过该函数的判断并不能明确 foo 的类型,故而调用 length 报错。

泛型约束

在使用泛型的过程中,我们的泛型几乎可以是任何类型

/**
 * Make all properties in T readonly
 */
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

如果我们可以明确传入的泛型是属于哪一类,那么应用泛型约束将会使得代码有更好可维护性。

比如,我们知道上面代码中的泛型 T 属于 object 类型,那么对其添加泛型约束如下:

type ObjectReadonly<T extends object> = {
    readonly [P in keyof T]: T[P];
};

这样当我们在使用 工具类型 ObjectReadonly<T> 时,将会限制我们只能传入 object 类型。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章