TypeScript 学习笔记(四)--- 泛型(Generics)
(注明:目前泛型理解不夠清晰,先照葫蘆畫瓢,整理一個粗略版本,后續會再次補充)
一、泛型的概念
? 在TS中,泛型(Generics)是一種創建可復用代碼的方式,類似于代碼組件的概念。具體來說,就是在定義接口、函數或類的時候,不預先指定參數、返回值的類型,而是在使用時,根據具體情況來指定相應的類型。
? 舉個例子,定義一個函數 identity ,該函數有一個參數,函數內部就是實現了將參數原樣返回,那么代碼如下:
const identity = (arg) => arg;? 然后我們給代碼加上類型聲明,并使函數的入參和返回值都應該可以是任意類型:
type idBoolean = (arg: boolean) => boolean; type idNumber = (arg: number) => number; type idString = (arg: string) => string; ...? 上面的實現辦法就是,TS有多少種類型就,寫多少行代碼,對應不同的類型簽名,但這樣會使代碼冗余,難以維護。還有一種辦法是使用any類型,不過這樣會喪失類型檢查功能,得不償失:
const identity = (arg: any) => any;// 哪怕返回值并沒有這個方法,但依舊不會報錯 identity("string").length; // ok identity("string").toFixed(2); // ok identity(null).toString(); // ok? 如果我們想要實現:根據傳遞的值得類型進行推導,并根據推導出來的類型,進行類型檢查,比如我傳了一個string,但是返回值使用量number的方法,那么就應該報錯,這種效果,就需要借助泛型來實現:
function identity<T>(arg: T): T { return arg; }? 上面代碼中的T,表示Type,是一個抽象類型,只有在調用函數時,才會確認具體的類型,這樣就能適用于不同類型的數據。調用函數時,先把類型傳遞給 <T>中的T,然后再鏈式傳遞給參數類型和返回值類型。
? 在泛型的<>中,我們可以定義任何數量的類型變量:
function identity <T, U>(value: T, message: U) : T { console.log(message); return value; } // 調用時,使用 <> 定義好對應類型變量的類型 像傳參一樣 一一對應 console.log(identity<Number, string>(68, "Semlinker"));? 在調用函數時,也可以省略使用 <> 顯式設定類型,編譯器可以自動根據我們的參數類型來得知對應類型:
function identity <T, U>(value: T, message: U) : T { console.log(message); return value; } // 省略 <> console.log(identity(68, "Semlinker"));二、泛型約束
? 如果我們不對泛型的類型變量進行約束,那么其類型理論上是可以是任何類型,那這樣只能使用所有類型共有的屬性或方法,否則就會報錯:
function trace<T>(arg: T): T { console.log(arg.size); // 報錯 Error: Property 'size doesn't exist on type 'T' return arg; }? 所以我們要對其進行約束,方式就是通過 extends 繼承一個接口:
interface Sizeable {size: number; } function trace<T extends Sizeable>(arg: T): T {console.log(arg.size); return arg; }三、泛型工具類
1、概念
? 為了方便開發,TS內置了一些常用的工具類型,比如 Partial、Required等。不過在學習這些內置工具類型之前,我們需要先去了解一些基礎的TS的特性。
2、基礎特性
① typeof
? typeof 在 JS 中是用來判斷值的類型,在 TS 中,typeof 不僅能判斷基本數據類型,還可以判斷接口類型:
interface Person { name: string; age: number; } const sem: Person = { name: "semlinker", age: 30 }; console.log(typeof sem) // Person // 獲取接口類型 并賦值給 類型變量 type Sem = typeof sem; // type Sem = Person const lolo: Sem = { name: "lolo", age: 5 }? 以及獲取對象的屬性類型結構,作為一個類型賦值給類型變量:
const Message = { name: "jimmy", age: 18, address: { province: '四川', city: '成都' } } type message = typeof Message; /*type message = { name: string; age: number; address: { province: string; city: string; }; } */// 函數對象也可以 function toArray(x: number): Array<number> {return [x]; } type Func = typeof toArray; // type Func = (x: number) => number[]② keyof
? keyof 操作符 是 TS 2.1 版本引入的,用來獲取某種類型的所有鍵,其返回類型是一個聯合類型:
interface Person { name: string; age: number; }// 獲取接口類型的鍵 type K1 = keyof Person; // "name" | "age" // 支持獲取數組類型的鍵 type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join" // 支持普通數據類型的鍵 let K1: keyof boolean; // let K1: "valueOf" let K2: keyof number; // let K2: "toString" | "toFixed" | "toExponential" | ... let K3: keyof symbol; // let K1: "valueOf"③ in
? in 用來遍歷枚舉類型,比如聯合類型:
type Keys = "a" | "b" | "c"type Obj = { [p in Keys]: any } // -> { a: any, b: any, c: any }④ infer(不清晰)
? 在條件類型語句中,可以用 infer 聲明一個類型變量并使用:
type ReturnType<T> = T extends ( ...args: any[] ) => infer R ? R : any;? 以上代碼中 infer R 就是聲明一個變量來承載傳入函數簽名的返回值類型,簡單說就是用它取到函數返回值的類型方便之后使用。
⑤ extends
? extends 主要用于給泛型添加約束:
interface Lengthwise { length: number; } function loggingIdentity<T extends Lengthwise>(arg: T): T { console.log(arg.length); return arg; }⑥ 映射類型
? 根據已經存在的類型創建出新的類型,那被創建的新類型,我們就稱為映射類型:
// 已存在的接口類型 interface TestInterface{ name:string, age:number } // 處理已存在類型 并生成新類型的方法 type OptionalTestInterface<T> = { [p in keyof T]+?:T[p] } // 獲取新類型 type newTestInterface = OptionalTestInterface<TestInterface>3、內置工具類型
① Partial
? Partial<T> 結合keyof 和 in ,將某類型的所有屬性變成可選:
// 工具類型 Partial 內部原理 type Partial<T> = { [P in keyof T]?: T[P]; }; // 已存在的接口類型 interface UserInfo { id: string; name: string; } // 使用Partial type NewUserInfo = Partial<UserInfo>; const xiaoming: NewUserInfo = { name: 'xiaoming' }? 但 Partial 也有它的局限性:只支持對第一層屬性進行處理,如果要處理的類型中有嵌套屬性,則第二層往下就不會再處理。但如果想要對嵌套屬性也進行處理,可以自己實現:
type DeepPartial<T> = { // 如果是 object,則遞歸類型 [U in keyof T]?: T[U] extends object ? DeepPartial<T[U]> : T[U] };type PartialedWindow = DeepPartial<T>; // 現在T上所有屬性都變成了可選啦② Required
? Required將某類型的所有屬性變成必選:
// Required 內部原理 type Required<T> = { [P in keyof T]-?: T[P] // 其中 -? 是代表移除 ? 這個 modifier 的標識 };③ Readonly
? Readonly<T> 的作用是將某個類型所有屬性變為只讀屬性,也就意味著這些屬性不能被重新賦值:
// Readonly 內部原理 type Readonly<T> = { readonly [P in keyof T]: T[P]; }; // 使用 interface Todo {title: string; } const todo: Readonly<Todo> = { title: "Delete inactive users" }; todo.title = "Hello"; // Error: cannot reassign a readonly property④ Pick
? Pick 從某個類型中挑出部分屬性出來:
// 原理 type Pick<T, K extends keyof T> = { [P in K]: T[P]; }; // 使用 interface Todo { title: string; description: string;completed: boolean; }type TodoPreview = Pick<Todo, "title" | "completed">;const todo: TodoPreview = { title: "Clean room", completed: false, };⑤ ReturnType
? ReturnType 用來得到一個函數的返回值類型:
// 原理 type ReturnType<T extends (...args: any[]) => any> = T extends ( ...args: any[] ) => infer R // infer在這里用于提取函數類型的返回值類型? R : any; // 使用 type Func = (value: number) => string; // ReturnType獲取到 Func 的返回值類型為 string,所以,foo 也就只能被賦值為字符串了。 const foo: ReturnType<Func> = "1";⑥ Exclude
? Exclude<T, U> 的作用是將某個類型中屬于另一個的類型移除掉,簡單來說就是取不同的部分:
// 原理 type Exclude<T, U> = T extends U ? never : T; // 使用 type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c" type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"⑦ Extract
? Extract<T, U> 的作用是從 T 中提取出 U,簡單來說就是取相同的部分:
// 原理 type Extract<T, U> = T extends U ? T : never; // 使用 type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a" type T1 = Extract<string | number | (() => void), Function>; // () =>void⑧ NonNullable
? NonNullable<T> 的作用是用來過濾類型中的 null 及 undefined 類型:
// 原理 type NonNullable<T> = T extendsnull | undefined ? never : T; // 使用 type T0 = NonNullable<string | number | undefined>; // string | number type T1 = NonNullable<string[] | null | undefined>; // string[]⑨ Record、Omit、Parameters等
總結
以上是生活随笔為你收集整理的TypeScript 学习笔记(四)--- 泛型(Generics)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对接天猫精灵X1 (https 的申请)
- 下一篇: 【nowcoder 224882】牛牛和