基础复合类型
联合类型
联合类型表示一个值可以是几种类型之一,用竖线( |)分隔每个类型.
举个栗子:
type MixType = number | string | boolean | null;
联合类型需要注意,如果一个类型是联合类型,我们只能访问所有类型的共有方法或属性。 如果要访问特定类型的属性和方法时,有两种方法:
- 类型断言
类型断言有两种形式,一种是使用
<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这种形式。
这种类型断言,只能提供简单的避免错误提示,你无法保证数据类型就是某个类型,所以更好的方法是使用下面的方式。
- 类型判断
使用JavaScript的原生类型判断
typeof与instanceof来分别区分基础数据类型和构造函数类型。
// 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]);
}
索引类型的关注点有两点:
keyof T, 索引类型查询操作符。T[K], 索引访问操作符。
keyof T返回的是T的已知的公共属性名的联合,返回的是一个字符串字面量类型,K extends keyof T,表示泛型的所属范围,相当于给这个字符串字面量类型定义了一个名称。
T[K]类似JavaScript的属性访问器,只是它代表的是T[K]的类型。
映射类型
TypeScript提供了从旧类型中创建新类型的一种方式 — 映射类型,在映射类型里,新类型以相同的形式去转换旧类型里每个属性。
映射类型中,有两个关注点:
-
[P in K],相当于JavaScript的for...in命令,表示P属于K中的某一项,并且要循环表示。 P in K 类似于 JavaScript 中的for...in语句,用于遍历 K 类型中的所有类型,而 T 类型变量用于表示 TS 中的任意类型 -
-号前缀,去修饰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 ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : 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 (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
借鉴资料: