typescript的高级类型特征总结

928 阅读7分钟

基础复合类型

联合类型

联合类型表示一个值可以是几种类型之一,用竖线( |)分隔每个类型.

举个栗子:

type MixType = number | string | boolean | null;

联合类型需要注意,如果一个类型是联合类型,我们只能访问所有类型的共有方法或属性。 如果要访问特定类型的属性和方法时,有两种方法:

  1. 类型断言 类型断言有两种形式,一种是使用<type>,另一种是使用as

使用<type>

type MixType = number | string;
function handle(a:MixType){
    return a;
}
const result = handle('1');
(<number>result).toFixed();

使用as

type MixType = number | string;
function handle(a:MixType){
    return a;
}
const result = handle('1');
(result as number).toFixed();

需要注意在tsx文件中,对<type>这种标识,react编译器把它当作组件,所以在tsx文件中一般使用as这种形式。 这种类型断言,只能提供简单的避免错误提示,你无法保证数据类型就是某个类型,所以更好的方法是使用下面的方式。

  1. 类型判断 使用JavaScript的原生类型判断typeofinstanceof来分别区分基础数据类型和构造函数类型。
// typeof
type MixType = number | string;
function handle(value:MixType){
    if (typeof value === "number") { 
        // do something
    } 
    if (typeof value === "string") {
        // do something 
    }
}
// instanceof
type MixType = ClassName1 | ClassName2;
function handle(value:MixType){
    if (value instanceof ClassName1) { 
        // do something
    } 
    if (value instanceof ClassName2) {
        // do something
    }
}

联合类型的本质是什么?它就是在一个属性或字段它有可能会有多种值的情况,为什么会有这种情况呢?这是JavaScript的特征决定的,但是都使用typescript了,为什么还有使用这种联合类型呢?使用typescript的特征,可以使用函数重载来进行多类型处理,但是无法处理局部变量,并且有时你无法保证外部的给你的数据是一个准确的类型,比如像后端接口,设计不合理时,某个字段可能是字符串或者数字,这种情况只有使用联合类型。

如果决策权在你自己的话,去尽量使用单类型的设计。

交叉类型

交叉类型是将多个类型合并为一个类型,用( &)分隔每个类型.

它包含了所需的所有类型的特性,包含了所有类型的特性。

它类似于接口的继承功能,但两者有区别。

有一点需要注意,交叉类型在合并基础类型时,会将交叉类型转化为never类型,这样进行基础类型时,就不满足类型要求。

type CrossType = number & string;// never
const value:CrossType = 1 // Error, because CrossType become never;

但是可以组合使用交叉类型和联合类型,来从基础类型的联合类型中确认一项;

type CrossType = number | string;
type NewType = number & CrossType; // number;

为什么要有交叉类型?其实它最主要的用途是当两个对象合并后,产生的新的数据结构,这在JavaScript中经常遇见,目的就是能够规范对象合并后的数据结构。

字符串字面量类型

字符串字面量类型允许你指定字符串必须的固定值

type StringType = 'value1' | 'value2' | 'value3';

当使用StringType类型进行类型注册后,这个值只能是上面三个值中的任何一个。

字符串字面量类型还可以用于区分函数重载:

function createElement(tagName: "img"): HTMLImageElement; 
function createElement(tagName: "input"): HTMLInputElement; 
// ... more overloads ... 
function createElement(tagName: string): Element { 
    // ... code goes here ...
}

数字字面量类型

字符串字面量类型,还有数字字面量类型,只允许使用指定数值的固定值

type NumberType = 0 | 1;

高级复合类型

索引类型

使用索引类型,编译器就能够检查使用了动态属性名的代码,这使用索引类型的目的。

function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] { 
    return names.map(n => o[n]); 
}

索引类型的关注点有两点:

  1. keyof T, 索引类型查询操作符
  2. T[K], 索引访问操作符

keyof T返回的是T的已知的公共属性名的联合,返回的是一个字符串字面量类型,K extends keyof T,表示泛型的所属范围,相当于给这个字符串字面量类型定义了一个名称。 T[K]类似JavaScript的属性访问器,只是它代表的是T[K]的类型。

映射类型

image.png TypeScript提供了从旧类型中创建新类型的一种方式 — 映射类型,在映射类型里,新类型以相同的形式去转换旧类型里每个属性。

映射类型中,有两个关注点:

  1. [P in K],相当于JavaScript的for...in命令,表示P属于K中的某一项,并且要循环表示。 P in K 类似于 JavaScript 中的 for...in 语句,用于遍历 K 类型中的所有类型,而 T 类型变量用于表示 TS 中的任意类型

  2. -号前缀,去修饰readonly 和 ?,来实现去掉这些修饰符。

使用 readonly 和 ? 这两个额外的修饰符。通过添加 +- 前缀,来增加和移除对应的修饰符。**如果没有添加任何前缀的话,默认是使用 +

以上特征组合,总结出常见的映射类型语法

[ P in K ] : T }
{ [ P in K ] ?: T }
{ [ P in K ] -?: T }
{ readonly [ P in K ] : T }
{ readonly [ P in K ] ?: T }
{ -readonly [ P in K ] ?: T }

使用映射类型,可以写一些 Partial、Required、Pick 和 Readonly 的工具类型

type Partial<T> = {
    [P extends keyof T]?: T[P];
}
type Required<T> = {
    [P extends keyof T]-?: T[P];
}
type Pick<T, K extends keyof T> = {
    [P in K]?: T[P];
}
type Readonly<T> = {
    readonly [P in keyof T]?: T[P];
}

条件类型

条件类型的语法:

T extends U ? X : Y

其中 T、U、X 和 Y 这些都是类型占位符。你可以这样理解该语法,当类型 T 可以赋值给类型 U 时,那么返回类型 X,否则返回类型 Y。

type IsString<T> = T extends string ? true : false;
type I0 = IsString<number>;  // false
type I1 = IsString<"abc">;  // true
type I2 = IsString<any>;  // boolean
type I3 = IsString<never>;  // never

利用条件类型和条件链,我们还可以同时判断多种类型

type TypeName<T> =
    T extends string ? "string" :
    T extends number ? "number" :
    T extends boolean ? "boolean" :
    T extends undefined ? "undefined" :
    T extends Function ? "function" :
    "object";

type T0 = TypeName<string>;  // "string"
type T1 = TypeName<"a">;  // "string"
type T2 = TypeName<true>;  // "boolean"
type T3 = TypeName<() => void>;  // "function"
type T4 = TypeName<string[]>;  // "object"

当对联合类型使用条件类型时,一个有趣的结果出现

type T10 = TypeName<string | (() => void)>;  // "string" | "function"\
type T11 = TypeName<string | string[] | undefined>;  // "string" | "object" | "undefined"

底层逻辑:

T extends U ? X : Y \
T => A | B | C \
A | B | C extends U ? X : Y  => \
(A extends U ? : Y) | (B extends U ? : Y) | (C extends U ? : Y)

联合类型产生分布式条件类型的结果,但是这是有一个前提的:联合类型中不能有被数组、元组或 Promise 等包装过的类型,才能出现分布式条件类型;

type Naked<T> = T extends boolean ? "Y" : "N";

type WrappedTuple<T> = [T] extends [boolean] ? "Y" : "N";
type WrappedArray<T> = T[] extends boolean[] ? "Y" : "N";
type WrappedPromise<T> = Promise<T> extends Promise<boolean> ? "Y" : "N";
\
type T0 = Naked<number | boolean>; // "N" | "Y"
type T1 = WrappedTuple<number | boolean>; // "N"
type T2 = WrappedArray<number | boolean>; // "N"
type T3 = WrappedPromise<number | boolean>; // "N"

开发工具类型用法:

type Exclude<T, U> = T extends U ? never : T;\
type T4 = Exclude<"a" | "b" | "c""a" | "b">\
\
("a" extends "a" | "b" ? never : "a"// => never\
| ("b" extends "a" | "b" ? never : "b"// => never\
| ("c" extends "a" | "b" ? never : "c"// => "c"\
\
never | never | "c" // => "c"

使用条件类型,开发实现 FunctionProperties 和 NonFunctionProperties 等工具类型:

type FunctionPropertyNames<T> = {
    [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;

type NonFunctionPropertyNames<T> = {
    [K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;

interface User {
    id: number;
    name: string;
    age: number;
    updateName(newName: string): void;
}

type T5 = FunctionPropertyNames<User>; // "updateName"
type T6 = FunctionProperties<User>; // { updateName: (newName: string) => void; }
type T7 = NonFunctionPropertyNames<User>; // "id" | "name" | "age"
type T8 = NonFunctionProperties<User>; // { id: number; name: string; age: number; }

使用条件类型,可以做出 Exclude、Extract、NonNullable、Parameters 和 ReturnType 这些工具类型:

type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
type NonNullable<T> = T extends null | undefined ? never : T;
type Parameters<T extends (...argsany) => any> = T extends (...args: infer P) => any ? P : never;
type ReturnType<T extends (...argsany) => any> = T extends (...argsany) => infer R ? R : any;

借鉴资料:

高级类型

用了 TS 映射类型,同事直呼内行!

用了 TS 条件类型,同事直呼 YYDS!