成為優(yōu)秀的TS體操高手 之 TS 類型體操前置知識(shí)儲(chǔ)備
TS 類型體操前置知識(shí)儲(chǔ)備
如果你正在學(xué)習(xí) TS,可是像我一樣僅僅停留在定義類型,定義 interface/type 的層面的話, 這份體操類型練習(xí)題一定不要錯(cuò)過(guò) !type-challenges[1]
寫這篇文章的時(shí)候我只做完了體操類型的中等級(jí)別的題目(還有 2-3 道其實(shí)沒完全解出來(lái))
簡(jiǎn)單搭建一下做題環(huán)境
我喜歡做一題拷貝一題,所以我 clone 了一份 type-challenges[2]。然后在 type-challenges 新建了一個(gè)文件夾(my-type-challenges),專門用來(lái)做 TS 體操
每次想做題的時(shí)候都只需要記住編號(hào),比如第一題 13-helloword 。只需要輸入就可以了
npm run copy 13
復(fù)制代碼
詳細(xì)的腳本在 Jioho/my-type-challenges[3]。寫了腳本后,就可以愉快的寫代碼了
體操入門基本語(yǔ)法
以下的內(nèi)容純粹是個(gè)人的見解,如有說(shuō)錯(cuò)或理解不到位的地方請(qǐng)指出
雖然說(shuō) TS 圖靈完備(如果一個(gè)計(jì)算系統(tǒng)可以計(jì)算每一個(gè)圖靈可計(jì)算函數(shù),那么這個(gè)系統(tǒng)就是圖靈完備的)
不過(guò) TS 并沒有那么多語(yǔ)法,比如 if,switch,return 之類的。
TS 用的最多的都是三目運(yùn)算符,判斷相等,數(shù)組,遞歸等一些技巧后面都會(huì)一一介紹到
TS 內(nèi)置的高級(jí)類型(內(nèi)置的體操)
入門第一步一定要看文檔(雖然我也不愛看,看不懂),不過(guò)還是需要有基礎(chǔ)的了解 utility-types[4]
目前的內(nèi)置方法就如下:
| --- | --- | --- | --- |
|---|---|---|---|
Partial<Type> | Required<Type> | Readonly<Type> | Record<Keys, Type> |
Pick<Type, Keys> | Omit<Type, Keys> | Exclude<UnionType, ExcludedMembers> | Extract<Type, Union> |
NonNullable<Type> | Parameters<Type> | ConstructorParameters<Type> | ReturnType<Type> |
InstanceType<Type> | ThisParameterType<Type> | OmitThisParameter<Type> | ThisType<Type> |
Uppercase<StringType> | Lowercase<StringType> | Capitalize<StringType> | Uncapitalize<StringType> |
比如拿一個(gè)后續(xù)可能用的比較多的來(lái)說(shuō)一下:
Pick[5] 方法
官網(wǎng)的 demo:
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
復(fù)制代碼
Pick 的作用就是從一個(gè)對(duì)象中,挑選需要的字段出來(lái),比如從 TODO 里面只取出 title 和 completed
如果沒有類型體操的話,TodoPreview 還得額外定義一個(gè)類型
interface TodoPreview {
title:string;
completed: boolean;
}
復(fù)制代碼
之前有一個(gè)練手項(xiàng)目我就是遇到了這樣的情況,明明都是同一個(gè)對(duì)象上的字段,為了適應(yīng)不同場(chǎng)景,硬是定義了一大堆的附屬字段,關(guān)鍵是如果想改一個(gè)類型,還得全部跟著改。。如果有 Pick 就一了百了,想要啥就 pick 啥
想看 Pick 實(shí)現(xiàn)也很簡(jiǎn)單,隨便起一個(gè) type,按住 ctrl+點(diǎn)擊 Pick 就可以看到 Pick 實(shí)現(xiàn)
代碼實(shí)現(xiàn)如下:
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
復(fù)制代碼
不過(guò)第一次看到這代碼, extends , keyof , in 這都是什么東西?這就是這篇文章存在的意義。往下看
判斷常用 extends 關(guān)鍵字
extends 翻譯是延伸的意思
在 TS 充當(dāng)了 if 和 在 XXX 范圍內(nèi) 的一個(gè)作用。extends 后面通常就要接三目運(yùn)算符了(也有例外)
舉個(gè) ?? 子
type mytype1 = 1 extends string ? true : false // false
type mytype2 = '1' extends string ? true : false // true
type mytype2_1 = string extends '1' ? true : false // false
type mytype3 = mytype1 extends any ? 1 : 2 // 1
type mytype4 = [90] extends unknown[] ? true : false // true
type mytype5 = [90] extends string[] ? true : false // false
復(fù)制代碼
上面簡(jiǎn)單的舉了幾個(gè)例子,簡(jiǎn)單解釋下:
1 是否屬于 string 類型 得到 false 因?yàn)?1 是數(shù)字類型 '1' 屬于是 string 類型的 三目運(yùn)算符判斷為 true string 類型 屬于 '1' 肯定是 false 的,string 類型范圍比'1'更大,不在屬于的范疇了 mytype1 屬于 any 類型是對(duì)的,因?yàn)?any 包含一切~ [90] 是一個(gè)數(shù)值型的數(shù)組,屬于一個(gè) unknown未知類型的數(shù)組中,這個(gè)也是對(duì)的,因?yàn)槲粗愋鸵矔?huì)包含數(shù)字類型而 [90] 就不屬于 string[]的范疇了
extends 也有不接三目運(yùn)算符的時(shí)候
比如寫一個(gè)限制 string 類型的 push 方法
type StrPush<T extends string[], U extends string> = [...T, U]
type myarr = StrPush<[1, 2, 3], 4>
復(fù)制代碼
比如在這個(gè)例子中,直接會(huì)報(bào)錯(cuò)。因?yàn)檫€沒進(jìn)到 StrPush 的判斷中,T 泛型就已經(jīng)被約束為 strinig 類型了,U 也被約束為 string 類型

extends 總結(jié)
extends 在 TS 的 函數(shù)體中的時(shí)候起到的是判斷范疇的一個(gè)作用在一些特殊位置 (比如接收泛型的時(shí)候,在函數(shù)運(yùn)算過(guò)程中斷言變量類型的時(shí)候)起到的是一個(gè) 約束類型 的作用
循環(huán)對(duì)象的鍵 keyof 和 in
只要了解 keyof 和 in 之后,Pick 的所有關(guān)鍵字也就講解完了,體操練習(xí)中簡(jiǎn)單的第一題 MyPick 也就完成了
還是上面的 demo 代碼
/**
* From T, pick a set of properties whose keys are in the union K
*/
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,
};
復(fù)制代碼
extends 上面講過(guò),屬于范疇判斷/約束類型,在泛型定義<>里面明顯就是為了約束類型
keyof 的作用可以理解為 把一個(gè)對(duì)象中的所有 鍵 提取出來(lái)。
直接用 keyof 提取一個(gè)對(duì)象看看效果:
type TodoKeys = keyof Todo
//type TodoKeys = 'title' | 'description' | 'completed'
type testkeys = keyof {[k:number]: any; name:string}
// type testkeys = number | "name"
// 特殊的例子
type Mapish = { [k: string]: boolean; name:string };
type M = keyof Mapish;
// type M = string | number
復(fù)制代碼
上面的幾個(gè)例子中,第一個(gè)是最好理解的,提取所有的 鍵
第二個(gè)案例中,k 作為未知的內(nèi)容,提取 number 作為 key 的范圍,加上 "name" 所以就得出 number | "name"
特殊的例子也是官網(wǎng)的例子,為啥會(huì)有個(gè) number 類型?
原話:Note that in this example, M is string | number — this is because JavaScript object keys are always coerced to a string, so obj[0] is always the same as obj["0"].
翻譯:請(qǐng)注意,在此示例中,M string | number — 這是因?yàn)?JavaScript 對(duì)象鍵總是被強(qiáng)制轉(zhuǎn)換為字符串,所以 obj[0] 總是與 obj["0"] 相同
結(jié)合 demo ,K extends keyof T 意思也就是說(shuō),K 參數(shù)的取值范圍只能在 Todo 的鍵中取('title' | 'description' | 'completed'),限制為字符串,并且是這 3 個(gè)鍵中的其中一個(gè)/多個(gè),只能少,不能多
如果是下面這種
type Mapish = { [k: string]: boolean; name:string };
// K extends keyof Mapish;
復(fù)制代碼
K 的范圍則是 string | number 類型(因?yàn)闆]有具體的鍵名,所以只能不限制具體的鍵名,只限制類型)
像這種用 | 拼起來(lái)的(或類型)規(guī)范點(diǎn)叫做 unio 聯(lián)合數(shù)據(jù)類型,想要循環(huán)這些數(shù)據(jù),可以用到 in 關(guān)鍵字
回到 demo 的講解
Pick 中 泛型 T 傳入的是 Todo 類型 K extends keyof T:K 限制為 Todo 的鍵值(傳入的是 "title" | "completed" 符合要求,因?yàn)橹荒苌俨荒芏嗦铮?/section>P in k 可以理解為 循環(huán) P 會(huì)依次被賦值為 title 然后賦值 completed T[P] 的意思和 JS 的[]取值一樣,獲取 T['title'] => string 和 T['completed'] => completed {} 會(huì)包裹循環(huán)出來(lái)的結(jié)果
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type TodoPreview = Pick<Todo, "title" | "completed">;
// TodoPreview = {title:string,completed:boolean}
復(fù)制代碼
Pick 的實(shí)現(xiàn)就完成了。[P in K] 這個(gè)循環(huán)和我們 JS 常規(guī)的寫法很不一樣,需要消化一下,其他應(yīng)該都好理解
以此內(nèi)推,完成 Readonly 眾所周知 readonly 只需要在鍵值前面加上 readonly 參數(shù)即可
實(shí)現(xiàn)如下:keyof 寫到了中括號(hào)里面,這個(gè)是允許的,這些關(guān)鍵字無(wú)論寫在哪里只要符合規(guī)范都 OK,比如 MyReadonly2
type MyReadonly<T> = {
readonly [P in keyof T]: T[P]
}
// 這個(gè)是畫蛇添足的,因?yàn)镻肯定是屬于keyof T的,就是從keyof T中循環(huán)出來(lái)的
// 不過(guò)證明keyof不僅僅在 <> 中能用
type MyReadonly2<T> = {
readonly [P in keyof T]: P extends keyof T ? T[P] : never
}
復(fù)制代碼
小拓展:
'name' extends keyof T ? true : false也能判斷 T 這個(gè)泛型對(duì)象中有沒有 name 這個(gè)屬性
keyof 和 in 小結(jié)
keyof是為了拿到一個(gè)對(duì)象中的所有的鍵名,當(dāng)鍵名是一個(gè)類型的時(shí)候,則會(huì)全部被升級(jí)為對(duì)應(yīng)的 類型in則是為了循環(huán) unio 聯(lián)合類型數(shù)據(jù)的,多數(shù)都用于循環(huán)重新生成一個(gè)對(duì)象
JS 轉(zhuǎn) TS -- typeof 關(guān)鍵字
typeof 作為從 js 世界轉(zhuǎn)換為 ts 世界的內(nèi)容。
假設(shè)一個(gè)場(chǎng)景,我們使用一個(gè) obj 對(duì)象作為數(shù)據(jù)映射的存儲(chǔ),比如使用一個(gè) map,存儲(chǔ)狀態(tài)碼返回對(duì)應(yīng)的 msg:
const statusMap = {
200: '操作成功',
404: '內(nèi)容找不到',
500: '操作失敗',
10001: '登錄失效'
}
var status1 = 200
console.log(statusMap[status1]) // 操作成功
var status2 = 10001
console.log(statusMap[status2]) // 登錄失效
復(fù)制代碼
類似上面的場(chǎng)景,那這時(shí)候statusMap中[] 中間的值肯定只有 200,404,500,10001 才符合要求,在 TS 層面自然我們就要約定 status 在這個(gè)范圍中
如果不用 typeof ,我們可以會(huì)寫出這樣的 TS:
type Status = 200 | 404 | 500 | 10001
var status1: Status = 200
console.log(statusMap[status1]) // 操作成功
var status2: Status = 10001
console.log(statusMap[status2]) // 登錄失效
var status3: Status = 301 // 報(bào)紅 (Type '301' is not assignable to type 'Status')
復(fù)制代碼
這時(shí)候假如我們?cè)谛略隽藥讉€(gè)鍵值,那 TS 還得在同步跟著改一次(太麻煩了),用上 typeof
const statusMap = {
200: '操作成功',
404: '內(nèi)容找不到',
500: '操作失敗',
10001: '登錄失效'
}
type MyStatusMap = typeof statusMap
// 這時(shí)候 MyStatus 的值會(huì)變成
// type MyStatus = {
// 200: string;
// 404: string;
// 500: string;
// 10001: string;
// }
// 然后接上keyof關(guān)鍵字,提取對(duì)象的鍵名
type MyStatus = keyof MyStatusMap
// type MyStatus = 200 | 404 | 500 | 10001
// 上面的2步可以簡(jiǎn)寫一步到位
type MyStatus2 = keyof typeof statusMap
// type MyStatus2 = 200 | 404 | 500 | 10001
復(fù)制代碼
這樣只需要我們改動(dòng) JS 的內(nèi)容,TS 將會(huì)自動(dòng)獲取對(duì)新的對(duì)象。返回對(duì)象后還不夠,因?yàn)槲覀兩厦嫦爰s束的是傳入的鍵值(想獲取鍵值,剛好上面學(xué)習(xí)了 keyof 關(guān)鍵字),就能動(dòng)態(tài)獲取所有符合規(guī)范的鍵值了!
typeof 小結(jié)
typeof 是一個(gè)可以動(dòng)態(tài)把 JS 的對(duì)象轉(zhuǎn)換為 TS 的關(guān)鍵字。
不過(guò)也有限制場(chǎng)景,那就是轉(zhuǎn)換的前提是這部分 JS 是已固定的內(nèi)容。就好比例子中的一個(gè)對(duì)象映射,那是固定的內(nèi)容,然后讓 TS 去推導(dǎo)
而且打包后的代碼是不可能存在 TS 的,如果想實(shí)現(xiàn)后端接口動(dòng)態(tài)返回內(nèi)容在用 typeof ,這是實(shí)現(xiàn)不了的
數(shù)組和字符串的循環(huán) 推斷類型 infer
infer 應(yīng)用場(chǎng)景非常多
簡(jiǎn)單一句話概括 infer 只是一個(gè) 占位 的工具,我就站在這個(gè)位置,至于這個(gè)位置是什么內(nèi)容 infer 并不關(guān)心,可以留給后面的程序去判斷
用簡(jiǎn)單題的 00014-easy-first[6] 來(lái)講解一下。實(shí)現(xiàn)一個(gè) First 工具類型
通常獲取第一個(gè)元素,我們想到的就是 T[0] 當(dāng)然在 TS 這個(gè)語(yǔ)法是可以行得通的
可以在測(cè)試用例中有一項(xiàng)
Equal<First<[]>, never> // 使用 T[0] 的話這個(gè)會(huì)報(bào)錯(cuò),因?yàn)?nbsp;First<[]> 返回的是 undefined
復(fù)制代碼
也就是說(shuō)當(dāng)這個(gè)元素非指定聲明為 undefined 時(shí),在 TS 多數(shù)都是要用 never 代替
正確答案如下:
type First<T extends any[]> = T extends [infer F,...infer Rest] ? F : never
復(fù)制代碼
簡(jiǎn)單的說(shuō)一下
infer必須在 TS 函數(shù)運(yùn)算過(guò)程中使用(在定義泛型的<>中不能使用,而 extends 就可以)infer 可以配合 ... 進(jìn)行運(yùn)算 T extends [infer F,...infer Rest]表達(dá)的意思就是 T extends [F,...Rest 剩余的值]。T 肯定是存在一個(gè)屬性 F的數(shù)組,...Rest是剩下的內(nèi)容,可有可無(wú)...infer Rest就是把除了 F 之外的元素在歸集為一個(gè)數(shù)組(這個(gè)是 ES6 的知識(shí)了)infer F的意思就是,我拿 F 在這個(gè)數(shù)組里面 占位 ,數(shù)組的第一項(xiàng)的內(nèi)容,就是被 F 占了回歸到題目,我們要拿的也正是第一項(xiàng),所以直接 return F 類型 如果 T 是一個(gè)空數(shù)組,那么 extends 那一步就都判斷不通過(guò),自然返回的就是 never,符合測(cè)試用例的要求。
infer 的其他妙用
infer 還能遍歷字符串
比如起一個(gè) 字符串切割為數(shù)組的需求:
type Split<T extends string, U extends unknown[] = []> =
T extends `${infer F}${infer Rest}` ? Split<Rest, [...U, F]> : U
type testSplit = Split<'123456'>
// type testSplit = ["1", "2", "3", "4", "5", "6"]
復(fù)制代碼
其中 ${infer F}${infer Rest} 的意思就是,F(xiàn) 占第一個(gè)字符,Rest 占剩下的字符,因?yàn)樵谧址胁淮嬖?code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">...的概念,所以 Rest 基本上就是占據(jù)了剩下的字符了
像這樣的一個(gè)測(cè)試?yán)?,一共?3 個(gè)占位符,
type testInfer<T extends string> = T extends `${infer F}${infer S}${infer R}` ? [F, S, R] : T
type testInfer1 = testInfer<'123456'>
// 按照占位符的特性,前面F和S分別占據(jù)2個(gè)字符,剩余的都給R占去了
// type testInfer1 = ["1", "2", "3456"]
// 稍作改動(dòng),在S占位符后面添加一個(gè)5
type testInfer2<T extends string> = T extends `${infer F}${infer S}5${infer R}` ? [F, S, R] : T
type testInfer3 = testInfer<'123456'>
// F 占第一個(gè)字符 = 1
// S 占據(jù)2-4,因?yàn)樵赗之前有一個(gè)5,所以S代表了第二個(gè)字符開始到5的所有字符
// 那么R就是從5開始,到末尾,所以得出的結(jié)果如下:
// type testInfer1 = ["1", "234", "6"]
復(fù)制代碼
后面的習(xí)題還有很多會(huì)用到 infer ,所以先了解好 infer 占位 的特性就好
infer 小結(jié)
infer 相當(dāng)于一個(gè)占位置的關(guān)鍵字,把占下來(lái)的位置復(fù)制給對(duì)應(yīng)的運(yùn)算變量。
其中對(duì)于數(shù)組或者其他的類型來(lái)說(shuō),還能用 ... 把所有的位置歸結(jié)起來(lái)形成一個(gè)數(shù)組
對(duì)于字符串這種不存在 ... 拓展運(yùn)算符的來(lái)說(shuō),只要前面占了一個(gè)位置,剩下的字符就會(huì)被第二個(gè)占位符全部代替
數(shù)組的用法
數(shù)組也是 TS 體操的一個(gè)很重要的特性,因?yàn)樵?TS 體操中并沒有加減法的概念,實(shí)際運(yùn)算中少不了加減法的操作,包括獲取長(zhǎng)度之類的。
所以數(shù)組還充當(dāng)了 計(jì)數(shù)器 的作用。關(guān)于數(shù)組的計(jì)數(shù)器還有一個(gè)非常有用的技巧,有用到我覺得可以單獨(dú)再起一個(gè)文章細(xì)細(xì)分析,下面就先簡(jiǎn)單的介紹一下數(shù)組的功能
先來(lái)一道簡(jiǎn)單題,學(xué)會(huì)數(shù)組的基本屬性和用法 00018-easy-tuple-length[7]
題目給出 2 個(gè)數(shù)組,用了 as const。需要求出這 2 個(gè)數(shù)組的長(zhǎng)度
const tesla = ['tesla', 'model 3', 'model X', 'model Y'] as const
const spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT'] as const
type teslaLength = Length<typeof tesla>
type spaceXLength = Length<typeof spaceX>
// 答案:
type Length<T extends readonly any[]> = T['length']
復(fù)制代碼
是不是很簡(jiǎn)單?!T['length'] 完事~
所以數(shù)組一個(gè)很重要的特性就是,他有 length 屬性。
想用
length屬性的前提:T extends any[]T 類型的范圍,肯定是一個(gè)數(shù)組,any[]或者unknown[]。都行,反正是數(shù)組,就有length屬性。
基于這個(gè)求長(zhǎng)度的問(wèn)題,延伸一下,求 2 個(gè)數(shù)組合并后的長(zhǎng)度
type MergeArray<T extends readonly any[],U extends readonly any[]> = [...T,...U]['length']
type arrLength = MergeArray<typeof tesla, typeof spaceX> // 9
復(fù)制代碼
稍微來(lái)點(diǎn)有意思的題目在感受一下數(shù)組的作用 難度為中等的題 05153-medium-indexof[8]
實(shí)現(xiàn)一個(gè) indexOf(原題還有另外一個(gè)需要注意的點(diǎn)就是判斷類型的時(shí)候需要注意的地方,我后面還會(huì)起文章來(lái)講解,現(xiàn)在先看最簡(jiǎn)單的實(shí)現(xiàn)):
既然要算索引位置,自然就涉及到了一個(gè) 計(jì)數(shù)器 的問(wèn)題,看下面的答案
type numberIndex = IndexOf<[1, 2, 3], 1> // 需要得到的答案是 0
type numberIndex2 = IndexOf<[1, 2, 3], 99> // 需要得到的答案是 -1
type numberIndex3 = IndexOf<[1, 2, 3], 1> // 需要得到的答案是 0
// 題目給出的初始模版(缺少計(jì)數(shù)器)
// type IndexOf<T extends unknown[],U> = any
// 對(duì)于這些缺參數(shù)的,我們完全可以自己補(bǔ)一個(gè)參數(shù),而且補(bǔ)充默認(rèn)值
type IndexOf2<T extends unknown[], U, C extends 1[] = []> =
T extends [infer F, ...infer Rest] ?
(F extends U ? C['length'] : IndexOf<Rest, U, [...C, 1]>) : -1
復(fù)制代碼
講解部分:
因?yàn)轭}目給出的模版缺少了一個(gè)計(jì)數(shù)器,我們可以補(bǔ)充一個(gè) C 變量,并且默認(rèn)賦值為 []。只要有默認(rèn)值,就非必填,非必填的話測(cè)試用例就不會(huì)報(bào)錯(cuò)了 T extends 和 infer 部分上面有講解過(guò),如果 T 已經(jīng)不滿足至少有一個(gè) F的時(shí)候,說(shuō)明 T 數(shù)組已經(jīng)空了,空了之后就說(shuō)明可以得出結(jié)果了T 為空了,還沒匹配到數(shù)據(jù),按 indexOf 的方法應(yīng)該是返回 -1 T 如果符合了至少有一個(gè) F變量的要求的話,判斷F與傳入的U數(shù)據(jù)相比,相同的話返回當(dāng)前計(jì)數(shù)器的長(zhǎng)度(也就是要計(jì)算的索引了)C['length']不符合的話,拿 Rest數(shù)組繼續(xù)循環(huán)(遞歸調(diào)用 IndexOf),與此同時(shí) 遞歸調(diào)用的時(shí)候 C 的入?yún)⒆兂闪?[...C,1]數(shù)組長(zhǎng)度遞增1[...C,1]的作用,就是保留原數(shù)組的內(nèi)容,在添加一個(gè) 1,使得原數(shù)組的 length + 1 計(jì)數(shù)器 效果達(dá)成
通過(guò)一個(gè)簡(jiǎn)單的拓展運(yùn)算符,加上一個(gè) 1 使得數(shù)組的長(zhǎng)度不斷的變化。達(dá)到計(jì)數(shù)器/加法運(yùn)算的效果
比如在第 4182 的題目中,需要實(shí)現(xiàn)一個(gè) 斐波那契數(shù)列。斐波那契數(shù)列的特性就是 n = (n-1)+(n-2)。如何實(shí)現(xiàn)這個(gè)加法?用代碼來(lái)說(shuō)就是 [...(N-1),...(N-2)]。(不理解沒關(guān)系,后面還有會(huì)詳細(xì)的文章來(lái)講)
數(shù)組小結(jié)
只要對(duì)應(yīng)的類型 extends [] 的話,就可以使用 ['length'] 屬性獲取長(zhǎng)度 數(shù)組在體操中不僅僅充當(dāng)了數(shù)組的作用,還充當(dāng)了 計(jì)數(shù)器 和 加法實(shí)現(xiàn) 的作用,用法千變?nèi)f化 數(shù)組的累加依賴于 遞歸方法的實(shí)現(xiàn),在每一次遞歸的過(guò)程中往新的方法里面?zhèn)魅胄碌拈L(zhǎng)度(不過(guò)遞歸容易造成內(nèi)存溢出,比如02257-medium-minusone[9]這一題)。需要一個(gè)更加高級(jí)的技巧處理
as 關(guān)鍵字
在上面講解數(shù)組的時(shí)候看到有 as const 的出現(xiàn),那就順便講講 as
在 TS 使用中,as 就是一個(gè) 斷言
假設(shè)這樣的一個(gè)場(chǎng)景,有一個(gè)變量 todo ,設(shè)置為了 Todo 類型,有對(duì)應(yīng)的屬性
然后某個(gè)函數(shù)的副作用,導(dǎo)致了我的 todo 變成了一個(gè) string 類型(實(shí)際代碼應(yīng)該規(guī)避這種副作用函數(shù))
but 事情就這么發(fā)生了,而且不能改,這時(shí)候的 todo 應(yīng)該是 string 類型。todo 還需要調(diào)用一個(gè)方法,需要傳入 stirng 類型的 function todoFn(str:string){} 。這時(shí)候直接傳入 todo 肯定會(huì)報(bào)錯(cuò),類型不符合
解決辦法就是 todoFn(todo as string)??聪路降拇a截圖會(huì)好理解一點(diǎn)

在我斷定了這個(gè)類型就是 xxx 類型的時(shí)候就能用
as關(guān)鍵字(當(dāng)然不推薦使用),盡可能還是用 TS 的類型推導(dǎo)
在 TS 的類型定義的時(shí)候,as 又有別的含義
比如說(shuō)這個(gè)
const teslaConst = ['tesla', 'model 3', 'model X', 'model Y'] as const
// const teslaConst: readonly ["tesla", "model 3", "model X", "model Y"]
// 用var變量和const推導(dǎo)是一樣的,不過(guò)會(huì)留下代碼隱患,不推薦
var teslaConst2 = ['tesla', 'model 3', 'model X', 'model Y'] as const
// var teslaConst: readonly ["tesla", "model 3", "model X", "model Y"]
const tesla = ['tesla', 'model 3', 'model X', 'model Y']
// const tesla: string[]
復(fù)制代碼
區(qū)別很明顯,as const 的會(huì)把所有的值拿出來(lái),而且變成 readonly。因?yàn)?const 確實(shí)是只讀的標(biāo)記。
不過(guò) TS 不吃 js 變量類型那一套,所以還得通過(guò)
as const來(lái)告訴 TS,我斷言這個(gè)就是一個(gè) const 數(shù)組了,里面的元素都不會(huì)改了,你可以遍歷這里面的值
沒用 as const 的只會(huì)認(rèn)為是個(gè) string[]的數(shù)組。這是一個(gè)很大的區(qū)別
最后
TS 類型體操前置知識(shí)儲(chǔ)備大概就介紹了extends,infer,typeof,keyof和in,數(shù)組的使用,as關(guān)鍵字
了解了這部分關(guān)鍵字作用之后,完成 TS 體操練習(xí)的中等難度的題目不在話下!(起碼完成 80%的題目沒得問(wèn)題),剩下的 20% 還需要學(xué)習(xí)更多的 TS 體操技巧
這種感覺就好像如果你要解開一道一元二次方程之前,你得學(xué)習(xí)加減乘除的用法和規(guī)則。上面介紹的就是加減乘除的入門規(guī)則,后面還要學(xué)習(xí)更加巧妙地技能完成更復(fù)雜的 TS 體操
感興趣的可以到主頁(yè)看看關(guān)于 TS 體操的其他文章
以上的知識(shí)點(diǎn)也是我作為一個(gè) TS 小白在摸索完中等題目后總結(jié)的一些筆記
如有說(shuō)錯(cuò)或理解不到位的地方請(qǐng)指出
關(guān)于本文
作者:Jioho
https://juejin.cn/post/7115789691810480135
