寫了一段時間ts,在從頭學習一遍,溫故而之新
ts的一些技巧
1、巧用注釋
通過/** */形式的注釋可以給 TS 類型做標記,編輯器會有更好的提示:
/** A cool guy. */
interface Person {
/** A cool name. */
name: string,
}
2、巧用注釋 進階
注釋有很多規范的字段,基本和 JSDOC 一致。但不用着急翻文檔,在 /** */ 里輸入 @ 就可以看到豐富的選擇
3、巧用 typeof
我們一般先寫類型,再使用:
interface Opt {
timeout: number
}
const defaultOption: Opt = {
timeout: 500
}
有時候可以反過來:
const defaultOption = {
timeout: 500
}
type Opt = typeof defaultOption
當一個 interface 總有一個字面量初始值時,可以考慮這種寫法以減少重復代碼。
4、巧用聯合類型
// 🙁 Not good.
interface Dinner1 {
fish?: number,
bear?: number,
}
// 🙂 Awesome!
type Dinner2 = {
fish: number,
} | {
bear: number,
}
5、巧用查找類型+泛型+keyo
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())
}
6、巧用顯式泛型
$('button') 是個 DOM 元素選擇器,可是返回值的類型是運行時才能確定的,除了返回 any ,還可以
function $<T extends HTMLElement>(id: string):T {
return document.getElementById(id)
}
// Tell me what element it is.
$<HTMLInputElement>('input').value
函數泛型不一定非得自動推導出類型,有時候顯式指定類型就好。
7、巧用 DeepReadonly
type DeepReadonly<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>;
}
const a = { foo: { bar: 22 } }
const b = a as DeepReadonly<typeof a>
b.foo.bar = 33 // Hey, stop!
8、巧用 Omit
import { Button, ButtonProps } from './components/button'
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
type BigButtonProps = Omit<ButtonProps, 'size'>
function BigButton(props: BigButtonProps) {
return Button({ ...props, size: 'big' })
}
一些知識點
1、交叉類型 (intersection types)與聯合類型 (union types)
TypeScript 1.6 引入了交叉類型作為聯合類型 (union types) 邏輯上的補充. 聯合類型 A | B 表示一個類型為 A 或 B 的實體, 而交叉類型 A & B 表示一個類型同時為 A 或 B 的實體.
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U> {};
for (let id in first) {
result[id] = first[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
result[id] = second[id];
}
}
return result;
}
var x = extend({ a: "hello" }, { b: 42 });
var s = x.a;
var n = x.b;
type LinkedList<T> = T & { next: LinkedList<T> };
interface Person {
name: string;
}
var people: LinkedList<Person>;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;
interface A { a: string }
interface B { b: string }
interface C { c: string }
var abc: A & B & C;
abc.a = "hello";
abc.b = "hello";
abc.c = "hello";
2、abstract (抽象的) 類和方法
TypeScript 1.6 為類和它們的方法增加了 abstract 關鍵字. 一個抽象類允許沒有被實現的方法, 並且不能被構造.
abstract class Base {
abstract getThing(): string;
getOtherThing() { return 'hello'; }
}
let x = new Base(); // 錯誤, 'Base' 是抽象的
// 錯誤, 必須也為抽象類, 或者實現 'getThing' 方法
class Derived1 extends Base { }
class Derived2 extends Base {
getThing() { return 'hello'; }
foo() {
super.getThing();// 錯誤: 不能調用 'super' 的抽象方法
}
}
var x = new Derived2(); // 正確
var y: Base = new Derived2(); // 同樣正確
y.getThing(); // 正確
y.getOtherThing(); // 正確
3、keyof和查找類型
在JavaScript中屬性名稱作為參數的API是相當普遍的,但是到目前為止還沒有表達在那些API中出現的類型關系。
輸入索引類型查詢或keyof,索引類型查詢keyof T產生的類型是T的屬性名稱。keyof T的類型被認為是string的子類型。
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
4、索引訪問類型,也稱為查找類型
type P1 = Person["name"]; // string
type P2 = Person["name" | "age"]; // string | number
type P3 = string["charAt"]; // (pos: number) => string
type P4 = string[]["push"]; // (...items: string[]) => number
type P5 = string[][0]; // string
將這種模式和類型系統的其它部分一起使用,以獲取類型安全的查找
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key]; // 推斷類型是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"); // 錯誤!"wargarbl"不存在"foo" | "bar"中
setProperty(x, "foo", "string"); // 錯誤!, 類型是number而非string
5、映射類型
使用現有類型並使其每個屬性完全可選
interface Person {
name: string;
age: number;
location: string;
}
Person的可選屬性類型將是這樣:
interface PartialPerson {
name?: string;
age?: number;
location?: string;
}
使用映射類型,PartialPerson可以寫成是Person類型的廣義變換:
type Partial<T> = {
[P in keyof T]?: T[P];
};
type PartialPerson = Partial<Person>;
映射類型可以表示許多有用的類型轉換:
// 保持類型相同,但每個屬性是只讀的。
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// 相同的屬性名稱,但使值是一個Promise,而不是一個具體的值
type Deferred<T> = {
[P in keyof T]: Promise<T[P]>;
};
// 為T的屬性添加代理
type Proxify<T> = {
[P in keyof T]: { get(): T[P]; set(v: T[P]): void }
};
6、Partial,Readonly,Record和Pick
Partial和Readonly,如前所述,是非常有用的結構。你可以使用它們來描述像一些常見的JS程序:
function assign<T>(obj: T, props: Partial<T>): void;
function freeze<T>(obj: T): Readonly<T>;
因此,它們現在默認包含在標准庫中。
我們還包括兩個其他實用程序類型:Record和Pick。
// 從T中選取一組屬性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 }
// 對於類型T的每個屬性K,將其轉換為U
function mapObject<K extends string | number, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>
const names = { foo: "hello", bar: "world", baz: "bye" };
const lengths = mapObject(names, s => s.length); // { foo: number, bar: number, baz: number }
其他不錯的地址
weekly 2.0-2.9 精讀
react的技巧
TypeScript 2.8下的終極React組件模式
react-redux-typescript-guide
復雜 React 應用中的TypeScript 3.0實踐
庫react-redux-typescript-guide
其他一些知識點2.8
Deep Readonly
interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}
type DeepReadonlyObject<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>
}
type DeepReadonly<T> =
T extends (infer R)[]
? DeepReadonlyArray<R>
: T extends Function
? T
: T extends object
? DeepReadonlyObject<T>
: T
Mutable
type Mutable<T> = { -readonly [P in keyof T]: T[P] }
利用 ReturnType 直接拿 type 可以減少 boilerplate
還有兩個很常見但一直沒有被收進的
Diff
type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T]
2.8 內置了 Exclude 作為官方版的 Diff
Omit
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>
TS 中的內置類型簡述
Partial 局部的;偏愛的;不公平的 美 ['pɑrʃəl]
ts 中的實現
// node_modules/typescript/lib/lib.es5.d.ts
type Partial<T> = {
[P in keyof T]?: T[P];
};
這個類型的用處就是可以將某個類型里的屬性加上 ? 這個 modifier ,加了這個 modifier 之后那些屬性就可以為 undefined 了。
舉個例子,我有個接口 Person ,里面定義了兩個必須的屬性 name 和 age。
interface Person {
name: string;
age: number;
}
// error , property age is missing.
const axes: Person = {
name: 'axes'
}
如果使用了 Partial
type NewPerson = Partial<Person>;
// correct, because age can be undefined.
const axes: NewPerson = {
name: 'axes'
}
這個 NewPerson 就等同於
interface Person {
name?: string;
age?: number;
}
但是 Partial 有個局限性,就是只支持處理第一層的屬性,如果我的接口定義是這樣的
interface Person {
name: string;
age: number;
child: {
name: string;
age: number;
}
}
type NewPerson = Partial<Person>;
// error, property age in child is missing
const axes: NewPerson = {
name: 'axes';
child: {
name: 'whx'
}
}
可以看到,第二層以后的就不會處理了,如果要處理多層,就可以自己通過 Conditional Types 實現一個更強力的 Partial
export type PowerPartial<T> = {
// 如果是 object,則遞歸類型
[U in keyof T]?: T[U] extends object
? PowerPartial<T[U]>
: T[U]
};
Required 必需的;(美)必修的 美 [rɪ'kwaɪrd]
// node_modules/typescript/lib/lib.es5.d.ts
type Required<T> = {
[P in keyof T]-?: T[P];
};
這個類型剛好跟 Partial 相反,Partial 是將所有屬性改成不必須,Required 則是將所有類型改成必須。
其中 -? 是代表移除 ? 這個 modifier 的標識。再拓展一下,除了可以應用於 ? 這個 modifiers ,還有應用在 readonly ,比如 Readonly 這個類型
// node_modules/typescript/lib/lib.es5.d.ts
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
就可以給子屬性添加 readonly 的標識,如果將上面的 readonly 改成 -readonly 就是移除子屬性的 readonly 標識。
Pick 挑選;采摘;挖 美 [pɪk]
// node_modules/typescript/lib/lib.es5.d.ts
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
這個類型則可以將某個類型中的子屬性挑出來,比如上面那個 Person 的類
type NewPerson = Pick<Person, 'name'>; // { name: string; }
可以看到 NewPerson 中就只有個 name 的屬性了,這個類型還有更有用的地方,等講到 Exclude 類型會說明。
Record 記錄,記載;標明;將...錄音 美 [(for v.) rɪˈkɔrd; (for n.) ˈrekərd;ˈrɛkɚd]
// node_modules/typescript/lib/lib.es5.d.ts
type Record<K extends keyof any, T> = {
[P in K]: T;
};
可以獲得根據 K 中的所有可能值來設置 key 以及 value 的類型
type T11 = Record<'a' | 'b' | 'c', Person>; // { a: Person; b: Person; c: Person; }
Exclude 排除;排斥;拒絕接納;逐出 美 [ɪk'sklʊd]
// node_modules/typescript/lib/lib.es5.d.ts
type Exclude<T, U> = T extends U ? never : T;
個類型可以將 T 中的某些屬於 U 的類型移除掉,舉個例
type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"
可以看到 T 是 "a" | "b" | "c" | "d" ,然后 U 是 "a" | "c" | "f" ,返回的新類型就可以將 U 中的類型給移除掉,也就是 "b" | "d" 了。
那這個類型有什么用呢,在我看來,可以結合 Pick 類型使用。
在我們給 js 寫聲明的時候,經常會遇到我們需要 extend 某個接口,但是我們又需要在新接口中將某個屬性給 overwrite 掉,但是這樣經常會遇到類型兼容性問題。舉個例子
interface Chicken {
name: string;
age: number;
egg: number;
}
我需要繼承上面這個接口
// error, Types of property 'name' are incompatible
interface NewChicken extends Chicken {
name: number;
}
可以看到就會報錯了,因為在 Chicken 中 name 是 string 類型,而 NewChicken 卻想重載成 number 類型。很多時候可能有人就直接把 name 改成 any 就算了,但是不要忘了我們有個 Pick 的類型,可以把我們需要的類型挑出來,那就可以這樣
// correct.
interface NewChicken extends Pick<Chicken, 'age' | 'egg'> {
name: number;
}
可以看到,我們把 Person 中的類型做了挑選,只把 age 和 egg 類型挑出來 extend ,那么我復寫 name 就沒問題了。
不過再想一下,如果我要繼承某個接口並且復寫某一個屬性,還得把他的所有屬性都寫出來么,當然不用,我們可以用 Exclude 就可以拿到除 name 之外的所有屬性的 key 類型了。
type T01 = Exclude<keyof Chicken, 'name'>; // 'age' | 'egg'
然后把上面代碼加到 extend 中就成了
// correct.
interface NewChicken extends Pick<Chicken, Exclude<keyof Chicken, 'name'>> {
name: number;
}
然后還可以把這個處理封裝成一個單獨的類型
type FilterPick<T, U> = Pick<T, Exclude<keyof T, U>>;
然后上面的 extend 的代碼就可以寫成這樣,就更簡潔了
interface NewChicken extends FilterPick<Chicken, 'name'> {
name: number;
}
這樣一來,我們就可以愉快的進行屬性 overwrite 了。
ReturnType
// node_modules/typescript/lib/lib.es5.d.ts
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
這個類型也非常好用,可以獲取方法的返回類型,可能有些人看到這一長串就被繞暈了,但其實也是使用了 Conditional Types ,推論 ( infer ) 泛型 T 的返回類型 R 來拿到方法的返回類型
實際使用的話,就可以通過 ReturnType 拿到方法的返回類型,如下的示例
function TestFn() {
return '123123';
}
type T01 = ReturnType<typeof TestFn>; // string
ThisType
// node_modules/typescript/lib/lib.es5.d.ts
interface ThisType<T> { }
可以看到聲明中只有一個接口,沒有任何的實現,說明這個類型是在 ts 源碼層面支持的,而不是通過類型變換,那這個類型有啥用呢,是用於指定上下文對象類型的。
interface Person {
name: string;
age: number;
}
const obj: ThisType<Person> = {
dosth() {
this.name // string
}
}
這樣的話,就可以指定 obj 里的所有方法里的上下文對象改成 Person 這個類型了。跟
const obj = {
dosth(this: Person) {
this.name // string
}
}
差不多效果。
NonNullable
// node_modules/typescript/lib/lib.es5.d.ts
type NonNullable<T> = T extends null | undefined ? never : T;
根據實現可以很簡單的看出,這個類型可以用來過濾類型中的 null 及 undefined 類型
type T22 = '123' | '222' | null;
type T23 = NonNullable<T22>; // '123' | '222'
其他常用
PromiseType
export type PromiseType<T extends Promise<any>> = T extends Promise<infer R> ? R : any;