<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【TS】1470- 總結(jié) TS 類型體操的9種類型運(yùn)算、4種類型套路

          共 17646字,需瀏覽 36分鐘

           ·

          2022-10-15 02:33

          今天給大家分享的主題是一起來做類型體操。

          主要分為 4 個(gè)部分進(jìn)行介紹:

          1. 類型體操的背景,通過背景了解為什么要在項(xiàng)目中加入類型體操;
          2. 了解類型體操的主要類型、運(yùn)算邏輯、和類型套路;
          3. 類型體操實(shí)踐,解析 TypeScript 內(nèi)置高級類型,手寫 ParseQueryString 復(fù)雜類型;
          4. 小結(jié),綜上分享,沉淀結(jié)論。

          一、背景

          在背景章節(jié)介紹的是什么是類型,什么是類型安全,怎么實(shí)現(xiàn)類型安全,什么是類型體操?

          以了解類型體操的意義。

          1. 什么是類型?

          了解什么是類型之前,先來介紹兩個(gè)概念:

          • 不同類型變量占據(jù)的內(nèi)存大小不同

          boolean 類型的變量會(huì)分配 4 個(gè)字節(jié)的內(nèi)存,而 number 類型的變量則會(huì)分配 8 個(gè)字節(jié)的內(nèi)存,給變量聲明了不同的類型就代表了會(huì)占據(jù)不同的內(nèi)存空間。

          • 不同類型變量可做的操作不同

          number 類型可以做加減乘除等運(yùn)算,boolean 就不可以,復(fù)合類型中不同類型的對象可用的方法不同,比如 Date 和 RegExp,變量的類型不同代表可以對該變量做的操作就不同。

          綜上,可以得到一個(gè)簡單的結(jié)論就是,類型就是編程語言提供對不同內(nèi)容的抽象定義

          2. 什么是類型安全?

          了解了類型的概念后,那么,什么是類型安全呢?

          一個(gè)簡單的定義就是,類型安全就是只做該類型允許的操作。比如對于 boolean 類型,不允許加減乘除運(yùn)算,只允許賦值 true、false。

          當(dāng)我們能做到類型安全時(shí),可以大量的減少代碼中潛在的問題,大量提高代碼質(zhì)量。

          3. 怎么實(shí)現(xiàn)類型安全?

          那么,怎么做到類型安全?

          這里介紹兩種類型檢查機(jī)制,分別是動(dòng)態(tài)類型檢查和靜態(tài)類型檢查。

          3.1 動(dòng)態(tài)類型檢查

          Javascript 就是典型的動(dòng)態(tài)類型檢查,它在編譯時(shí),沒有類型信息,到運(yùn)行時(shí)才檢查,導(dǎo)致很多隱藏 bug。

          3.2 靜態(tài)類型檢查

          TypeScript 作為 Javascript 的超集,采用的是靜態(tài)類型檢查,在編譯時(shí)就有類型信息,檢查類型問題,減少運(yùn)行時(shí)的潛在問題。

          4. 什么是類型體操

          上面介紹了類型的一些定義,都是大家熟悉的一些關(guān)于類型的背景介紹,這一章節(jié)回歸到本次分享的主題概念,類型體操。

          了解類型體操前,先介紹 3 種類型系統(tǒng)。

          4.1 簡單類型系統(tǒng)

          簡單類型系統(tǒng),它只基于聲明的類型做檢查,比如一個(gè)加法函數(shù),可以加整數(shù)也可以加小數(shù),但在簡單類型系統(tǒng)中,需要聲明 2 個(gè)函數(shù)來做這件事情。

          int add(int a, int b) {
              return a + b
          }

          double add(double a, double b) {
              return a + b
          }

          4.2 泛型類型系統(tǒng)

          泛型類型系統(tǒng),它支持類型參數(shù),通過給參數(shù)傳參,可以動(dòng)態(tài)定義類型,讓類型更加靈活。

          T add<T>(T a, T b) {
              return a + b
          }

          add(12)
          add(1.12.2)

          但是在一些需要類型參數(shù)邏輯運(yùn)算的場景就不適用了,比如一個(gè)返回對象某個(gè)屬性值的函數(shù)類型。

          function getPropValue<T>(obj: T, key{
            return obj[key]
          }

          4.3 類型編程系統(tǒng)

          類型編程系統(tǒng),它不僅支持類型參數(shù),還能給類型參數(shù)做各種邏輯運(yùn)算,比如上面提到的返回對象某個(gè)屬性值的函數(shù)類型,可以通過 keyof、T[K] 來邏輯運(yùn)算得到函數(shù)類型。

          function getPropValue<
            T extends object
            Key extends keyof T
          >(obj: T, key: Key): T[Key
          {
            return obj[key]
          }

          總結(jié)上述,類型體操就是類型編程,對類型參數(shù)做各種邏輯運(yùn)算,以產(chǎn)生新的類型

          之所以稱之為體操,是因?yàn)樗膹?fù)雜度,右側(cè)是一個(gè)解析參數(shù)的函數(shù)類型,里面用到了很多復(fù)雜的邏輯運(yùn)算,等先介紹了類型編程的運(yùn)算方法后,再來解析這個(gè)類型的實(shí)現(xiàn)。

          二、了解類型體操

          熟悉完類型體操的概念后,再來繼續(xù)了解類型體操有哪些類型,支持哪些運(yùn)算邏輯,有哪些運(yùn)算套路。

          1. 有哪些類型

          類型體操的主要類型列舉在圖中。TypeScript 復(fù)用了 JS 的基礎(chǔ)類型和復(fù)合類型,并新增元組(Tuple)、接口(Interface)、枚舉(Enum)等類型,這些類型在日常開發(fā)過程中類型聲明應(yīng)該都很常用,不做贅述。

          // 元組(Tuple)就是元素個(gè)數(shù)和類型固定的數(shù)組類型
          type Tuple = [number, string];

          // 接口(Interface)可以描述函數(shù)、對象、構(gòu)造器的結(jié)構(gòu):
          interface IPerson {
              name: string;
              age: number;
          }

          class Person implements IPerson {
              name: string;
              age: number;
          }

          const obj: IPerson = {
              name'aa',
              age18
          }

          // 枚舉(Enum)是一系列值的復(fù)合:
          enum Transpiler {
              Babel = 'babel',
              Postcss = 'postcss',
              Terser = 'terser',
              Prettier = 'prettier',
              TypeScriptCompiler = 'tsc'
          }

          const transpiler = Transpiler.TypeScriptCompiler;

          2. 運(yùn)算邏輯

          重點(diǎn)介紹的是類型編程支持的運(yùn)算邏輯。

          TypeScript 支持條件、推導(dǎo)、聯(lián)合、交叉、對聯(lián)合類型做映射等 9 種運(yùn)算邏輯。

          • 條件:T extends U ? X : Y

          條件判斷和 js 邏輯相同,都是如果滿足條件就返回 a 否則返回 b。

          // 條件:extends ? :
          // 如果 T 是 2 的子類型,那么類型是 true,否則類型是 false。
          type isTwo<T> = T extends 2 ? true : false;
          // false
          type res = isTwo<1>;
          • 約束:extends

          通過約束語法 extends 限制類型。

          // 通過 T extends Length 約束了 T 的類型,必須是包含 length 屬性,且 length 的類型必須是 number。
          interface Length {
              length: number
          }

          function fn1<T extends Length>(arg: T): number{
              return arg.length
          }
          • 推導(dǎo):infer

          推導(dǎo)則是類似 js 的正則匹配,都滿足公式條件時(shí),可以提取公式中的變量,直接返回或者再次加工都可以。

          // 推導(dǎo):infer
          // 提取元組類型的第一個(gè)元素:
          // extends 約束類型參數(shù)只能是數(shù)組類型,因?yàn)椴恢罃?shù)組元素的具體類型,所以用 unknown。
          // extends 判斷類型參數(shù) T 是不是 [infer F, ...infer R] 的子類型,如果是就返回 F 變量,如果不是就不返回
          type First<T extends unknown[]> = T extends [infer F, ...infer R] ? F : never;
          // 1
          type res2 = First<[123]>;
          • 聯(lián)合:|

          聯(lián)合代表可以是幾個(gè)類型之一。

          type Union = 1 | 2 | 3
          • 交叉:&

          交叉代表對類型做合并。

          type ObjType = { a: number } & { c: boolean }
          • 索引查詢:keyof T

          keyof 用于獲取某種類型的所有鍵,其返回值是聯(lián)合類型。

          // const a: 'name' | 'age' = 'name'
          const a: keyof {
              name: string,
              age: number
          } = 'name'
          • 索引訪問:T[K]

          T[K] 用于訪問索引,得到索引對應(yīng)的值的聯(lián)合類型。

          interface I3 {
            name: string,
            age: number
          }

          type T6 = I3[keyof I3] // string | number

          • 索引遍歷: in

          in 用于遍歷聯(lián)合類型。

          const obj = {
              name'tj',
              age11
          }

          type T5 = {
              [P in keyof typeof obj]: any
          }

          /*
          {
            name: any,
            age: any
          }
          */

          • 索引重映射: as

          as 用于修改映射類型的 key。

          // 通過索引查詢 keyof,索引訪問 t[k],索引遍歷 in,索引重映射 as,返回全新的 key、value 構(gòu)成的新的映射類型
          type MapType<T> = {
              [
              Key in keyof T
              as `${Key & string}${Key & string}${Key & string}`
              ]: [T[Key], T[Key], T[Key]]
          }
          // {
          //     aaa: [1, 1, 1];
          //     bbb: [2, 2, 2];
          // }
          type res3 = MapType<{ a1b2 }>

          3. 運(yùn)算套路

          根據(jù)上面介紹的 9 種運(yùn)算邏輯,我總結(jié)了 4 個(gè)類型套路。

          • 模式匹配做提取;
          • 重新構(gòu)造做變換;
          • 遞歸復(fù)用做循環(huán);
          • 數(shù)組長度做計(jì)數(shù)。

          3.1 模式匹配做提取

          第一個(gè)類型套路是模式匹配做提取。

          模式匹配做提取的意思是通過類型 extends 一個(gè)模式類型,把需要提取的部分放到通過 infer 聲明的局部變量里。

          舉個(gè)例子,用模式匹配提取函數(shù)參數(shù)類型。

          type GetParameters<Func extends Function> =
              Func extends (...args: infer Args) => unknown ? Args : never;

          type ParametersResult = GetParameters<(name: string, age: number) => string>

          首先用 extends 限制類型參數(shù)必須是 Function 類型。

          然后用 extends 為 參數(shù)類型匹配公式,當(dāng)滿足公式時(shí),提取公式中的變量 Args。

          實(shí)現(xiàn)函數(shù)參數(shù)類型的提取。

          3.2 重新構(gòu)造做變換

          第二個(gè)類型套路是重新構(gòu)造做變換。

          重新構(gòu)造做變換的意思是想要變化就需要重新構(gòu)造新的類型,并且可以在構(gòu)造新類型的過程中對原類型做一些過濾和變換。

          比如實(shí)現(xiàn)一個(gè)字符串類型的重新構(gòu)造。

          type CapitalizeStr<Str extends string> =
              Str extends `${infer First}${infer Rest}`
              ? `${Uppercase<First>}${Rest}` : Str;

          type CapitalizeResult = CapitalizeStr<'tang'>

          首先限制參數(shù)類型必須是字符串類型。

          然后用 extends 為參數(shù)類型匹配公式,提取公式中的變量 First Rest,并通過 Uppercase 封裝。

          實(shí)現(xiàn)了首字母大寫的字符串字面量類型。

          3.3 遞歸復(fù)用做循環(huán)

          第三個(gè)類型套路是遞歸復(fù)用做循環(huán)。

          TypeScript 本身不支持循環(huán),但是可以通過遞歸完成不確定數(shù)量的類型編程,達(dá)到循環(huán)的效果。

          比如通過遞歸實(shí)現(xiàn)數(shù)組類型反轉(zhuǎn)。

          type ReverseArr<Arr extends unknown[]> =
              Arr extends [infer First, ...infer Rest]
              ? [...ReverseArr<Rest>, First]
              : Arr;


          type ReverseArrResult = ReverseArr<[12345]>

          首先限制參數(shù)必須是數(shù)組類型。

          然后用 extends 匹配公式,如果滿足條件,則調(diào)用自身,否則直接返回。

          實(shí)現(xiàn)了一個(gè)數(shù)組反轉(zhuǎn)類型。

          3.4 數(shù)組長度做計(jì)數(shù)

          第四個(gè)類型套路是數(shù)組長度做計(jì)數(shù)。

          類型編程本身是不支持做加減乘除運(yùn)算的,但是可以通過遞歸構(gòu)造指定長度的數(shù)組,然后取數(shù)組長度的方式來完成數(shù)值的加減乘除。

          比如通過數(shù)組長度實(shí)現(xiàn)類型編程的加法運(yùn)算。

          type BuildArray<
              Length extends number,
              Ele = unknown,
              Arr extends unknown[] = []
              > = Arr['length'] extends Length
              ? Arr
              : BuildArray<Length, Ele, [...Arr, Ele]>;

          type Add<Num1 extends number, Num2 extends number> =
              [...BuildArray<Num1>, ...BuildArray<Num2>]['length'];


          type AddResult = Add<3225>

          首先通過遞歸創(chuàng)建一個(gè)可以生成任意長度的數(shù)組類型

          然后創(chuàng)建一個(gè)加法類型,通過數(shù)組的長度來實(shí)現(xiàn)加法運(yùn)算。

          三、類型體操實(shí)踐

          分享的第三部分是類型體操實(shí)踐。

          前面分享了類型體操的概念及常用的運(yùn)算邏輯。

          下面我們就用這些運(yùn)算邏輯來解析 TypeScript 內(nèi)置的高級類型。

          1. 解析 TypeScript 內(nèi)置高級類型

          • partial 把索引變?yōu)榭蛇x

          通過 in 操作符遍歷索引,為所有索引添加 ?前綴實(shí)現(xiàn)把索引變?yōu)榭蛇x的新的映射類型。

          type TPartial<T> = {
              [P in keyof T]?: T[P];
          };

          type PartialRes = TPartial<{ name'aa'age18 }>
          • Required 把索引變?yōu)楸剡x

          通過 in 操作符遍歷索引,為所有索引刪除 ?前綴實(shí)現(xiàn)把索引變?yōu)楸剡x的新的映射類型。

          type TRequired<T> = {
              [P in keyof T]-?: T[P]
          }

          type RequiredRes = TRequired<{ name?: 'aa', age?: 18 }>
          • Readonly 把索引變?yōu)橹蛔x

          通過 in 操作符遍歷索引,為所有索引添加 readonly 前綴實(shí)現(xiàn)把索引變?yōu)橹蛔x的新的映射類型。

          type TReadonly<T> = {
              readonly [P in keyof T]: T[P]
          }

          type ReadonlyRes = TReadonly<{ name?: 'aa', age?: 18 }>
          • Pick 保留過濾索引

          首先限制第二個(gè)參數(shù)必須是對象的 key 值,然后通過 in 操作符遍歷第二個(gè)參數(shù),生成新的映射類型實(shí)現(xiàn)。

          type TPick<T, K extends keyof T> = {
              [P in K]: T[P]
          }

          type PickRes = TPick<{ name?: 'aa', age?: 18 }, 'name'>
          • Record 創(chuàng)建映射類型

          通過 in 操作符遍歷聯(lián)合類型 K,創(chuàng)建新的映射類型。

          type TRecord<K extends keyof any, T> = {
              [P in K]: T
          }

          type RecordRes = TRecord<'aa' | 'bb', string>
          • Exclude 刪除聯(lián)合類型的一部分

          通過 extends 操作符,判斷參數(shù) 1 能否賦值給參數(shù) 2,如果可以則返回 never,以此刪除聯(lián)合類型的一部分。

          type TExclude<T, U> = T extends U ? never : T

          type ExcludeRes = TExclude<'aa' | 'bb''aa'>
          • Extract 保留聯(lián)合類型的一部分

          和 Exclude 邏輯相反,判斷參數(shù) 1 能否賦值給參數(shù) 2,如果不可以則返回 never,以此保留聯(lián)合類型的一部分。

          type TExtract<T, U> = T extends U ? T : never

          type ExtractRes = TExtract<'aa' | 'bb''aa'>
          • Omit 刪除過濾索引

          通過高級類型 Pick、Exclude 組合,刪除過濾索引。

          type TOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

          type OmitRes = TOmit<{ name'aa'age18 }, 'name'>
          • Awaited 用于獲取 Promise 的 valueType

          通過遞歸來獲取未知層級的 Promise 的 value 類型。

          type TAwaited<T> =
              T extends null | undefined
                  ? T
                  : T extends object & { then(onfulfilled: infer F): any }
                      ? F extends ((value: infer V, ...args: any) => any)
                          ? Awaited<V>
                          : never
                      : T;


          type AwaitedRes = TAwaited<Promise<Promise<Promise<string>>>>

          還有非常多高級類型,實(shí)現(xiàn)思路和上面介紹的類型套路大多一致,這里不一一贅述。

          2. 解析 ParseQueryString 復(fù)雜類型

          重點(diǎn)解析的是在背景章節(jié)介紹類型體操復(fù)雜度,舉例說明的解析字符串參數(shù)的函數(shù)類型。

          如圖示 demo 所示,這個(gè)函數(shù)是用于將指定字符串格式解析為對象格式。

          function parseQueryString1(queryStr{
            if (!queryStr || !queryStr.length) {
              return {}
            }
            const queryObj = {}
            const items = queryStr.split('&')
            items.forEach((item) => {
              const [key, value] = item.split('=')
              if (queryObj[key]) {
                if (Array.isArray(queryObj[key])) {
                  queryObj[key].push(value)
                } else {
                  queryObj[key] = [queryObj[key], value]
                }
              } else {
                queryObj[key] = value
              }
            })
            return queryObj
          }

          比如獲取字符串 a=1&b=2 中 a 的值。

          常用的類型聲明方式如下圖所示:

          function parseQueryString1(queryStr: string): Record<stringany{
            if (!queryStr || !queryStr.length) {
              return {}
            }
            const queryObj = {}
            const items = queryStr.split('&')
            items.forEach((item) => {
              const [key, value] = item.split('=')
              if (queryObj[key]) {
                if (Array.isArray(queryObj[key])) {
                  queryObj[key].push(value)
                } else {
                  queryObj[key] = [queryObj[key], value]
                }
              } else {
                queryObj[key] = value
              }
            })
            return queryObj
          }

          參數(shù)類型為 string,返回類型為 Record<string, any>,這時(shí)看到,res1.a 類型為 any,那么有沒有辦法,準(zhǔn)確的知道 a 的類型是字面量類型 1 呢?

          下面就通過類型體操的方式,來重寫解析字符串參數(shù)的函數(shù)類型。

          首先限制參數(shù)類型是 string 類型,然后為參數(shù)匹配公式 a&b,如果滿足公式,將 a 解析為 key value 的映射類型,將 b 遞歸 ParseQueryString 類型,繼續(xù)解析,直到不再滿足 a&b 公式。

          最后,就可以得到一個(gè)精準(zhǔn)的函數(shù)返回類型,res.a = 1


          type ParseParam<Param extends string> =
              Param extends `${infer Key}=${infer Value}`
                  ? {
                      [K in Key]: Value
                  } : Record<string, any>;

          type MergeParams<
              OneParam extends Record<string, any>,
              OtherParam extends Record<string, any>
          > = {
            readonly [Key in keyof OneParam | keyof OtherParam]:
              Key extends keyof OneParam
                  ? OneParam[Key]
                  : Key extends keyof OtherParam
                      ? OtherParam[Key]
                      : never
          }

          type ParseQueryString<Str extends string> =
              Str extends `${infer Param}&${infer Rest}`
                  ? MergeParams<ParseParam<Param>, ParseQueryString<Rest>>
                  : ParseParam<Str>;
          function parseQueryString<Str extends string>(queryStr: Str): ParseQueryString<Str{
              if (!queryStr || !queryStr.length) {
                  return {} as any;
              }
              const queryObj = {} as any;
              const items = queryStr.split('&');
              items.forEach(item => {
                  const [key, value] = item.split('=');
                  if (queryObj[key]) {
                      if(Array.isArray(queryObj[key])) {
                          queryObj[key].push(value);
                      } else {
                          queryObj[key] = [queryObj[key], value]
                      }
                  } else {
                      queryObj[key] = value;
                  }
              });
              return queryObj as any;
          }


          const res = parseQueryString('a=1&b=2&c=3');

          console.log(res.a) // type 1

          四、小結(jié)

          綜上分享,從 3 個(gè)方面介紹了類型體操。

          • 第一點(diǎn)是類型體操背景,了解了什么是類型,什么是類型安全,怎么實(shí)現(xiàn)類型安全;

          • 第二點(diǎn)是熟悉類型體操的主要類型、支持的邏輯運(yùn)算,并總結(jié)了 4 個(gè)類型套路;

          • 第三點(diǎn)是類型體操實(shí)踐,解析了 TypeScript 內(nèi)置高級類型的實(shí)現(xiàn),并手寫了一些復(fù)雜函數(shù)類型。

          從中我們了解到需要?jiǎng)討B(tài)生成類型的場景,必然是要用類型編程做一些運(yùn)算,即使有的場景下可以不用類型編程,但是使用類型編程能夠有更精準(zhǔn)的類型提示和檢查,減少代碼中潛在的問題。

          參考資料+源碼

          這里列舉了本次分享的參考資料及示例源碼,歡迎大家擴(kuò)展閱讀:

          [1]

          參考資料《TypeScript 類型體操通關(guān)秘籍》: https://juejin.cn/book/7047524421182947366

          [2]

          示例源碼: https://github.com/jiaozitang/ts-demo


          往期回顧

          #

          如何使用 TypeScript 開發(fā) React 函數(shù)式組件?

          #

          11 個(gè)需要避免的 React 錯(cuò)誤用法

          #

          6 個(gè) Vue3 開發(fā)必備的 VSCode 插件

          #

          3 款非常實(shí)用的 Node.js 版本管理工具

          #

          6 個(gè)你必須明白 Vue3 的 ref 和 reactive 問題

          #

          6 個(gè)意想不到的 JavaScript 問題

          #

          試著換個(gè)角度理解低代碼平臺(tái)設(shè)計(jì)的本質(zhì)

          瀏覽 62
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  奶大灬大灬大灬硬灬爽灬无码视频 | 激情白浆| 欧美日韩午夜 | 阿宾 麻豆 | 日韩婷婷 |