<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>

          半小時(shí)快速上手 TypeScript 類型編程!(附手摸手實(shí)戰(zhàn)案例)

          共 25544字,需瀏覽 52分鐘

           ·

          2021-03-26 07:52

          1. Why

          在介紹什么叫 TypeScript 類型編程和為什么需要學(xué)習(xí) TypeScript 類型編程之前,我們先看一個(gè)例子,這里例子里包含一個(gè) promisify 的函數(shù),這個(gè)函數(shù)用于將 NodeJS 中 callback style 的函數(shù)轉(zhuǎn)換成 promise style 的函數(shù)。

          import * as fs from "fs";
          function promisify(fn{
            return function(...args{
              return new Promise((resolve, reject) => {
                fn(...args, (err, data) => {
                  if(err) {
                    return reject(err);
                  }
                  resolve(data);
                });
              });
            }
          }

          (async () => {
            let file = await promisify(fs.readFile)("./xxx.json");
          })();

          如果我們直接套用上述的代碼,那么 file 的類型和 promisify(fs.readFile)(...)(...) 的類型也會(huì)丟失,也就是我們有兩個(gè)目標(biāo):

          1. 我們需要知道 promisify(fs.readFile)(...) 這里能夠接受的類型。

          2. 我們需要知道 let file = await ... 這里 file 的類型。

          這個(gè)問題的答案在實(shí)戰(zhàn)演練環(huán)節(jié)會(huì)結(jié)合本文的內(nèi)容給出答案,如果你覺得這個(gè)問題簡單得很,那么恭喜你,你已經(jīng)具備本文將要介紹的大部分知識(shí)點(diǎn)。如何讓類似于 promisify這樣的函數(shù)保留類型信息是“體操”或者我稱之為類型編程的意義所在。

          2. 前言 (Preface)

          最近在國內(nèi)的前端圈流行一個(gè)名詞“TS 體操”,簡稱為“TC”,體操這個(gè)詞是從 Haskell 社區(qū)來的,本意就是高難度動(dòng)作,關(guān)于“體操”能夠?qū)崿F(xiàn)到底多高難度的動(dòng)作,可以參照下面這篇文章。

          1. https://www.zhihu.com/question/418792736/answer/1448121319[1]
          不過筆者認(rèn)為上述概念在前端圈可能比較小眾、“體操”這個(gè)名字對于外行人來說相對難以與具體的行為對應(yīng)起來、目前整個(gè) TC 過程更像有趣的 brain teaser[2],所以筆者覺得 TC “體操”還是用 Type Computing 、Type Calculation 或者“類型編程”來記憶會(huì)比較好理解,這也容易與具體行為對應(yīng),本文在接下來的環(huán)節(jié)會(huì)用“類型編程”來取代“體操”說法。

          3. 建模 (Modeling)

          其實(shí)類型編程說白了就是寫程序,這個(gè)程序接受類型作為輸入,然后輸出另一個(gè)類型,因此可以把它建模成寫普通的程序,并按照一般計(jì)算機(jī)語言的組成部分對 TS 的類型相關(guān)語法進(jìn)行歸類。

          4. 語法分類 (Grammar Classification)

          首先我們看看基本的語言都有哪些語法結(jié)構(gòu),以 JS 為例,從 AST(抽象語法樹)的角度來看[3],語法可以按照以下層級(jí)結(jié)構(gòu)進(jìn)行分類:
          但是我們今天不會(huì)以這種從上到下的樹狀結(jié)構(gòu)來整理和學(xué)習(xí),這樣子的學(xué)習(xí)曲線一開始會(huì)比較陡峭,所以作者并沒有按照從上到下的順序來整理,而是以學(xué)習(xí)普通語言的語法順序來整理。

          4.1 基本類型 (Basic Types)

          類似于 JS 里面有基本類型,TypeScript 也有基本類型,這個(gè)相信大家都很清楚,TypeScript 的基本類型如下:
          • Boolean[4]
          • Number[5]
          • String[6]
          • Array[7]
          • Tuple[8] (TypeScript 獨(dú)有)
          • Enum[9] (TypeScript 獨(dú)有)
          • Unknown[10] (TypeScript 獨(dú)有)
          • Any[11] (TypeScript 獨(dú)有)
          • Void[12] (TypeScript 獨(dú)有)
          • Null and Undefined[13]
          • Never[14] (TypeScript 獨(dú)有)
          • Object[15]
          任何復(fù)雜類型都是基本類型的組合,每個(gè)基本類型都可以有具體的枚舉:
          type A = {
              attrA: string,
              attrB: number,
              attrA: true// Boolean 的枚舉
              ...
          }

          4.2 函數(shù) (Function)

          類比 let func = (argA, argB, ...) => expression;
          Javascript 中有函數(shù)的概念,那么 TypeScript 的 Type-level programming(以下簡稱 TP) 相關(guān)語法中有沒有函數(shù)的概念呢?答案是有的,帶范型的類型就相當(dāng)于函數(shù)。
          // 函數(shù)定義
          type B<T> = T & {
              attrB: "anthor value"
          }

          // 變量
          class CCC {
          ...
          }
          type DDD = {
          ...
          }

          // 函數(shù)調(diào)用
          type AnotherType = B<CCC>;
          type YetAnotherType = B<DDD>;
          其中  <T> 就相當(dāng)于函數(shù)括弧和參數(shù)列表,= 后面的就相當(dāng)于函數(shù)定義。或者按照這個(gè)思路你可以開始沉淀很多工具類 TC 函數(shù)了,例如
          // 將所有屬性變成可選的
          type Optional<T> = {
            [key in keyof T]?: T[key];
          }

          // 將某些屬性變成必選的
          type MyRequired<T, K extends keyof T> = T &
            {
              [key in K]-?: T[key];
            };
            
          // 例如我們有個(gè)實(shí)體
          type App = {
            _id?: string;
            appId: string;
            name: string;
            description: string;
            ownerList: string[];
            createdAt?: number;
            updatedAt?: number;
          };

          // 我們在更新這個(gè)對象/類型的時(shí)候,有些 key 是必填的,有些 key 是選填的,這個(gè)時(shí)候就可以這樣子生成我們需要的類型
          type AppUpdatePayload = MyRequired<Optional<App>, '_id'>
          上面這個(gè)例子又暴露了另外一個(gè)可以類比的概念,也就是函數(shù)的參數(shù)的類型可以用 <K extends keyof T> 這樣的語法來表達(dá)。

          TypeScript 函數(shù)的缺陷 (Defect)

          目前下面這三個(gè)缺陷筆者還沒有找到辦法克服,聰明的你可以嘗試看看有沒有辦法克服。
          高版本才能支持遞歸
          4.1.0 才支持遞歸
          函數(shù)不能作為參數(shù)
          在 JS 里面,函數(shù)可以作為另外一個(gè)函數(shù)的入?yún)ⅲ纾?/section>
          function map(s, mapperreturn s.map(mapper) }
          map([123], (t) => s);
          但是在類型編程的“函數(shù)”里面,暫時(shí)沒有相關(guān)語法能夠?qū)崿F(xiàn)將函數(shù)作為參數(shù)傳入這種形式,正確來說,傳入的參數(shù)只能作為靜態(tài)值變量引用,不能作為可調(diào)用的函數(shù)。
          type Map<T, Mapper> = {
            [k in keyof T]: Mapper<T[k]>; // 語法報(bào)錯(cuò)
          }
          支持閉包,但是沒有辦法修改閉包中的值
          TypeScript 的“函數(shù)中”目前筆者沒有找到相關(guān)語法可以替代
          type ClosureValue = string;

          type Map<T> = {
            [k in keyof T]: ClosureValue; // 筆者沒有找到語法能夠修改 ClosureValue
          }
          但是我們可以通過類似于函數(shù)式編程的概念,組合出新的類型。
          type ClosureValue = string;

          type Map<T> = {
            [k in keyof T]: ClosureValue & T[k]; // 筆者沒有找到語法能夠修改 ClosureValue
          }

          4.3 語句 (Statements)

          在 TypeScript 中能夠?qū)?yīng)語句相關(guān)語法好像只有變量聲明語句相關(guān)語法,在 TypeScript 中沒有條件語句、循環(huán)語句函數(shù)、專屬的函數(shù)聲明語句(用下述的變量聲明語句來承載)。

          變量聲明語句 (Variable Declaration)

          類比:let a = Expression;
          變量聲明在上面的介紹已經(jīng)介紹過,就是簡單地通過 type ToDeclareType = Expresion 這樣子的變量名加表達(dá)式的語法來實(shí)現(xiàn),表達(dá)式有很多種類,我們接下來會(huì)詳細(xì)到介紹到,
          type ToDeclareType<T> = T extends (args: any) => PromiseLike<infer R> ? R : never; // 條件表達(dá)式/帶三元運(yùn)算符的條件表達(dá)式
          type ToDeclareType = Omit<App>; // 函數(shù)調(diào)用表達(dá)式
          type ToDeclareType<T>= { // 循環(huán)表達(dá)式
              [key in keyof T]: Omit<T[key], '_id'>
          }

          4.4 表達(dá)式 (Expressions)

          帶三元運(yùn)算符的條件表達(dá)式 (IfExpression with ternary operator)

          類比:a == b ? 'hello' : 'world';
          我們在 JS 里面寫“帶三元運(yùn)算符的條件表達(dá)式”的時(shí)候一般是 Condition ? ExpressionIfTrue : ExpressionIfFalse 這樣的形式,在 TypeScript 中則可以用以下的語法來表示:
          type TypeOfWhatPromiseReturn<T> = T extends (args: any) => PromiseLike<infer R> ? R : never;
          其中 T extends (args: any) => PromiseLike<infer R> 就相當(dāng)條件判斷,R : never 就相當(dāng)于為真時(shí)的表達(dá)式和為假時(shí)的表達(dá)式。
          利用上述的三元表達(dá)式,我們可以擴(kuò)展一下 ReturnType,讓它支持異步函數(shù)和同步函數(shù)
          async function hello(name: string): Promise<string{
            return Promise.resolve(name);
          }
          // type CCC: string = ReturnType<typeof hello>; doesn't work
          type MyReturnType<T extends (...args) => any> = T extends (
            ...args
          ) => PromiseLike<infer R>
            ? R
            : ReturnType<T>;
          type CCC: string = MyReturnType<typeof hello>; // it works

          函數(shù)調(diào)用/定義表達(dá)式 (CallExpression)

          類比:call(a, b, c);
          在上述“函數(shù)”環(huán)節(jié)已經(jīng)介紹過

          循環(huán)相關(guān) (Loop Related)(Object.keys、Array.map等)

          類比:for (let k in b) { ... }
          循環(huán)實(shí)現(xiàn)思路 (Details Explained )
          TypeScript 里面并沒有完整的循環(huán)語法,循環(huán)是通過遞歸來實(shí)現(xiàn)的,下面是一個(gè)例子:
          注意:遞歸只有在 TS 4.1.0 才支持
          type IntSeq<N, S extends any[] = []> =
              S["length"] extends N ? S :
              IntSeq<N, [...S, S["length"]]>
          理論上下面介紹的這些都是函數(shù)定義/表達(dá)式的一些例子,但是對于對象的遍歷還是很常見,用于補(bǔ)全循環(huán)語句,值得單獨(dú)拿出來講一下。
          對對象進(jìn)行遍歷 (Loop Object)
          type AnyType = {
            [key: string]: any;
          };
          type OptionalString<T> = {
            [key in keyof T]?: string;
          };
          type CCC = OptionalString<AnyType>;
          對數(shù)組(Tuple)進(jìn)行遍歷 (Loop Array/Tuple)
          map
          類比:Array.map
          const a = ['123'1, {}];
          type B = typeof a;
          type Map<T> = {
            [k in keyof T]: T[k] extends (...args) => any ? 0 : 1;
          };
          type C = Map<B>;
          type D = C[0];
          reduce
          類比:Array.reduce
          const a = ['123'1, {}];
          type B = typeof a;
          type Reduce<T extends any[]> = T[numberextends (...arg: any[]) => any ? 1 : 0;
          type C = Reduce<B>;
          注意這里的 reduce 返回的是一個(gè) Union 類型。

          4.5 成員表達(dá)式 (Member Expression)

          我們在 JS 中用例如 a.b.c 這樣的成員表達(dá)式主要是因?yàn)槲覀冎懒四硞€(gè)對象/變量的結(jié)構(gòu),然后想拿到其中某部分的值,在 TypeScript 中有個(gè)比較通用的方法,就是用 infer 語法,例如我們想拿到函數(shù)的某個(gè)參數(shù)就可以這么做:
          function hello(a: any, b: string{
            return b;
          }
          type getSecondParameter<T> = T extends (a: any, b: infer U) => any ? U : never;
          type P = getSecondParameter<typeof hello>;
          其中 T extends (a: any, b: infer U) => any 就是在表示結(jié)構(gòu),并拿其中某個(gè)部分。
          當(dāng)然其中 TypeScript 本身就有一些更加簡單的語法
          type A = {
            a: string;
            b: string;
          };
          type B = [stringstringboolean];
          type C = A['a'];
          type D = B[number];
          type E = B[0];
          // eslint-disable-next-line prettier/prettier
          type Last<T extends any[]> = T extends [...infer _, infer L] ? L : never;
          type F = Last<B>;

          4.6 常見數(shù)據(jù)結(jié)構(gòu)和操作 (Common Datastructures and Operations)

          Set

          集合數(shù)據(jù)結(jié)構(gòu)可以用 Union 類型來替代
          Add
          type S = '1' | 2 | a;
          S = S | 3;
          Remove
          type S = '1' | 2 | a;
          S = Exclude<S, '1'>;
          Has
          type S = '1' | 2 | a;
          type isInSet = 1 extends S ? true : false;
          Intersection
          type SA = '1' | 2;
          type SB = 2 | 3;
          type interset = Extract<SA, SB>;
          Diff
          type SA = '1' | 2;
          type SB = 2 | 3;
          type diff = Exclude<SA, SB>;
          Symmetric Diff
          type SA = '1' | 2;
          type SB = 2 | 3;
          type sdiff = Exclude<SA, SB> | Exclude<SB, SA>;
          ToIntersectionType
          type A = {
            a: string;
            b: string;
          };
          type B = {
            b: string;
            c: string;
          };
          type ToIntersectionType<U> = (
            U extends any ? (arg: U) => any : never
          extends (arg: infer I) => void
            ? I
            : never;
          type D = ToIntersectionType <A | B>;
          ToArray
          注意:遞歸只有在 TS 4.1.0 才支持
          type Input = 1 | 2;
          type UnionToIntersection<U> = (
            U extends any ? (arg: U) => any : never
          extends (arg: infer I) => void
            ? I
            : never;
          type ToArray<T> = UnionToIntersection<(extends any ? (t: T) => T : never)> extends (_: any) => infer W
            ? [...ToArray<Exclude<T, W>>, W]
            : [];
          type Output = ToArray<Input>;
          注意:這可能是 TS 的 bug 才使得這個(gè)功能成功,因?yàn)?:
          type C = ((arg: any) => true) & ((arg: any) => false);
          type D = C extends (arg: any) =>
           infer R ? R : never; // false;
          但在邏輯上,上述類型 C 應(yīng)該是 never 才對,因?yàn)槟阏也坏揭粋€(gè)函數(shù)的返回永遠(yuǎn)是 true 又永遠(yuǎn)是 false。
          Size
          type Input = 1 | 2;
          type Size = ToArray<Input>['length'];

          Map/Object

          Merge/Object.assign
          type C = A & B;
          Intersection
          interface A {
            a: string;
            b: string;
            c: string;
          }
          interface B {
            b: string;
            c: number;
            d: boolean;
          }
          type Intersection<A, B> = {
            [KA in Extract<keyof A, keyof B>]: A[KA] | B[KA];
          };
          type AandB = Intersection<A, B>;
          Filter
          type Input = { foo: number; bar?: string };
          type FilteredKeys<T> = {
            [P in keyof T]: T[P] extends number ? P : never;
          }[keyof T];
          type Filter<T> = {
            [key in FilteredKeys<T>]: T[key];
          };
          type Output = Filter<Input>;

          Array

          成員訪問
          type B = [stringstringboolean];
          type D = B[number];
          type E = B[0];
          // eslint-disable-next-line prettier/prettier
          type Last<T extends any[]> = T extends [...infer _, infer L] ? L : never;
          type F = Last<B>;
          type G = B['length'];
          Append
          type Append<T extends any[], V> = [...T, V];
          Pop
          type Pop<T extends any[]> = T extends [...infer I, infer _] ? I : never
          Dequeue
          type Dequeue<T extends any[]> = T extends [infer _, ...infer I] ? I : never
          Prepend
          type Prepend<T extends any[], V> = [V, ...T];
          Concat
          type Concat<T extends any[], V extends any[] > = [...T, ...V];
          Filter
          注意:遞歸只有在 TS 4.1.0 才支持
          type Filter<T extends any[]> = T extends [infer V, ...infer R]
            ? V extends number
              ? [V, ...Filter<R>]
              : Filter<R>
            : [];
          type Input = [12string];
          type Output = Filter<Input>;
          Slice
          注意:遞歸只有在 TS 4.1.0 才支持
          注意:為了實(shí)現(xiàn)簡單,這里 Slice 的用法和 Array.slice 用法不一樣:N 表示剩余元素的個(gè)數(shù)。
          type Input = [stringstringboolean];
          type Slice<N extends number, T extends any[]> = T['length'extends N
            ? T
            : T extends [infer _, ...infer U]
            ? Slice<N, U>
            : never;
          type Out = Slice<2, Input>;
          這里只用一層循環(huán)實(shí)現(xiàn) Array.slice(s) 這種效果,實(shí)現(xiàn) Array.slice(s, e) 涉及減法,比較麻煩,暫不在這里展開了。

          4.7 運(yùn)算符 (Operators)

          注意:運(yùn)算符的實(shí)現(xiàn)涉及遞歸,遞歸只有在 TS 4.1.0 才支持
          注意:下面的運(yùn)算符只能適用于整型
          注意:原理依賴于遞歸、效率較低

          基本原理 (Details Explained)

          基本原理是通過 Array 的 length 屬性來輸出整型,如果要實(shí)現(xiàn) * 法,請循環(huán)加法 N 次。。。
          type IntSeq<N, S extends any[] = []> =
              S["length"extends N ? S :
              IntSeq<N, [...S, S["length"]]>;

          ===

          type IfEquals<X, Y, A = X, B = never> = (<T>() => T extends X ? 1 : 2extends <
            T
          >() =>
           T extends Y ? 1 : 2
            ? A
            : B;

          +

          type NumericPlus<A extends Numeric, B extends Numeric> = [...IntSeq<A>, ...IntSeq<B>]["length"];

          -

          注意:減法結(jié)果不支持負(fù)數(shù) ...
          type NumericMinus<A extends Numeric, B extends Numeric> = _NumericMinus<B, A, []>;
          type ToNumeric<T extends number> = T extends Numeric ? T : never;
          type _NumericMinus<A extends Numeric, B extends Numeric, M extends any[]> = NumericPlus<A, ToNumeric<M["length"]>> extends B ? M["length"] : _NumericMinus<A, B, [...M, 0]>;

          4.8 其他 (MISC)

          inferface
          有些同學(xué)可能會(huì)問 interface 語法屬于上述的哪些范疇,除了 Declaration Merging[16],interface 的功能都可以用 type 來實(shí)現(xiàn),interface 更像是語法糖,所以筆者并沒有將 interface 來實(shí)現(xiàn)上述任意一個(gè)功能。
          inteface A extends B {
              attrA: string
          }
          Utility Types
          TypeScript 本身也提供了一些工具類型,例如取函數(shù)的參數(shù)列表有 Parameters 等,具體可以參照一下這個(gè)鏈接[17]

          5. 實(shí)戰(zhàn)演練 (Excercise)

          Promisify

          import * as fs from "fs";
          function promisify(fn{
            return function(...args: XXXX{
              return new Promise<XXXX>((resolve, reject) => {
                fn(...args, (err, data) => {
                  if(err) {
                    return reject(err);
                  }
                  resolve(data);
                }
          );
              }
          );
            }
          }
          (async () => {
            let file = await promisify(fs.readFile)("./xxx.json");
          }
          )();
          1. 我們需要知道 promisify(fs.readFile)(...) 這里能夠接受的類型。
          2. 我們需要 let file = await ... 這里 file 的類型。
          答案
          結(jié)合類型編程和新版本 TS,會(huì)比官方實(shí)現(xiàn)庫更簡潔、更具擴(kuò)展性(只支持 5 個(gè)參數(shù))  https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/util.promisify/implementation.d.ts[18]
          import * as fs from "fs";
          // 基于數(shù)據(jù)的基本操作 Last 和 Pop
          type Last<T extends any[]> = T extends [...infer _, infer L] ? L : never;
          type Pop<T extends any[]> = T extends [...infer I, infer _] ? I : never;
          // 對數(shù)組進(jìn)行操作
          type GetParametersType<T extends (...args: any) => any> = Pop<Parameters<T>>;
          type GetCallbackType<T extends (...args: any) => any> = Last<Parameters<T>>;
          // 類似于成員變量取值
          type GetCallbackReturnType<T extends (...args: any) => any> = GetCallbackType<T> extends (err: Error, data: infer R) => void ? R : any;
          function promisify<T extends (...args: any) => any>(fn: T{
            return function(...args: GetParametersType<T>{
              return new Promise<GetCallbackReturnType<T>>((resolve, reject) => {
                fn(...args, (err, data) => {
                  if(err) {
                    return reject(err);
                  }
                  resolve(data);
                }
          );
              }
          );
            }
          }
          (async () => {
            let file = await promisify(fs.readFile)("./xxx.json");
          }
          )();

          MyReturnType[19]

          基本上就是成員表達(dá)式部分提到的通用的提取某個(gè)部分的實(shí)現(xiàn)方法(用 infer 關(guān)鍵字)
          const fn = (v: boolean) => {
            if (v) return 1;
            else return 2;
          };
          type MyReturnType<F> = F extends (...args) => infer R ? R : never;
          type a = MyReturnType<typeof fn>;

          Readonly 2[20]

          基本上就是 Merge 和遍歷 Object
          interface Todo {
            title: string;
            description: string;
            completed: boolean;
          }
          type MyReadonly2<T, KEYS extends keyof T> = T &
            {
              readonly [k in KEYS]: T[k];
            };
          const todo: MyReadonly2<Todo, 'title' | 'description'> = {
            title: 'Hey',
            description: 'foobar',
            completed: false,
          };
          todo.title = 'Hello'// Error: cannot reassign a readonly property
          todo.description = 'barFoo'// Error: cannot reassign a readonly property
          todo.completed = true// O

          Type Lookup[21]

          成員訪問和三元表達(dá)式的應(yīng)用
          interface Cat {
            type'cat';
            breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal';
          }
          interface Dog {
            type'dog';
            breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer';
            color: 'brown' | 'white' | 'black';
          }
          type LookUp<T, K extends string> = T extends { typestring }
            ? T['type'extends K
              ? T
              : never
            : never;
          type MyDogType = LookUp<Cat | Dog, 'dog'>; // expected to be `Dog`

          Get Required[22]

          參照 Object 的 Filter 方法
          type GetRequiredKeys<T> = {
            [key in keyof T]-?: {} extends Pick<T, key> ? never : key;
          }[keyof T];
          type GetRequired<T> = {
            [key in GetRequiredKeys<T>]: T[key];
          };
          type I = GetRequired<{ foo: number; bar?: string }>; // expected to be { foo: number }

          6. 想法 (Thoughts)

          沉淀類型編程庫 (Supplementary Utility Types)

          除了 Utility Types 之外,添加通用的,易于理解的 TypeScript 工具類庫,做 TS 屆的 underscore。
          Update: 發(fā)現(xiàn)已經(jīng)有這樣的庫了:
          • https://github.com/piotrwitek/utility-types

          • https://github.com/sindresorhus/type-fest


          直接用 JS  做類型編程 (Doing Type Computing in Plain TS)

          即使按照本文的建模方式,由上面的歸類可以看出,目前對比起現(xiàn)代的編程語言還是缺失挺多的關(guān)鍵能力。類型編程學(xué)習(xí)成本太高、像智力游戲的原因也是因?yàn)檎Z法成分缺失、使用不直觀的原因。為了使類型編程面向更廣的受眾,應(yīng)當(dāng)提供更友好的語法、更全面的語法,一個(gè)樸素的想法是在 compile time 運(yùn)行的類似 JS 本身的語法(宏?)。
          以下語法純粹拍腦袋,例如:
          type Test = {
              a: string
          }
          typecomp function Map(T, mapper{
              for (let key of Object.keys(T)) {
                  T[key] = mapper(T[key]);       
              }
          }
          typecomp AnotherType = Map(Test, typecomp (T) => {
              if (T extends 'hello') {
                  return number;
              } else {
                  return string;
              }
          });
          如果有這樣子直觀的語法,筆者感覺會(huì)使得類型編程更容易上手。需要實(shí)現(xiàn)這樣的效果,可能需要我們 fork TypeScript 的 repo,添加以上的功能,希望有能力的讀者可以高質(zhì)量地實(shí)現(xiàn)這個(gè)能力,效果好的話,還可以 merge 到源 TypeScript Repo 中,造福筆者這個(gè)時(shí)刻為類型編程苦惱的開發(fā)者。

          7. Reference

          1. https://github.com/type-challenges/type-challenges
          2. https://www.zhihu.com/question/418792736/answer/1448121319
          3. https://github.com/piotrwitek/utility-types#requiredkeyst

          參考資料

          [1] https://www.zhihu.com/question/418792736/answer/1448121319

          [2] 有趣的 brain teaser: https://github.com/type-challenges/type-challenges

          [3] AST(抽象語法樹)的角度來看: https://github.com/babel/babel/blob/main/packages/babel-types/src/definitions/core.js

          [4] Boolean: https://www.typescriptlang.org/docs/handbook/basic-types.html#boolean

          [5] Number: https://www.typescriptlang.org/docs/handbook/basic-types.html#number

          [6] String: https://www.typescriptlang.org/docs/handbook/basic-types.html#string

          [7] Array: https://www.typescriptlang.org/docs/handbook/basic-types.html#array

          [8] Tuple: https://www.typescriptlang.org/docs/handbook/basic-types.html#tuple

          [9] Enum: https://www.typescriptlang.org/docs/handbook/basic-types.html#enum

          [10] Unknown: https://www.typescriptlang.org/docs/handbook/basic-types.html#unknown

          [11] Any: https://www.typescriptlang.org/docs/handbook/basic-types.html#any

          [12] Void: https://www.typescriptlang.org/docs/handbook/basic-types.html#void

          [13] Null and Undefined: https://www.typescriptlang.org/docs/handbook/basic-types.html#null-and-undefined

          [14] Never: https://www.typescriptlang.org/docs/handbook/basic-types.html#never

          [15] Object: https://www.typescriptlang.org/docs/handbook/basic-types.html#object

          [16] Declaration Merging: https://www.typescriptlang.org/docs/handbook/declaration-merging.html

          [17] 這個(gè)鏈接: https://www.typescriptlang.org/docs/handbook/utility-types.html

          [18] https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/util.promisify/implementation.d.ts

          [19] https://github.com/type-challenges/type-challenges/blob/master/questions/2-medium-return-type/README.md

          [20] https://github.com/type-challenges/type-challenges/blob/master/questions/8-medium-readonly-2/README.md

          [21] https://github.com/type-challenges/type-challenges/blob/master/questions/62-medium-type-lookup/README.md

          [22] https://github.com/type-challenges/type-challenges/blob/master/questions/57-hard-get-required/README.md


          最后



          如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個(gè)小忙:

          1. 點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)

          2. 歡迎加我微信「qianyu443033099」拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...

          3. 關(guān)注公眾號(hào)「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。

          點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了



          瀏覽 36
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  欧美日在线观看 | 亚洲在线观看视频 | 99国产高清 | 女人黄色A级毛片 | 国产又大又黄的视频 |