TS大法好!
[TOC]
TS Tricks
巧用注释
/** A cool guy. */
interface Person {
/** A cool name. */
name: string,
}
巧用 typeof
我们一般先写类型,再使用:
interface Opt {
timeout: number
}
const defaultOption: Opt = {
timeout: 500
}
有时候可以反过来:
const defaultOption = {
timeout: 500
}
type Opt = typeof defaultOption
当一个 interface 总有一个字面量初始值时,可以考虑这种写法以减少重复代码。
巧用查找类型
interface Person {
addr: {
city: string,
street: string,
num: number,
}
}
当需要使用 addr 的类型时,除了把类型提出来
interface Address {
city: string,
street: string,
num: number,
}
interface Person {
addr: Address,
}
还可以
Person["addr"] // This is Address.
有些场合后者会让代码更整洁、易读。
巧用查找范型
interface API {
'/user': { name: string },
'/menu': { foods: Food[] },
}
const get = <URL extends keyof API>(url: URL): Promise<API[URL]> => {
return fetch(url).then(res => res.json())
}
巧用显式泛型
$(‘button’) 是个 DOM 元素选择器,可是返回值的类型是运行时才能确定的,除了返回 any ,还可以
function $<T extends HTMLElement>(id: string):T {
return document.getElementById(id)
}
// Tell me what element it is.
$<HTMLInputElement>('input').value
函数泛型不一定非得自动推导出类型,有时候显式指定类型就好。
Lookup Types / 查找类型
[]
的类型版。
T[K]
返回 (类型T中以K为属性名的值) 的类型。K 必须是 keyof T 的子集,可以是一个字符串字面量。
const a = { k1: 1, k2: "v2" };
// tv1 为number
type tv1 = (typeof a)["k1"];
// tv2 为string
type tv2 = (typeof a)["k2"];
// tv$ 为 (number|string): 属性名的并集对应到了属性值的类型的并集
type tv$ = (typeof a)["k1" | "k2"];
// 以上的括号不是必需的: typeof 优先级更高
// 也可以用于获取内置类型 (string 或 string[]) 上的方法的类型
// (pos: number) => string
type t_charAt = string["charAt"];
// (...items: string[]) => number
type t_push = string[]["push"];
Readonly
使用上面介绍的新特性可以定义出一些可用作 类型的 decorator 的泛型,比如下面的 Readonly (已经在TS2.1标准库中):
/**
* Make all properties in T readonly
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface A {
k1: string;
k2: string;
k3: number;
}
/**
类型运算的结果为
type A_ro = {
readonly k1: string;
readonly k2: string;
readonly k3: number;
}
*/
type A_ro = Readonly<A>;
Readonly的强化版: DeepReadonly
前面提到的 Readonly 只限制属性只读,不会把属性的属性也变成只读:
const v = { k1: 1, k2: { k21: 2 } };
const v_ro = v as Readonly<typeof v>;
// 属性: 不可赋值
v_ro.k1 = 2;
// 属性的属性: 可以赋值
v_ro.k2.k21 = 3;
我们可以写一个DeepReadonly,实现递归的只读:
type DeepReadonly<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>;
};
const v_deep_ro = v as any as DeepReadonly<typeof v>;
// 属性: 不可赋值
v_deep_ro.k1 = 2;
// 属性的属性: 也不可赋值
v_deep_ro.k2.k21 = 3;
DeepReadonly 是怎样展开的
背景: 如果 A
是泛型的类型参数 (比如 T<A>
),则称形如 { [P in keyof A]
: (类型表达式) } 的映射类型为 A
的 同构 (isomorphic) 类型。这样的类型含有和 A
相同的属性名,即相同的”形状”。在展开 T<A>
时有如下的附加规则:
基本类型
(string | number | boolean | undefined | null)
的同构类型强行定义为其本身,即跳过了对值类型的运算union
类型 (如type A = A1 | A2
) 的同构类型T<A>
展开为T<A1> | T<A2>
所以上面的 DeepReadonly<typeof v>
的 (概念上) 展开过程是这样的 :
type T_DeepRO = DeepReadonly<{ k1: number; k2: { k21: number } }>
↓
type T_DeepRO = {
readonly k1: number;
readonly k2: DeepReadonly<{ k21: number }>;
}
↓
type T_DeepRO = {
readonly k1: number;
readonly k2: {
readonly k21: DeepReadonly<number>;
}
}
↓ (规则1)
type T_DeepRO = {
readonly k1: number;
readonly k2: {
readonly k21: number;
}
}
巧用Omit
利用Pick
和Exclude
组合成Omit。
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
泛型
function identity<T>(arg: T): T {
return arg;
}
/** We can also write the generic type as a call signature of an object literal type: */
let myIdentity: {<T>(arg: T): T} = identity;
泛型的限制
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
可以通过interface和extends
关键字解决限制
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}