新聞中心
TS 類型體操小冊掘金排期到 4月份了,有點晚。。。

創(chuàng)新互聯(lián)建站專業(yè)為企業(yè)提供瑯琊網(wǎng)站建設(shè)、瑯琊做網(wǎng)站、瑯琊網(wǎng)站設(shè)計、瑯琊網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計與制作、瑯琊企業(yè)網(wǎng)站模板建站服務(wù),10多年瑯琊做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡(luò)服務(wù)。
所以,我把其中一個套路提出來作為文章發(fā)了,大家可以提前感受下,到時候也會設(shè)置為小冊的試讀章節(jié)。
這個套路叫做數(shù)組長度做計數(shù),就是用數(shù)組長度實現(xiàn)加減乘除、各種計數(shù),是六大套路里最騷的一個。
下面是正文(小冊原文):
套路四:數(shù)組長度做計數(shù)
TypeScript 類型系統(tǒng)不是圖靈完備,各種邏輯都能寫么,但好像沒發(fā)現(xiàn)數(shù)值相關(guān)的邏輯。
沒錯,數(shù)值相關(guān)的邏輯比較繞,被我單獨摘了出來,就是這節(jié)要講的內(nèi)容。
這是類型體操的第四個套路:數(shù)組長度做計數(shù)。
數(shù)組長度做計數(shù)
TypeScript 類型系統(tǒng)沒有加減乘除運算符,怎么做數(shù)值運算呢?
不知道大家有沒有注意到數(shù)組類型取 length 就是數(shù)值。
比如:
而數(shù)組類型我們是能構(gòu)造出來的,那么通過構(gòu)造不同長度的數(shù)組然后取 length,不就是數(shù)值的運算么?
TypeScript 類型系統(tǒng)中沒有加減乘除運算符,但是可以通過構(gòu)造不同的數(shù)組然后取 length 的方式來完成數(shù)值計算,把數(shù)值的加減乘除轉(zhuǎn)化為對數(shù)組的提取和構(gòu)造。
這點可以說是類型體操中最麻煩的一個點,需要思維做一些轉(zhuǎn)換,繞過這個彎來。
下面我們就來做一些真實的案例來掌握它吧。
數(shù)組長度實現(xiàn)加減乘除
Add
我們知道了數(shù)值計算要轉(zhuǎn)換為對數(shù)組類型的操作,那么加法的實現(xiàn)很容易想到:
構(gòu)造兩個數(shù)組,然后合并成一個,取 length。
比如 3 + 2,就是構(gòu)造一個長度為 3 的數(shù)組類型,再構(gòu)造一個長度為 2 的數(shù)組類型,然后合并成一個數(shù)組,取 length。
構(gòu)造多長的數(shù)組是不確定的,需要遞歸構(gòu)造,這個我們實現(xiàn)過:
type BuildArray<
Length extends number,
Ele = unknown,
Arr extends unknown[] = []
> = Arr['length'] extends Length
? Arr
: BuildArray;
類型參數(shù) Length 是要構(gòu)造的數(shù)組的長度。類型參數(shù) Ele 是數(shù)組元素,默認為 unknown。類型參數(shù) Arr 為構(gòu)造出的數(shù)組,默認是 []。
如果 Arr 的長度到達了 Length,就返回構(gòu)造出的 Arr,否則繼續(xù)遞歸構(gòu)造。
構(gòu)造數(shù)組實現(xiàn)了,那么基于它就能實現(xiàn)加法:
type Add=
[...BuildArray,...BuildArray ]['length'];
我們拿大一點的數(shù)測試下:
結(jié)果是對的。
就這樣,我們通過構(gòu)造一定長度的數(shù)組取 length 的方式實現(xiàn)了加法運算。
Subtract
加法是構(gòu)造數(shù)組,那減法怎么做呢?
減法是從數(shù)值中去掉一部分,很容易想到可以通過數(shù)組類型的提取來做。
比如 3 是 [unknown, unknown, unknown] 的數(shù)組類型,提取出 2 個元素之后,剩下的數(shù)組再取 length 就是 1。
所以減法的實現(xiàn)是這樣的:
type Subtract=
BuildArrayextends [...arr1: BuildArray , ...arr2: infer Rest]
? Rest['length']
: never;
類型參數(shù) Num1、Num2 分別是被減數(shù)和減數(shù),通過 extends 約束為 number。
構(gòu)造 Num1 長度的數(shù)組,通過模式匹配提取出 Num2 長度個元素,剩下的放到 infer 聲明的局部變量 Rest 里。
取 Rest 的長度返回,就是減法的結(jié)果。
就這樣,我們通過數(shù)組類型的提取實現(xiàn)了減法運算。
Multiply
我們把加法轉(zhuǎn)換為了數(shù)組構(gòu)造,把減法轉(zhuǎn)換為了數(shù)組提取。那乘法怎么做呢?
為了解釋乘法,我去翻了下小學(xué)教材,找到了這樣一張圖:
1 乘以 5 就相當(dāng)于 1 + 1 + 1 + 1 + 1,也就是說乘法就是多個加法結(jié)果的累加。
那么我們在加法的基礎(chǔ)上,多加一個參數(shù)來傳遞中間結(jié)果的數(shù)組,算完之后再取一次 length 就能實現(xiàn)乘法:
type Mutiply<
Num1 extends number,
Num2 extends number,
ResultArr extends unknown[] = []
> = Num2 extends 0 ? ResultArr['length']
: Mutiply, [...BuildArray , ...ResultArr]>;
類型參數(shù) Num1 和 Num2 分別是被加數(shù)和加數(shù)。
因為乘法是多個加法結(jié)果的累加,我們加了一個類型參數(shù) ResultArr 來保存中間結(jié)果,默認值是 [],相當(dāng)于從 0 開始加。
每加一次就把 Num2 減一,直到 Num2 為 0,就代表加完了。
加的過程就是往 ResultArr 數(shù)組中放 Num1 個元素。
這樣遞歸的進行累加,也就是遞歸的往 ResultArr 中放元素。
最后取 ResultArr 的 length 就是乘法的結(jié)果。
就這樣,我們通過遞歸的累加實現(xiàn)了乘法。
Divide
乘法是遞歸的累加,那減法不就是遞歸的累減么?
我再去翻了下小學(xué)教材,找到了這樣一張圖:
我們有 9 個蘋果,分給美羊羊 3 個,分給懶羊羊 3 個,分給沸羊羊 3 個,最后剩下 0 個。所以 9 / 3 = 3。
所以,除法的實現(xiàn)就是被減數(shù)不斷減去減數(shù),直到減為 0,記錄減了幾次就是結(jié)果。
也就是這樣的:
type Divide<
Num1 extends number,
Num2 extends number,
CountArr extends unknown[] = []
> = Num1 extends 0 ? CountArr['length']
: Divide, Num2, [unknown, ...CountArr]>;
類型參數(shù) Num1 和 Num2 分別是被減數(shù)和減數(shù)。
類型參數(shù) CountArr 是用來記錄減了幾次的累加數(shù)組。
如果 Num1 減到了 0 ,那么這時候減了幾次就是除法結(jié)果,也就是 CountArr['length']。
否則繼續(xù)遞歸的減,讓 Num1 減去 Num2,并且 CountArr 多加一個元素代表又減了一次。
這樣就實現(xiàn)了除法:
就這樣,我們通過遞歸的累減并記錄減了幾次實現(xiàn)了除法。
做完了加減乘除,我們再來做一些別的數(shù)值計算的類型體操。
數(shù)組長度實現(xiàn)計數(shù)
StrLen
數(shù)組長度可以取 length 來得到,但是字符串類型不能取 length,所以我們來實現(xiàn)一個求字符串長度的高級類型。
字符串長度不確定,明顯要用遞歸。每次取一個并計數(shù),直到取完,就是字符串長度。
type StrLen<
Str extends string,
CountArr extends unknown[] = []
> = Str extends `${string}${infer Rest}`
? StrLen
: CountArr['length']
類型參數(shù) Str 是待處理的字符串。類型參數(shù) CountArr 是做計數(shù)的數(shù)組,默認值 [] 代表從 0 開始。
每次通過模式匹配提取去掉一個字符之后的剩余字符串,并且往計數(shù)數(shù)組里多放入一個元素。遞歸進行取字符和計數(shù)。
如果模式匹配不滿足,代表計數(shù)結(jié)束,返回計數(shù)數(shù)組的長度 CountArr['length']。
這樣就能求出字符串長度:
GreaterThan
能夠做計數(shù)了,那也就能做兩個數(shù)值的比較。
我們往一個數(shù)組類型中不斷放入元素取長度,如果先到了 A,那就是 B 大,否則是 A 大:
type GreaterThan<
Num1 extends number,
Num2 extends number,
CountArr extends unknown[] = []
> = Num1 extends Num2
? false
: CountArr['length'] extends Num2
? true
: CountArr['length'] extends Num1
? false
: GreaterThan;
類型參數(shù) Num1 和 Num2 是待比較的兩個數(shù)。
類型參數(shù) CountArr 是計數(shù)用的,會不斷累加,默認值是 [] 代表從 0 開始。
如果 Num1 extends Num2 成立,代表相等,直接返回 false。
否則判斷計數(shù)數(shù)組的長度,如果先到了 Num2,那么就是 Num1 大,返回 true。
反之,如果先到了 Num1,那么就是 Num2 大,返回 false。
如果都沒到就往計數(shù)數(shù)組 CountArr 中放入一個元素,繼續(xù)遞歸。
這樣就實現(xiàn)了數(shù)值比較。
當(dāng) 3 和 4 比較時:
當(dāng) 6 和 4 比較時:
Fibonacci談到了數(shù)值運算,就不得不提起經(jīng)典的 Fibonacci 數(shù)列的計算。
Fibonacci
數(shù)列是 1、1、2、3、5、8、13、21、34、…… 這樣的數(shù)列,有當(dāng)前的數(shù)是前兩個數(shù)的和的規(guī)律。
F(0) = 1,F(xiàn)(1) = 1, F(n) = F(n - 1) + F(n - 2)(n ≥ 2,n ∈ N*)
也就是遞歸的加法,在 TypeScript 類型編程里用構(gòu)造數(shù)組來實現(xiàn)這種加法:
type FibonacciLoop<
PrevArr extends unknown[],
CurrentArr extends unknown[],
IndexArr extends unknown[] = [],
Num extends number = 1
> = IndexArr['length'] extends Num
? CurrentArr['length']
: FibonacciLoop
type Fibonacci= FibonacciLoop<[1], [], [], Num>;
類型參數(shù) PrevArr 是代表之前的累加值的數(shù)組。類型參數(shù) CurrentArr 時代表當(dāng)前數(shù)值的數(shù)組。
類型參數(shù) IndexArr 用于記錄 index,每次遞歸加一,默認值是 [],代表從 0 開始。
類型參數(shù) Num 代表求數(shù)列的第幾個數(shù)。
判斷當(dāng)前 index 也就是 IndexArr['length'] 是否到了 Num,到了就返回當(dāng)前的數(shù)值 CurrentArr['length']。
否則求出當(dāng)前 index 對應(yīng)的數(shù)值,用之前的數(shù)加上當(dāng)前的數(shù) [...PrevArr, ... CurrentArr]。
然后繼續(xù)遞歸,index + 1,也就是 [...IndexArr, unknown]。
這就是遞歸計算 Fibinacci 數(shù)列的數(shù)的過程。
可以正確的算出第 8 個數(shù)是 21:
總結(jié)
TypeScript 類型系統(tǒng)沒有加減乘除運算符,所以我們通過數(shù)組類型的構(gòu)造和提取,然后取長度的方式來實現(xiàn)數(shù)值運算。
我們通過構(gòu)造和提取數(shù)組類型實現(xiàn)了加減乘除,也實現(xiàn)了各種計數(shù)邏輯。
用數(shù)組長度做計數(shù)這一點是 TypeScript 類型體操中最麻煩的一個點,也是最容易讓新手困惑的一個點。
網(wǎng)頁名稱:TypeScript類型體操:數(shù)組長度實現(xiàn)數(shù)值運算
網(wǎng)頁路徑:http://fisionsoft.com.cn/article/dpjeicp.html


咨詢
建站咨詢
