寫了一段時間ts,在從頭學習一遍,溫故而之新node
經過/** */形式的註釋能夠給 TS 類型作標記,編輯器會有更好的提示:react
/** A cool guy. */ interface Person { /** A cool name. */ name: string, }
註釋有不少規範的字段,基本和 JSDOC 一致。但不用着急翻文檔,在 /** */ 裏輸入 @ 就能夠看到豐富的選擇git
咱們通常先寫類型,再使用: interface Opt { timeout: number } const defaultOption: Opt = { timeout: 500 } 有時候能夠反過來: const defaultOption = { timeout: 500 } type Opt = typeof defaultOption 當一個 interface 總有一個字面量初始值時,能夠考慮這種寫法以減小重複代碼。
// 🙁 Not good. interface Dinner1 { fish?: number, bear?: number, } // 🙂 Awesome! type Dinner2 = { fish: number, } | { bear: number, }
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 函數泛型不必定非得自動推導出類型,有時候顯式指定類型就好。
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!
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' }) }
TypeScript 1.6 引入了交叉類型做爲聯合類型 (union types) 邏輯上的補充. 聯合類型 A | B 表示一個類型爲 A 或 B 的實體, 而交叉類型 A & B 表示一個類型同時爲 A 或 B 的實體.github
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";
TypeScript 1.6 爲類和它們的方法增長了 abstract 關鍵字. 一個抽象類容許沒有被實現的方法, 而且不能被構造.typescript
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(); // 正確
在JavaScript中屬性名稱做爲參數的API是至關廣泛的,可是到目前爲止尚未表達在那些API中出現的類型關係。
輸入索引類型查詢或keyof,索引類型查詢keyof T產生的類型是T的屬性名稱。keyof T的類型被認爲是string的子類型。json
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
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
將這種模式和類型系統的其它部分一塊兒使用,以獲取類型安全的查找redux
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
使用現有類型並使其每一個屬性徹底可選安全
interface Person { name: string; age: number; location: string; }
Person的可選屬性類型將是這樣:編輯器
interface PartialPerson { name?: string; age?: number; location?: string; }
使用映射類型,PartialPerson能夠寫成是Person類型的廣義變換:ide
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 } };
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 精讀
TypeScript 2.8下的終極React組件模式
react-redux-typescript-guide
複雜 React 應用中的TypeScript 3.0實踐
庫react-redux-typescript-guide
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 中的實現
// 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] };
// 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 標識。
// 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 類型會說明。
// 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; }
// 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 了。
// 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
// 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 } } 差很少效果。
// 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;