进阶类型
TypeScript 中的进阶类型
Union Type | 联合类型
let stringOrNumber: string | number = 1;
stringOrNumber = "hello";
很多时候,我们希望将某个常量数组转化为联合字符串类型,则可以利用
const DATE_TIME_FIELDS = ["createdAt", "updatedAt", "deletedAt"] as const;
const a: typeof DATE_TIME_FIELDS[number];
// a: "createdAt" | "updatedAt" | "deletedAt"
Index Types | 索引类型
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
需要注意的是,
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]);
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
}
}
如果我们将接口属性定义为了可选属性,那么又会面临大量的空判断;
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: "...",
};
}
type RectangleShape = Partial<Shape & Perimeter> & Point;
我们也可以借鉴
export type DeepPartial<T> = {
[key in keyof T]?: DeepPartial<T[key]>;
};
// 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 }