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

          TypeScript4 大版本更新,到底有哪些新特性!

          共 8783字,需瀏覽 18分鐘

           ·

          2020-12-03 12:37

          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 要準(zhǔn)備很久,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)㈩愋徒馕龅姆桨福蚬P者水平有限,暫未想到更好的解法,如果你有更好的解法,歡迎告知筆者。

          4 總結(jié)

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


          最后



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

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

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

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


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



          瀏覽 79
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  亚洲综合另类 | 一级乱伦视频 | 亚洲精品一区二区三区2023年最新 | 国产色视频在线看 | 亚洲一级操逼片 |