进阶类型
TypeScript 中的进阶类型
Union Type | 联合类型
TypeScript 中允许定义联合类型,即指定某个变量可能为 A 类型也可能为 B 类型:
let stringOrNumber: string | number = 1;
stringOrNumber = "hello";
很多时候,我们希望将某个常量数组转化为联合字符串类型,则可以利用 typeof 来进行定义:
const DATE_TIME_FIELDS = ["createdAt", "updatedAt", "deletedAt"] as const;
const a: typeof DATE_TIME_FIELDS[number];
// a: "createdAt" | "updatedAt" | "deletedAt"
Index Types | 索引类型
TypeScript 2.1 中为我们引入了 keyof 关键字,能够获取某个类型 T 的属性名列表,其返回结果也是联合类型,譬如:
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string
需要注意的是,keyof 关键字后面只能衔接 Interface 类型,如果我们希望获取某个正常的 JS 对象的键,那么需要先使用 typeof 关键字来获取到该对象对应的接口类型:
const Obj = {
a: "b",
};
type K1 = keyof typeof Obj;
这即是所谓的索引存取类型,或者搜索类型,我们经常会将其用于限制参数的输入值:
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key]; // Inferred type is T[K]
}
function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]) {
obj[key] = value;
}
let x = { foo: 10, bar: "hello!" };
let foo = getProperty(x, "foo"); // number
let bar = getProperty(x, "bar"); // string
let oops = getProperty(x, "wargarbl"); // Error! "wargarbl" is not "foo" | "bar"
setProperty(x, "foo", "string"); // Error!, string expected number
Generics | 泛型
泛型允许我们灵活地定义某些函数或者类接收的参数类型,更易于创建灵活而可控地可重用组件,泛型函数定义格式如下:
<T>(items :T[], callback :(item :T) => T) :T[]
这里我们以简单地创建数组的函数为例:
function genericFunc<T>(argument: T): T[] {
const arrayOfT: T[] = []; // Create empty array of type T.
arrayOfT.push(argument); // Push, now arrayOfT = [argument].
return arrayOfT;
}
const arrayFromString = genericFunc<string>("beep");
console.log(arrayFromString[0]); // "beep"
console.log(typeof arrayFromString[0]); // String
const arrayFromNumber = genericFunc(42);
console.log(arrayFromNumber[0]); // 42
console.log(typeof arrayFromNumber[0]); // number
// 接口泛型
interface Pair<T1, T2> {
first: T1;
second: T2;
}
// 泛型类属性
class Pair<T> {
fst: T;
snd: T;
}
我们还可以指定泛型子类,即指定某个类型必须是实现某个接口或者继承自某个类:
interface HasLength {
length: number;
}
function addLengths<T extends HasLength>(t1: T, t2: T): number {
return t1.length + t2.length;
}
addLengths("hello", "abc");
addLengths([1, 2, 3], [100, 11, 99]);
TypeScript 2.3 之后支持泛型默认参数,可以某些场景减少函数类型重载的代码量,譬如:
declare function create<T extends HTMLElement = HTMLDivElement, U = T[]>(
element?: T,
children?: U
): Container<T, U>;
类泛型
在编码过程中,我们经常会需要根据传入的类型来动态创建该类型的对象,其编写方式如下:
// 直接设置类型为参数会抛出异常
function activatorNotWorking<T extends IActivatable>(type: T): T {
return new T(); // compile error could not find symbol T
}
// 应该以如下方式实现
function activator<T extends IActivatable>(type: { new (): T }): T {
return new type();
}
const classA: ClassA = activator(ClassA);
某个实例如下:
class TestBase {
hi() {
alert("Hi from base");
}
}
class TestSub extends TestBase {
hi() {
alert("Hi from sub");
}
}
class TestTwo<T extends TestBase> {
constructor(private testType: new () => T) {}
getNew(): T {
return new this.testType();
}
}
//let test = new TestTwo<TestBase>(TestBase);
let test = new TestTwo<TestSub>(TestSub);
let example = test.getNew();
example.hi();
Mapped Types
Partial Type | 偏类型
在实际开发中,我们往往只希望用到某个接口的部分属性,特别是在实体类的定义中:
interface UserModel {
email: string;
password: string;
address: string;
phone: string;
}
class User {
// 这里强制传入完全符合 UserModel 结构定义的对象,否则会抛出错误
update(user: UserModel) {
// Update user
}
}
如果我们将接口属性定义为了可选属性,那么又会面临大量的空判断;TypeScript 2.1 之后为我们提供了 Partial 关键字,其内部的类型声明类似于:
type Partial<T> = { [P in keyof T]?: T[P] };
我们可以用其声明部分校验:
class User {
update(user: Partial<UserModel>) {
// Update user
}
}
type ComponentConfig = {
optionOne: string;
optionTwo: string;
optionThree: string;
};
// 这里的使用场景是传入部分配置项
export class SomeComponent {
private _defaultConfig: Partial<ComponentConfig> = {
optionOne: "...",
};
}
Partial 同样能够用于类的声明中:
type RectangleShape = Partial<Shape & Perimeter> & Point;
我们也可以借鉴 Partial 的思想封装 DeepPartial,即深度嵌套:
export type DeepPartial<T> = {
[key in keyof T]?: DeepPartial<T[key]>;
};
TypeScript 还为我们提供了 Pick 与 Record 类型,Pick 类型允许我们定义仅包含目标类型中的部分属性:
// From T pick a set of properties K
declare function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K>;
const nameAndAgeOnly = pick(person, "name", "age"); // { name: string, age: number }