大部分示例和教程來自優秀的 A. Sharif 的系列文章html
function prop(obj, key) {
return obj[key]
}
複製代碼
這樣一個函數如何讓其類型安全呢?typescript
function prop<T, Key extends keyof T>(obj: T, key: Key) {
return obj[key];
}
const user: User = {
id: 1,
name: "Test User",
points: 0
};
const userName = prop(user, "name"); // const userName : string;
複製代碼
須要像這樣使用到泛型,由於咱們不知道這個 obj 的類型定義是怎樣的,因此咱們須要使用 T 來做爲 obj 的定義的佔位,T 是第一個泛型參數。Key 是第二個泛型參數,它 extends 自 T 的全部 key 值,Key 在這裏就是 "id" | "name" | "points"
的全部子集。express
在使用的時候咱們能夠不用顯式的傳入泛型參數,由於咱們給函數傳參的時候,TS 編譯器就已經幫咱們推導出來正確的泛型參數了,而且驗證了第二個參數的類型必須是 extends 自 T 的全部 key 值。這就爲咱們提供了魯棒性很是好的,類型安全的函數了,結合 vscode 還有自動補全的功能哦。json
條件類型自 Typescript 2.8 引入。 它的定義是安全
"A conditional type selects one of two possible types based on a condition expressed as a type relationship test"app
type S = string extends any ? "string" : never;
/* type S = "string" */
複製代碼
這個三元操做符的解釋是若是 string
類型 extends 自 any
類型,(答案是明顯的YES)。那麼類型 S 的定義就是 string
,不然就是 never
。函數
常見的應用就是標準庫中 Exclude
的定義。學習
/** * Exclude from T those types that are assignable to U */
type Exclude<T, U> = T extends U ? never : T;
複製代碼
Exclude
可用於兩個聯合類型之間取差集。ui
在 lodash
庫中有兩個實用的方法,pick
和 omit
.es5
var object = { 'a': 1, 'b': '2', 'c': 3 };
_.pick(object, ['a', 'c']);
// => { 'a': 1, 'c': 3 }
複製代碼
pick
用於由選取的對象屬性組成新的對象。
var object = { 'a': 1, 'b': '2', 'c': 3 };
_.omit(object, ['a', 'c']);
// => { 'b': '2' }
複製代碼
omit
的釋義是消除。與 pick
相反; 此方法建立一個對象,消除指定的屬性,剩下的對象屬性組成一個新的對象。
在 Typescript 中也有相似的概念。只不過被操做的對象不是對象字面量,而是類型.
Pick 在 Typescript 的標準庫中的定義是
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
複製代碼
能夠看出 Pick 也是一種類型,可用於創造新的類型。這個新的類型的 key 值爲泛型 T 的全部 key 值的聯合類型的子集。
interface Person {
name: string;
age: number;
location: string;
}
type PersonWithouLocation = Pick<Person, 'name' | 'age'>
// => type PersonWithouLocation = {
// name: string;
// age: number;
// }
複製代碼
那如何爲 _.pick
加上類型呢?
declare function pick<T extends object, K extends keyof T>(object: T, paths?: K[]): Pick<T, K> var object = { 'a': 1, 'b': '2', 'c': 3 };
const result = pick(object, ['a', 'c']);
// const result: Pick<{
// 'a': number;
// 'b': string;
// 'c': number;
// }, "a" | "b">
複製代碼
使用泛型和 Pick
,咱們就獲得了一個類型安全的 pick
方法
在 Typescript 3.5 版本才引入 Omit
類型。
Omit
做爲 Pick
的反操做,咱們想獲得和上面 Pick 相同的操做只須要對類型的鍵值取反便可。
interface Person {
name: string;
age: number;
location: string;
}
type PersonWithouLocation = Omit<Person, 'location'>
// 結果是
// type PersonWithouLocation = {
// name: string;
// age: number;
// }
複製代碼
Omit 在標準庫中的定義是
/** * Construct a type with the properties of T except for those in type K. */
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
複製代碼
讓咱們看看 Omit 的實現,就是 Pick 出 T 中 keyof T 和 K 的差集組成新的類型。也就是說消除了第二個參數中爲鍵值的類型組成新的類型。
Omit 實現中的第二個泛型參數爲何不是 K extends keyof T
,而是使用的 K extends keyof any
呢?這樣 IDE 就不能在第二個參數幫助補全了,這是個人一個疑問。在 Stack Overflow 上有找到 key 值多是 string | number | symbol
三種類型。
那如何爲 _.omit
加上類型呢?
declare function omit<T extends object, K extends keyof T>(object: T, paths: K[]): Omit<T, K>
const result = omit(object, ['a', 'b']);
// const result: Pick<{
// 'a': number;
// 'b': string;
// 'c': number;
// }, "c">
複製代碼
擴展一下,也許某些時候 omit
方法的第二個參數多是擴展運算符。例如
function omit(object, ...rest) {
// do omit
}
omit(object, 'a', 'b')
複製代碼
那這個時候的類型定義應該是這樣
declare function omit<T extends object, K extends keyof T>(object: T, ...paths: K[]): Omit<T, K> 複製代碼
infer 是 TS 的一個關鍵字,用於顯式類型推斷。與之相關的常見的兩個類型就是 ReturnType
和 Parameters
function getInt(a: string) {
return parseInt(a);
}
type A = ReturnType<typeof getInt>; // => number
複製代碼
再這個例子中,咱們先須要使用 typeof
關鍵字獲取函數的類型定義,也就是 (a: string) => number
, 而後再將其做爲泛型參數傳入 ReturnType
。這樣就能從一個函數聲明獲得它的返回類型,這能夠在寫代碼的時候減小咱們一些心智負擔,可以很是靈活的獲取類型。
ReturnType
在標準庫中的實現是
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
複製代碼
對這個實現的解釋是,若是類型 T 是擴展自函數類型,那麼返回值就是 infer 關鍵字推斷出的 R 類型,若是不是,就返回 any
類型。
根據這個實現咱們還能實現一個高階類型用於推斷函數參數。
type ParametersType<T> = T extends (...args: infer K) => any ? K : any;
複製代碼
使用這個類型推斷出的類型是參數元組類型。
其實這個類型在標準庫中的的實現是這樣的,只不過這個標準庫實現了限定了泛型入參的類型必須爲函數類型。
/** * Obtain the parameters of a function type in a tuple */
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
複製代碼
lib.es5.d.ts
中還實現了不少相似的高階類型。可參考 官網
常見的 Mapped types 都很簡單, 能夠簡單過一下
假設咱們有以下類型。
type User = {
readonly id: number;
name: string;
}
複製代碼
type Readonly<Type> = {readonly [key in keyof Type ]: Type[key]};
type ReadonlyUser = Readonly<User>;
複製代碼
Readonly
的實現就是讓泛型的每個 key 都用 readonly
關鍵字標註,這樣就能獲得每個 key 都是 readonly 只讀的類型了。
/** * Make all properties in T optional */
type Partial<T> = {
[P in keyof T]?: T[P];
};
複製代碼
同理使用 ?
讓每個 key 均可選。
type BlogPost = {
id: number;
title: string;
description?: string;
}
type PartialBlogPost = Partial<BlogPost>;
/* => type PartialBlogPost { id?: number | undefined; title?: string / undefined; description?: string / undefined; } */
複製代碼
/** * Make all properties in T required */
type Required<T> = {
[P in keyof T]-?: T[P];
};
複製代碼
Required
類型和上兩個差很少,須要注意的是 -
這個修飾符,它意味着去除後面的可選修飾符 ?
, 那就是每個 key 都是必需。同理也存在 +
修飾符。
type BlogPost = {
id: number;
title: string;
description?: string;
}
type RequiredBlogPost = Required<BlogPost>;
/* => type RequiredBlogPost { id: number; title: string; description: string; } */
複製代碼
在剛開始學習 TS 的時候,Record
的實現和使用場景讓我比較困惑。
/** * Construct a type with a set of properties K of type T */
type Record<K extends keyof any, T> = {
[P in K]: T;
};
複製代碼
type ExportFormat = "jsonMiniDiary" | "md" | "pdf" | "txtDayOne";
const fileExtensions: Record<ExportFormat, string> = {
jsonMiniDiary: "json",
md: "md",
pdf: "pdf",
txtDayOne: "txt",
};
複製代碼
這是一段來自 real world 的代碼。經過這段代碼,能看到 Record
可以幫助咱們得到類型安全的對象聲明。
待持續更新,TS 的類型世界真複雜。