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

          精讀《TypeScript 4》

          共 8766字,需瀏覽 18分鐘

           ·

          2021-02-04 09:28


          1 引言

          隨著 Typescript 4 Beta 的發(fā)布,又帶來了許多新功能,其中 Variadic Tuple Types 解決了大量重載模版代碼的頑疾,使得這次更新非常有意義。

          2 簡介

          可變元組類型

          考慮 concat 場景,接收兩個數(shù)組或者元組類型,組成一個新數(shù)組:

          function?concat(arr1,?arr2)?{
          ??return?[...arr1,?...arr2];
          }

          如果要定義 concat 的類型,以往我們會通過枚舉的方式,先枚舉第一個參數(shù)數(shù)組中的每一項:

          function?concat<>(arr1:?[],?arr2:?[]):?[A];
          function?concat<A>(arr1:?[A],?arr2:?[]):?[A];
          function?concat<A,?B>(arr1:?[A,?B],?arr2:?[]):?[A,?B];
          function?concat<A,?B,?C>(arr1:?[A,?B,?C],?arr2:?[]):?[A,?B,?C];
          function?concat<A,?B,?C,?D>(arr1:?[A,?B,?C,?D],?arr2:?[]):?[A,?B,?C,?D];
          function?concat<A,?B,?C,?D,?E>(arr1:?[A,?B,?C,?D,?E],?arr2:?[]):?[A,?B,?C,?D,?E];
          function?concat<A,?B,?C,?D,?E,?F>(arr1:?[A,?B,?C,?D,?E,?F],?arr2:?[]):?[A,?B,?C,?D,?E,?F];)

          再枚舉第二個參數(shù)中每一項,如果要完成所有枚舉,僅考慮數(shù)組長度為 6 的情況,就要定義 36 次重載,代碼幾乎不可維護:

          function?concat<A2>(arr1:?[],?arr2:?[A2]):?[A2];
          function?concat<A1,?A2>(arr1:?[A1],?arr2:?[A2]):?[A1,?A2];
          function?concat<A1,?B1,?A2>(arr1:?[A1,?B1],?arr2:?[A2]):?[A1,?B1,?A2];
          function?concat<A1,?B1,?C1,?A2>(
          ??arr1:?[A1,?B1,?C1],
          ??arr2:?[A2]
          ):?[A1,?B1,?C1,?A2]
          ;
          function?concat<A1,?B1,?C1,?D1,?A2>(
          ??arr1:?[A1,?B1,?C1,?D1],
          ??arr2:?[A2]
          ):?[A1,?B1,?C1,?D1,?A2]
          ;
          function?concat<A1,?B1,?C1,?D1,?E1,?A2>(
          ??arr1:?[A1,?B1,?C1,?D1,?E1],
          ??arr2:?[A2]
          ):?[A1,?B1,?C1,?D1,?E1,?A2]
          ;
          function?concat<A1,?B1,?C1,?D1,?E1,?F1,?A2>(
          ??arr1:?[A1,?B1,?C1,?D1,?E1,?F1],
          ??arr2:?[A2]
          ):?[A1,?B1,?C1,?D1,?E1,?F1,?A2]
          ;

          如果我們采用批量定義的方式,問題也不會得到解決,因為參數(shù)類型的順序得不到保證:

          function?concat<T,?U>(arr1:?T[],?arr2,?U[]):?Array<T?|?U>;

          在 Typescript 4,可以在定義中對數(shù)組進行解構(gòu),通過幾行代碼優(yōu)雅的解決可能要重載幾百次的場景:

          type?Arr?=?readonly?any[];

          function?concat<T?extends?Arr,?U?extends?Arr>(arr1:?T,?arr2:?U):?[...T,?...U]?{
          ??return?[...arr1,?...arr2];
          }

          上面例子中,Arr 類型告訴 TS TU 是數(shù)組類型,再通過 [...T, ...U] 按照邏輯順序依次拼接類型。

          再比如 tail,返回除第一項外剩下元素:

          function?tail(arg)?{
          ??const?[_,?...result]?=?arg;
          ??return?result;
          }

          同樣告訴 TS T 是數(shù)組類型,且 arr: readonly [any, ...T] 申明了 T 類型表示除第一項其余項的類型,TS 可自動將 T 類型關(guān)聯(lián)到對象 rest

          function?tail<T?extends?any[]>(arr:?readonly?[any,?...T])?{
          ??const?[_ignored,?...rest]?=?arr;
          ??return?rest;
          }

          const?myTuple?=?[1,?2,?3,?4]?as?const;
          const?myArray?=?["hello",?"world"];

          //?type?[2,?3,?4]
          const?r1?=?tail(myTuple);

          //?type?[2,?3,?...string[]]
          const?r2?=?tail([...myTuple,?...myArray]?as?const);

          另外之前版本的 TS 只能將類型解構(gòu)放在最后一個位置:

          type?Strings?=?[string,?string];
          type?Numbers?=?[number,?number];

          //?[string,?string,?number,?number]
          type?StrStrNumNum?=?[...Strings,?...Numbers];

          如果你嘗試將 [...Strings, ...Numbers] 這種寫法,將會得到一個錯誤提示:

          A rest element must be last in a tuple type.

          但在 Typescript 4 版本支持了這種語法:

          type?Strings?=?[string,?string];
          type?Numbers?=?number[];

          //?[string,?string,?...Array]
          type?Unbounded?=?[...Strings,?...Numbers,?boolean];

          對于再復(fù)雜一些的場景,例如高階函數(shù) partialCall,支持一定程度的柯里化:

          function?partialCall(f,?...headArgs)?{
          ??return?(...tailArgs)?=>?f(...headArgs,?...tailArgs);
          }

          我們可以通過上面的特性對其進行類型定義,將函數(shù) f 第一個參數(shù)類型定義為有順序的 [...T, ...U]

          type?Arr?=?readonly?unknown[];

          function?partialCall<T?extends?Arr,?U?extends?Arr,?R>(
          ??f:?(...args:?[...T,?...U])?=>?R,
          ??...headArgs:?T
          )?
          {
          ??return?(...b:?U)?=>?f(...headArgs,?...b);
          }

          測試效果如下:

          const?foo?=?(x:?string,?y:?number,?z:?boolean)?=>?{};

          //?This?doesn't?work?because?we're?feeding?in?the?wrong?type?for?'x'.
          const?f1?=?partialCall(foo,?100);
          //??????????????????????????~~~
          //?error!?Argument?of?type?'number'?is?not?assignable?to?parameter?of?type?'string'.

          //?This?doesn't?work?because?we're?passing?in?too?many?arguments.
          const?f2?=?partialCall(foo,?"hello",?100,?true,?"oops");
          //??????????????????????????????????????????????~~~~~~
          //?error!?Expected?4?arguments,?but?got?5.

          //?This?works!?It?has?the?type?'(y:?number,?z:?boolean)?=>?void'
          const?f3?=?partialCall(foo,?"hello");

          //?What?can?we?do?with?f3?now?

          f3(123,?true);?//?works!

          f3();
          //?error!?Expected?2?arguments,?but?got?0.

          f3(123,?"hello");
          //??????~~~~~~~
          //?error!?Argument?of?type?'"hello"'?is?not?assignable?to?parameter?of?type?'boolean'

          值得注意的是,const f3 = partialCall(foo, "hello"); 這段代碼由于還沒有執(zhí)行到 foo,因此只匹配了第一個 x:string 類型,雖然后面 y: number, z: boolean 也是必選,但因為 foo 函數(shù)還未執(zhí)行,此時只是參數(shù)收集階段,因此不會報錯,等到 f3(123, true) 執(zhí)行時就會校驗必選參數(shù)了,因此 f3() 時才會提示參數(shù)數(shù)量不正確。

          元組標(biāo)記

          下面兩個函數(shù)定義在功能上是一樣的:

          function?foo(...args:?[string,?number]):?void?{
          ??//?...
          }

          function?foo(arg0:?string,?arg1:?number):?void?{
          ??//?...
          }

          但還是有微妙的區(qū)別,下面的函數(shù)對每個參數(shù)都有名稱標(biāo)記,但上面通過解構(gòu)定義的類型則沒有,針對這種情況,Typescript 4 支持了元組標(biāo)記:

          type?Range?=?[start:?number,?end:?number];

          同時也支持與解構(gòu)一起使用:

          type?Foo?=?[first:?number,?second?:?string,?...rest:?any[]];

          Class 從構(gòu)造函數(shù)推斷成員變量類型

          構(gòu)造函數(shù)在類實例化時負責(zé)一些初始化工作,比如為成員變量賦值,在 Typescript 4,在構(gòu)造函數(shù)里對成員變量的賦值可以直接為成員變量推導(dǎo)類型:

          class?Square?{
          ??//?Previously:?implicit?any!
          ??//?Now:?inferred?to?`number`!
          ??area;
          ??sideLength;

          ??constructor(sideLength:?number)?{
          ????this.sideLength?=?sideLength;
          ????this.area?=?sideLength?**?2;
          ??}
          }

          如果對成員變量賦值包含在條件語句中,還能識別出存在 undefined 的風(fēng)險:

          class?Square?{
          ??sideLength;

          ??constructor(sideLength:?number)?{
          ????if?(Math.random())?{
          ??????this.sideLength?=?sideLength;
          ????}
          ??}

          ??get?area()?{
          ????return?this.sideLength?**?2;
          ????//?????~~~~~~~~~~~~~~~
          ????//?error!?Object?is?possibly?'undefined'.
          ??}
          }

          如果在其他函數(shù)中初始化,則 TS 不能自動識別,需要用 !: 顯式申明類型:

          class?Square?{
          ??//?definite?assignment?assertion
          ??//????????v
          ??sideLength!:?number;
          ??//?????????^^^^^^^^
          ??//?type?annotation

          ??constructor(sideLength:?number)?{
          ????this.initialize(sideLength);
          ??}

          ??initialize(sideLength:?number)?{
          ????this.sideLength?=?sideLength;
          ??}

          ??get?area()?{
          ????return?this.sideLength?**?2;
          ??}
          }

          短路賦值語法

          針對以下三種短路語法提供了快捷賦值語法:

          a?&&=?b;?//?a?=?a?&&?b
          a?||=?b;?//?a?=?a?||?b
          a???=?b;?//?a?=?a????b

          catch error unknown 類型

          Typescript 4.0 之后,我們可以將 catch error 定義為 unknown 類型,以保證后面的代碼以健壯的類型判斷方式書寫:

          try?{
          ??//?...
          }?catch?(e)?{
          ??//?error!
          ??//?Property?'toUpperCase'?does?not?exist?on?type?'unknown'.
          ??console.log(e.toUpperCase());

          ??if?(typeof?e?===?"string")?{
          ????//?works!
          ????//?We've?narrowed?'e'?down?to?the?type?'string'.
          ????console.log(e.toUpperCase());
          ??}
          }

          PS:在之前的版本,catch (e: unknown) 會報錯,提示無法為 error 定義 unknown 類型。

          自定義 JSX 工廠

          TS 4 支持了 jsxFragmentFactory 參數(shù)定義 Fragment 工廠函數(shù):

          {
          ??"compilerOptions":?{
          ????"target":?"esnext",
          ????"module":?"commonjs",
          ????"jsx":?"react",
          ????"jsxFactory":?"h",
          ????"jsxFragmentFactory":?"Fragment"
          ??}
          }

          還可以通過注釋方式覆蓋單文件的配置:

          //?Note:?these?pragma?comments?need?to?be?written
          //?with?a?JSDoc-style?multiline?syntax?to?take?effect.
          /**?@jsx?h?*/
          /**?@jsxFrag?Fragment?*/

          import?{?h,?Fragment?}?from?"preact";

          let?stuff?=?(
          ??<>
          ????
          Hello</div>
          ??>
          );

          以上代碼編譯后解析結(jié)果如下:

          //?Note:?these?pragma?comments?need?to?be?written
          //?with?a?JSDoc-style?multiline?syntax?to?take?effect.
          /**?@jsx?h?*/
          /**?@jsxFrag?Fragment?*/
          import?{?h,?Fragment?}?from?"preact";
          let?stuff?=?h(Fragment,?null,?h("div",?null,?"Hello"));

          其他升級

          其他的升級快速介紹:

          構(gòu)建速度提升,提升了 --incremental + --noEmitOnError 場景的構(gòu)建速度。

          支持 --incremental + --noEmit 參數(shù)同時生效。

          支持 @deprecated 注釋, 使用此注釋時,代碼中會使用 刪除線 警告調(diào)用者。

          局部 TS Server 快速啟動功能, 打開大型項目時,TS Server 要準備很久,Typescript 4 在 VSCode 編譯器下做了優(yōu)化,可以提前對當(dāng)前打開的單文件進行部分語法響應(yīng)。

          優(yōu)化自動導(dǎo)入, 現(xiàn)在 package.json dependencies 字段定義的依賴將優(yōu)先作為自動導(dǎo)入的依據(jù),而不再是遍歷 node_modules 導(dǎo)入一些非預(yù)期的包。

          除此之外,還有幾個 Break Change:

          lib.d.ts 類型升級,主要是移除了 document.origin 定義。

          覆蓋父 Class 屬性的 getter 或 setter 現(xiàn)在都會提示錯誤。

          通過 delete 刪除的屬性必須是可選的,如果試圖用 delete 刪除一個必選的 key,則會提示錯誤。

          3 精讀

          Typescript 4 最大亮點就是可變元組類型了,但可變元組類型也不能解決所有問題。

          拿筆者的場景來說,函數(shù) useDesigner 作為自定義 React Hook 與 useSelector 結(jié)合支持 connect redux 數(shù)據(jù)流的值,其調(diào)用方式是這樣的:

          const?nameSelector?=?(state:?any)?=>?({
          ??name:?state.name?as?string,
          });

          const?ageSelector?=?(state:?any)?=>?({
          ??age:?state.age?as?number,
          });

          const?App?=?()?=>?{
          ??const?{?name,?age?}?=?useDesigner(nameSelector,?ageSelector);
          };

          nameage 是 Selector 注冊的,內(nèi)部實現(xiàn)方式必然是 useSelector + reduce,但類型定義就麻煩了,通過重載可以這么做:

          import?*?as?React?from?'react';
          import?{?useSelector?}?from?'react-redux';

          type?Function?=?(...args:?any)?=>?any;

          export?function?useDesigner();
          export?function?useDesigner<T1?extends?Function>(
          ??t1:?T1
          ):?ReturnType<T1>?
          ;
          export?function?useDesigner<T1?extends?Function,?T2?extends?Function>(
          ??t1:?T1,
          ??t2:?T2
          ):?ReturnType<T1>?&?ReturnType<T2>?
          ;
          export?function?useDesigner<
          ??T1?extends?Function,
          ??T2?extends?Function,
          ??T3?extends?Function
          >(
          ??t1:?T1,
          ??t2:?T2,
          ??t3:?T3,
          ??t4:?T4,
          ):?ReturnType<T1>?&
          ??ReturnType<T2>?&
          ??ReturnType<T3>?&
          ??ReturnType<T4>?&
          ;
          export?function?useDesigner<
          ??T1?extends?Function,
          ??T2?extends?Function,
          ??T3?extends?Function,
          ??T4?extends?Function
          >(
          ??t1:?T1,
          ??t2:?T2,
          ??t3:?T3,
          ??t4:?T4
          ):?ReturnType<T1>?&
          ??ReturnType<T2>?&
          ??ReturnType<T3>?&
          ??ReturnType<T4>?&
          ;
          export?function?useDesigner(...selectors:?any[])?{
          ??return?useSelector((state)?=>
          ????selectors.reduce((selected,?selector)?=>?{
          ??????return?{
          ????????...selected,
          ????????...selector(state),
          ??????};
          ????},?{})
          ??)?as?any;
          }

          可以看到,筆者需要將 useDesigner 傳入的參數(shù)通過函數(shù)重載方式一一傳入,上面的例子只支持到了三個參數(shù),如果傳入了第四個參數(shù)則函數(shù)定義會失效,因此業(yè)界做法一般是定義十幾個重載,這樣會導(dǎo)致函數(shù)定義非常冗長。

          但參考 TS4 的例子,我們可以避免類型重載,而通過枚舉的方式支持:

          type?Func?=?(state?:?any)?=>?any;
          type?Arr?=?readonly?Func[];

          const?useDesigner?=?extends?Arr>(
          ??...selectors:?T
          ):?ReturnType0]>?&
          ??ReturnType1]>?&
          ??ReturnType2]>?&
          ??ReturnType3]>?=>?{
          ??return?useSelector((state)?=>
          ????selectors.reduce((selected,?selector)?=>?{
          ??????return?{
          ????????...selected,
          ????????...selector(state),
          ??????};
          ????},?{})
          ??)?as?any;
          };

          可以看到,最大的變化是不需要寫四遍重載了,但由于場景和 concat 不同,這個例子返回值不是簡單的 [...T, ...U],而是 reduce 的結(jié)果,所以目前還只能通過枚舉的方式支持。

          當(dāng)然可能存在不用枚舉就可以支持無限長度的入?yún)㈩愋徒馕龅姆桨?,因筆者水平有限,暫未想到更好的解法,如果你有更好的解法,歡迎告知筆者。

          4 總結(jié)

          Typescript 4 帶來了更強類型語法,更智能的類型推導(dǎo),更快的構(gòu)建速度以及更合理的開發(fā)者工具優(yōu)化,唯一的幾個 Break Change 不會對項目帶來實質(zhì)影響,期待正式版的發(fā)布。

          ??愛心三連擊

          1.看到這里了就點個在看支持下吧,你的點贊,在看是我創(chuàng)作的動力。

          2.關(guān)注公眾號程序員成長指北,回復(fù)「1」加入高級前端交流群!「在這里有好多 前端?開發(fā)者,會討論?前端 Node 知識,互相學(xué)習(xí)」!

          3.也可添加微信【ikoala520】,一起成長。

          “在看轉(zhuǎn)發(fā)”是最大的支持

          瀏覽 15
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  威特成人版 | 免费国产黄色片 | 我要看18毛片 | 热情网站AV | 日韩一区二区三区操b |