typescript?type類型使用梳理總結(jié)
引言
最近準(zhǔn)備開始刷type-challenges,因此先梳理一下ts類型相關(guān)知識點(diǎn)。
遺漏知識點(diǎn)總結(jié)
ts
的type符合圖靈完備。這意味著ts
類型包含循環(huán)、判斷等一系列基本操作。
類型集合
類型應(yīng)當(dāng)作集合來看,其中:
unknown
是全集,包含所有類型,是所有類型的父級
之前在泛型中會寫Comp<T extends unknown>
看來是有些多此一舉了...
never
是空集,是所有類型的子集
ts的設(shè)計(jì)符合里氏替換原則,即子集可以替換所有父級,這意味著:
interface Parent { id: string; } // 符合里氏替換原則,不會報(bào)錯(cuò) interface Child extends Parent { id: "childIdA"|"childIdB"; // "childIdA"|"childIdB"是string的子集 key: number; // Parent中沒有key,該屬性是Parent的拓展 } // 不符合里氏替換原則,報(bào)錯(cuò) interface ChildError extends Parent { id: number; // number不是string的子集 } type A = {a: string}; type B = {a: "A"|"a", b: number}; let a: A = {a: "xxx"}; let b: B = {a: "a", b: 2}; // 符合B是A的子集,該賦值符合里氏替換原則, 反之 b=a 會報(bào)錯(cuò) a = b;
將TS的type看作編程語言
既然ts
的type是圖靈完備的,那么它自然可以完成一切計(jì)算,因此可以看作是一門語言
函數(shù)
在類型語境中,可將泛型看做是函數(shù)
type Fn<T> = T; // 看作 const Fn = val => val;
循環(huán)
經(jīng)常可以在代碼中看到如下寫法:
type R = "A" | "B" type T = { [k in R]: k }
這里[k in R]
可看做循環(huán)for(const k in R){}
借助此特性,可以完成如下操作:
type MyPick<T, K extends keyof T> = { [k in K]: T[k] } type A = MyPick<{a: string, b: number, c: boolean}, "a"|"c">
當(dāng)然這種循環(huán)只針對特定類型("a"|"b"
這種),稍微復(fù)雜點(diǎn)的循環(huán)還是得通過遞歸實(shí)現(xiàn),例子的話,后面用ts type
實(shí)現(xiàn)四則運(yùn)算時(shí)會用到。
條件語句
type
支持三元運(yùn)算符,這個(gè)沒什么好說的。。
需要注意的是,type
里沒有等號,但可以用extends代替等號
type Equal5<T> = T extends 5 ? true : false;
來點(diǎn)練習(xí)
// eq1: 實(shí)現(xiàn)`GetProp<Obj, K>`(獲取Obj[K]類型) type GetProp<Obj, K> = K extends keyof Obj ? Obj[K] : undefined; // eq2: 實(shí)現(xiàn)getName<User> type GetName<User> = GetProp<User, "name">
extends
也常和infer
一起用于類型推斷。
例如
// 實(shí)現(xiàn)KeyOf type KeyOf<T> = T extends {[k in infer U]: unknown} ? U : never; // 實(shí)現(xiàn)ValueOf type ValueOf<T> = T extends { [k in keyof T]: infer U } ? U : never; // 實(shí)現(xiàn)Parameters和ReturnType type FuncBase<F, C extends "params"|"return"> = F extends (...params: infer P) => infer R ? (C extends "params" ? P : R) : never; type Params<F> = FuncBase<F, "params">; type Return<F> = FuncBase<F, "return">;
賦值
賦值部分參看前面類型集合章節(jié),賦值要遵循里氏替換原則。
條件語句章節(jié)提到了infer
,或許可以用infer
實(shí)現(xiàn)解構(gòu)賦值。
// 需要實(shí)現(xiàn)類似js的const {name, ...extra} = user,求extra。(其實(shí)就是Omit方法) type MyPick<Obj, T extends keyof Obj> = {[k in T]: Obj[k]}; type MyExclude<Obj, T extends keyof Obj> = keyof Obj extends T|(infer U extends keyof Obj) ? U : never; type MyOmit<Obj, T extends keyof Obj> = MyPick<Obj, MyExclude<Obj, T>>; // 實(shí)現(xiàn)數(shù)組的解構(gòu)賦值const [A, ...extra] = arr; type GetArrBase<T extends unknown[], C extends "first"|"rest"> = T extends [infer First, ...infer Rest] ? (C extends "first"?First: Rest): never; type GetFirst<T extends unknown[]> = GetArrBase<T, "first">; type GetRest<T extends unknown[]> = GetArrBase<T, "rest">;
對象
type
的對象可以類比js
中的對象,使用方法如下,注意最后一個(gè)例子
type Obj = { name: string; age: 20; } Obj["name"] // string; Obj.age // Error Obj["age"] // 20 Obj['name'|'age'] // string | 20 , 這個(gè)特性很重要?。?!
利用這個(gè)特性,可以完成如下功能
interface Test { a: string; b: number; c: boolean; } // 之前不知道這個(gè)特性時(shí),用infer也能達(dá)到同樣的效果,但實(shí)現(xiàn)不如這個(gè)直觀 type ValueOf<T> = T[keyof T]; type R = ValueOf<Test>;
數(shù)組
ts
中的數(shù)組分為Array
數(shù)組和Tuple
元組。
Array
數(shù)組是諸如string[]
的寫法,類似java
或其他語言的數(shù)組。
Tuple
元組更像是js
中的數(shù)組,寫法是[string, number, boolean]
這種。
(注:js
中不存在真正意義上的數(shù)組,數(shù)組是在內(nèi)存上開辟連續(xù)空間,每個(gè)單元格所占內(nèi)存都一樣,在js
中,數(shù)組寫成['a', 5555, {a: 1}]
都沒問題,顯然在實(shí)現(xiàn)上不是真正的開辟了連續(xù)內(nèi)存空間,應(yīng)該是用鏈表模擬的,為了解決鏈表本身查詢慢的問題,應(yīng)該是采用了跳表或者紅黑樹的方式組織的?)
數(shù)組中需要注意的點(diǎn)如下:
type A = string[]; type B = [string, number, boolean]; // =========================分割線========================== // 重點(diǎn)注意!?。? A[0]; // string A[1]; // string B[0]; // string B[1]; // number; B[0|2]; // string|boolean // 注意以下寫法,為什么可以這么寫,因?yàn)閚umber是所有數(shù)字的集合 A[number]; // string B[number]; // string | number | boolean A["length"]; // number B["length"]; // 3 // ts數(shù)組同樣可以像js數(shù)組那樣展開 type Spread<T extends unknown[]> = [...T] Spread<[1,2,3]> // [1,2,3]
根據(jù)以上特性,很容易實(shí)現(xiàn)以下練習(xí):
// eq1: 實(shí)現(xiàn) `ItemOf`方法(獲取數(shù)組中項(xiàng)的類型) type ItemOf<T extends unknown[]> = T[number]; // 之前不知道這個(gè)特性時(shí),用infer實(shí)現(xiàn)的代碼如下 type ItemOfByinfer<T> = T extends (infer N)[] ? N : never; // eq2:實(shí)現(xiàn)`Append`方法 type Append<T extends unknown[], Ele> = [...T, Ele]; // eq3: 實(shí)現(xiàn)返回?cái)?shù)組length+1 // ts雖然無法實(shí)現(xiàn)加減運(yùn)算,但可以通過模擬生成對應(yīng)新類型,返回其屬性,從而模擬加減運(yùn)算 type LengthAddOne<T extends unknown[]> = [unknown, ...T]["length"];
四則運(yùn)算
運(yùn)算加減依賴于元組長度,因此先定義一些基本方法,注意..由于是依賴元組長度,因此無法算負(fù)數(shù)和小數(shù),只能算正整數(shù)...
(注:雖然無法計(jì)算負(fù)數(shù)和小數(shù),但ts的type依舊是圖靈完備的,位運(yùn)算也只是01的運(yùn)算,負(fù)數(shù)和小數(shù)都是一堆01的定義,比如把10000看做0,且最后兩位是小數(shù),那么9999就是 -0.01)
// 返回Push后的數(shù)組 type Append<T extends unknown[], E = unknown> = [...T, U]; // 同理,返回Pop后的數(shù)組代碼如下,暫時(shí)用不到 // type RemoveTop<T extends unknown[]> = T extends [...(infer U), unknown] ? U : never; type Tuple<N extends number, Arr extends unknown[] = []> = Arr["length"] extends N ? Arr : Tuple<N, Append<Arr>>
有了這些基本方法,先實(shí)現(xiàn)加法和減法
type Add<A extends number, B extends number> = [...Tuple<A>, ...Tuple<B>]["length"]; type Subtract<A extends number, B extends number> = Tuple<A> extends [...Tuple<B>, ...(infer U)] ? U["length"] : never;
乘法的話,A*B
就是A
個(gè)B
相加,簡易版乘法如下,思路不難,但直接用Add和Subtract封裝,很多寫法都提示嵌套太深。。
注意,這里用于統(tǒng)計(jì)和的參數(shù)S以元組表示,因?yàn)樗羞\(yùn)算都是以元組為基準(zhǔn),S用數(shù)字表示會先轉(zhuǎn)元組再轉(zhuǎn)數(shù)字,來來回回開銷比較大。
type MultipleBase<A extends number, B extends number, S extends unknown[] = []> = B extends 0 ? S["length"] : MultipleBase<A, Subtract<B, 1>, [...S, ...Tuple<A>]>;
乘法還有優(yōu)化的空間,例如2*100
,直接用這個(gè)算的是100個(gè)2相加,時(shí)間復(fù)雜度不如100*2
,而計(jì)算這么優(yōu)化的前提是,實(shí)現(xiàn)BiggerThan
方法。
type BiggerThan<A extends number, B extends number> = Tuple<A> extends [...Tuple<B>, ...infer U] ? (U["length"] extends (never|0) ? false : true): false; // 優(yōu)化后的乘法如下 type Mutiple<A extends number, B extends number> = BiggerThan<A, B> extends true ? MultipleBase<A, B> : MultipleBase<B, A>;
有了BiggerThan
,除法也好說,例如a/b
,判定b*2、b*3...b*n
和A的大小就行。
同乘法的實(shí)現(xiàn),用于統(tǒng)計(jì)的參數(shù)R為元組。。
type Divide<A extends number, B extends number, R extends unknown[] = []> = BiggerThan<B, A> extends true ? R["length"] : Divide<Subtract<A,B>, B, Append<R>>;
至此,四則運(yùn)算實(shí)現(xiàn)完畢。
以上就是typescript type類型使用梳理總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于typescript type類型的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
typescript難學(xué)嗎?前端有必要學(xué)?該怎么學(xué)typescript
TypeScript代碼與?JavaScript?代碼有非常高的兼容性,無門檻,你把?JS?代碼改為?TS?就可以運(yùn)行。TypeScript?應(yīng)該不會脫離?JavaScript?成為獨(dú)立的語言。學(xué)習(xí)?TypeScript?應(yīng)該主要指的是學(xué)習(xí)它的類型系統(tǒng)。2022-12-12TypeScript十大排序算法插入排序?qū)崿F(xiàn)示例詳解
這篇文章主要為大家介紹了TypeScript十大排序算法插入排序?qū)崿F(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Typescript?extends?關(guān)鍵字繼承類型約束及條件類型判斷實(shí)現(xiàn)示例解析
這篇文章主要介紹了Typescript?extends?關(guān)鍵字繼承類型約束及條件類型判斷實(shí)現(xiàn)示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08TypeScript十大排序算法之選擇排序?qū)崿F(xiàn)示例詳解
這篇文章主要為大家介紹了TypeScript十大排序算法之選擇排序?qū)崿F(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02