<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 的一些注意事項(xiàng)(回顧總結(jié))

          共 8405字,需瀏覽 17分鐘

           ·

          2021-02-24 12:46


          ???作者 @王福朋,原文地址:https://juejin.cn/post/6929793926979125255,作者授權(quán)轉(zhuǎn)載,如需轉(zhuǎn)載請聯(lián)系作者授權(quán)。

          背景

          ts 用了一年了,回顧起來,也沒有那么順利。趁這兩天春節(jié)假期有時(shí)間,整理了幾個(gè)自己覺得需要注意的情況,復(fù)盤一下。

          我上學(xué)時(shí)學(xué)過 java 和 C# ,畢業(yè)后又做了兩年 C# 全棧開發(fā),對于靜態(tài)類型語言是有一定經(jīng)驗(yàn)的。ts 之所以能夠慢慢取代 js ,也是因?yàn)樗庆o態(tài)類型語言。

          但 ts 和 java 是不一樣的,本質(zhì)是因?yàn)樗鳛橐粋€(gè)靜態(tài)類型語言,要編譯成弱類型語言 js 來執(zhí)行。所以,ts 只管得了編譯時(shí),卻管不了運(yùn)行時(shí)。 下文的很多內(nèi)容,都是這個(gè)特點(diǎn)的具體表現(xiàn)。

          【個(gè)人提醒】我感覺 ts 為了能讓自己更適應(yīng) js 的轉(zhuǎn)型,做了很多非常繁瑣(或者叫靈活)的設(shè)計(jì),我沒有詳細(xì)總結(jié),但這種感覺很強(qiáng)烈。所以,如果你覺得 ts 有些地方過于繁瑣時(shí),也不要擔(dān)心,這可能不是你的問題,而是它的問題。

          任何美好的東西,都是應(yīng)該簡單的、明確的。

          易混亂的類型

          如果問“ts 的變量有多少種類型”,你能否回答全面?ts 比 js 類型多一些。

          never vs void

          只需要記住一個(gè)特點(diǎn):返回 never 的函數(shù),都必須存在無法到達(dá)的終點(diǎn),如死循環(huán)、拋出異常。

          function?fn1():?never?{
          ?while(true)?{?/*...*/?}
          }

          function?fn2():?never?{
          ?throw?new?Error(?/*...*/?)
          }

          any vs unknown

          • any 任何類型,會忽略語法檢查
          • unknown 不可預(yù)知的類型,不會忽略語法檢查(這就是最大區(qū)別)
          const?bar:?any?=?10;
          any.substr(1);?//?OK?-?any?會忽略所有類型檢查

          const?foo:?unknown?=?'string';
          foo.substr(1);?//?Error:?語法檢查不通過報(bào)錯(cuò)
          //?(foo?as?string).substr(1)?//?OK
          //?if?(typeof?foo?===?'string')?{?foo.substr(1)?}?//?OK

          一些“欺騙”編譯器語法檢查的行為

          就如同你告訴編譯器:“按我寫的來,不要管太多,出了事兒我負(fù)責(zé)!”

          編譯器不給你添麻煩了,不進(jìn)行語法檢查了,但你一定要考慮好后果。所以,以下內(nèi)容請慎用,不要無腦使用。

          @ts-ignore

          增加 @ts-ignore 的注釋,會忽略下一行的語法檢查。

          const?num1:?number?=?100
          num1.substr()?//?Error?語法檢查錯(cuò)誤

          const?num2:?number?=?200
          //?@ts-ignore
          num2.substr()?//?Ok?語法檢查通過

          any

          如果 ts 是西游記,any 就是孫悟空,自由、無約束。了解西游記大部分是從孫悟空開始,了解 ts 可能也是從 any 開始用。

          但西游記最后,孫悟空變成了佛。你的 any 也應(yīng)該變成 interface 或者 type 。

          類型斷言 as

          文章一開始說過,ts 只管編譯時(shí),不管運(yùn)行時(shí)。as 就是典型的例子,你用 as 告訴編譯器類型,編譯器就聽你的。但運(yùn)行時(shí),后果自負(fù)。

          function?fn(a:?string?|?null):?void?{
          ????const?length?=?(a?as?string).length
          ????console.log(length)
          }
          fn('abc')?//?Ok
          //?fn(null)?//?Error?js?運(yùn)行報(bào)錯(cuò)

          非空斷言操作符 !

          ! 用于排除 null undefined ,即告訴編譯器:xx 變量肯定不是 null 或 undefined ,你放心吧~

          同理,運(yùn)行時(shí)有可能出錯(cuò)。

          //?例子?1
          function?fn(a:?string?|?null?|?undefined)?{
          ????let?s:?string?=?''
          ????s?=?a?//?Error?語法檢查失敗
          ????s?=?a!?// OK ——?【注意】如果 a 真的是 null 或者 undefined ,那么 s 也會是 null 或者 undefined ,可能會帶來 bug ?。。?/span>
          }
          //?fn(null)
          //?例子?2
          type?NumGenerator?=?()?=>?number;

          function?myFunc(numGenerator:?NumGenerator?|?undefined)?{
          ??const?num1?=?numGenerator();?//?Error?語法檢查失敗
          ??const?num2?=?numGenerator!();?//?OK
          }

          // myFunc(undefined)?//?【注意】,如果真的傳入 undefined ,也會去執(zhí)行,當(dāng)然會執(zhí)行報(bào)錯(cuò)?。?!
          //?例子?3
          let?a:?number
          console.log(a)?//?Error?-?Variable?'n'?is?used?before?being?assigned.
          let?b!:?number
          console.log(b)?//?OK?-?`!`?表示,你會給?b?一個(gè)賦值,不用編譯器關(guān)心

          可選鏈 ?.

          ?.遇到 nullundefined 就可以立即停止某些表達(dá)式的運(yùn)行,并返回 undefined【注意】這里只針對 nullundefined ,對于 0 false '' 等 falsely 變量是不起作用的。這一點(diǎn)和 && 不一樣。

          這個(gè)運(yùn)算符,看似是獲取一個(gè)屬性,其實(shí)它是有條件判斷的。即,它就是一個(gè) ? : 三元表達(dá)式的語法糖。既然它有判斷邏輯,那你考慮不到位,就有可能出錯(cuò)。

          //?例子?1?-?獲取對象屬性
          interface?IFoo?{?a:?number?}

          function?fn(obj:?IFoo?|?null?|?undefined):?number?|?undefined?{
          ????const?a?=?obj?.a?//??.?可選鏈運(yùn)算符

          ????//?第一,如果?a?是?IFoo?類型,則打印?100
          ????//?第二,如果?a?是?null?或者?undefined?,則打印?undefined
          ????console.log('a',?a)

          ????return?a?//?100?或者?undefined
          }
          fn({?a:?100?})
          //?fn(null)
          //?fn(undefined)
          //?例子?2?-?獲取數(shù)組元素
          function?tryGetArrayElement<T>(arr?:?T[],?index:?number?=?0)?{
          ??return?arr?.[index];
          }
          //?編譯產(chǎn)出:
          //?"use?strict";
          //?function?tryGetArrayElement(arr,?index?=?0)?{
          //?????return?arr?===?null?||?arr?===?void?0???void?0?:?arr[index];
          //?}
          //?例子?3?-?用于函數(shù)調(diào)用
          type?NumGenerator?=?()?=>?number;
          function?fn(numGenerator:?NumGenerator?|?undefined?|?null)?{
          ??const?num?=?numGenerator?.();
          ??console.log('num',?num)?//?如果不是函數(shù),則不調(diào)用,也不會報(bào)錯(cuò),返回?undefined
          }
          //?fn(null)
          //?fn(undefined)

          【吐槽】對于這種語法糖,我還是比較反感的,我覺得自己寫幾行邏輯判斷會更好。它雖然簡潔,但是它會帶來閱讀理解上的負(fù)擔(dān),代碼簡潔不一定就可讀性好 —— 當(dāng)然了,如果大家都這么用,用久了,大家都熟悉了,可能也就沒有這個(gè)障礙了。

          type 和 interface

          關(guān)于兩者的區(qū)別,大家可以看看這篇文章 ,本文主要說一下我的理解。

          先說結(jié)論:我目前還是處于一種懵逼狀態(tài)。我感覺 typeinsterface 有太多的灰色地帶,這就導(dǎo)致我們?nèi)粘J褂脮r(shí),大部分情況下用誰都可以。我搞不懂 ts 為何要這樣設(shè)計(jì)。

          按照我前些年對 java 和 C# 的理解:(我不知道近幾年 java C# 有沒有相關(guān)的語法變化)

          • 如果自定義一個(gè)靜態(tài)的類型,僅有一些屬性,沒有方法,就用 type
          • 如果定義一種行為(行為肯定是需要方法的,僅屬性是不夠的),需要 class 實(shí)現(xiàn),就用 interface

          但是查到的資料,以及查閱 ts 的類庫 lib.dom.d.ts 和 lib.es2015.d.ts 源碼,也都是用 interface 。我曾經(jīng)一度很困惑,見的多了,就慢慢習(xí)慣成自然了,但問題并沒有解決。

          問題沒有解決,但事情還是要繼續(xù)做的,代碼也是要繼續(xù)寫的,所以我就一直跟隨大眾,盡量用 interface 。

          private 和

          兩者都表示私有屬性。背景不同:

          • private 是 ts 中一開始就有的語法,而且目前只有 ts 有,ES 規(guī)范沒有。
          • # 是 ES 目前的提案語法,然后被 ts 3.8 支持了。即,ts 和 ES 都支持 #

          如果僅對于 ts 來說,用哪個(gè)都一樣。

          但本文一開始提到過:ts 只關(guān)注編譯時(shí),不關(guān)注運(yùn)行時(shí)。所以,還得看看兩者的編譯結(jié)果。

          private

          private 編譯之后,就失去了私有的特點(diǎn)。即,如果你執(zhí)行 (new Person()).name ,雖然語法檢查不通過,但運(yùn)行時(shí)是可以成功的。即,private 僅僅是 ts 的語法,編譯成 js 之后,就失效了。

          //?ts?源碼
          class?Person?{
          ????private?name:?string
          ????constructor()?{
          ????????this.name?=?'zhangsan'
          ????}
          }

          /*?編譯結(jié)果如下
          "use?strict";
          class?Person?{
          ????constructor()?{
          ????????this.name?=?'zhangsan';
          ????}
          }
          */

          #

          # 編譯之后,依然具有私有特點(diǎn),而且用 (new Person()).name,在運(yùn)行時(shí)也是無法實(shí)現(xiàn)的。即,# 是 ts 語法,但同時(shí)也是 ES 的提案語法,編譯之后也不能失效。

          但是,編譯結(jié)果中,“私有”是通過 WeekMap 來實(shí)現(xiàn)的,所以要確保你的運(yùn)行時(shí)環(huán)境支持 ES6 。WeekMap 沒有完美的 Polyfill 方案,強(qiáng)行 Polyfill 可能會發(fā)生內(nèi)存泄漏。

          //?ts?源碼
          class?Person?{
          ????#name:?string
          ????constructor()?{
          ????????this.#name?=?'zhangsan'
          ????}
          }

          /*?編譯結(jié)果如下
          "use?strict";
          var?__classPrivateFieldSet?=?(this?&&?this.__classPrivateFieldSet)?||?function?(receiver,?privateMap,?value)?{
          ????if?(!privateMap.has(receiver))?{
          ????????throw?new?TypeError("attempted?to?set?private?field?on?non-instance");
          ????}
          ????privateMap.set(receiver,?value);
          ????return?value;
          };
          var?_name;
          class?Person?{
          ????constructor()?{
          ????????_name.set(this,?void?0);
          ????????__classPrivateFieldSet(this,?_name,?'zhangsan');
          ????}
          }
          _name?=?new?WeakMap();
          */

          函數(shù)重載

          java 中的函數(shù)重載

          java 中的函數(shù)重載是非常好用,而且非常好理解的,傻瓜式的,一看就懂。如下代碼,定義了四個(gè)名為 test 的函數(shù),參數(shù)不同。那就直接寫四個(gè)函數(shù)即可,調(diào)用時(shí)也直接調(diào)用,java 會自動匹配。

          public?class?Overloading?{
          ????public?int?test(){
          ????????System.out.println("test1");
          ????????return?1;
          ????}
          ????public?void?test(int?a){
          ????????System.out.println("test2");
          ????}???
          ????public?String?test(int?a,String?s){
          ????????System.out.println("test3");
          ????????return?"returntest3";
          ????}???
          ????public?String?test(String?s,int?a){
          ????????System.out.println("test4");
          ????????return?"returntest4";
          ????}???
          ????public?static?void?main(String[]?args){
          ????????Overloading?o?=?new?Overloading();
          ????????System.out.println(o.test());
          ????????o.test(1);
          ????????System.out.println(o.test(1,"test3"));
          ????????System.out.println(o.test("test4",1));
          ????}
          }

          ts 中的函數(shù)重載

          ts 的函數(shù)重載,先把各個(gè)情況的函數(shù)頭寫出來,然后再寫一個(gè)統(tǒng)一的、兼容上述所有情況的函數(shù)頭。最后,函數(shù)體自行處理參數(shù)。

          class?Person?{
          ????//?第一,各個(gè)情況的函數(shù)頭寫出來
          ????test():?void
          ????test(a:?number,?b:?number):?number
          ????test(a:?string,?b:?string):?string
          ????//?第二,統(tǒng)一的、兼容上述所有情況的函數(shù)頭(有一個(gè)不兼容,就報(bào)錯(cuò))
          ????test(a?:?string?|?number,?b?:?string?|?number):?void?|?string?|?number?{
          ????????//?第三,函數(shù)體自行處理參數(shù)
          ????????
          ????????if?(typeof?a?===?'string'?&&?typeof?b?===?'string')?{
          ????????????return?'string?params'
          ????????}
          ????????if?(typeof?a?===?'number'?&&?typeof?b?===?'number')?{
          ????????????return?'number?params'
          ????????}
          ????????console.log('no?params')
          ????}
          }

          這和 java 的語法比起來,簡直就是復(fù)雜 + 丑陋,完全違背設(shè)計(jì)原則。但是,為何要這樣呢?最終還是因?yàn)?ts 只關(guān)注編譯時(shí),管不了運(yùn)行時(shí) —— 這是原罪。試想,如果 ts 也設(shè)計(jì)像 java 一樣的重載寫法,那編譯出來的 js 代碼就會亂套的。因?yàn)?js 是弱類型的。

          注意函數(shù)定義的順序

          參數(shù)越精準(zhǔn)的,放在前面。

          /*?錯(cuò)誤:any 類型不精準(zhǔn),應(yīng)該放在最后?*/
          declare?function?fn(x:?any):?any;
          declare?function?fn(x:?HTMLElement):?number;
          declare?function?fn(x:?HTMLDivElement):?string;

          var?myElem:?HTMLDivElement;
          var?x?=?fn(myElem);?//?x:?any,?wat?

          不要為僅在末尾參數(shù)不同時(shí)寫不同的重載,應(yīng)該盡可能使用可選參數(shù)。

          /*?錯(cuò)誤?*/
          interface?Example1?{
          ????diff(one:?string):?number;
          ????diff(one:?string,?two:?string):?number;
          ????diff(one:?string,?two:?string,?three:?boolean):?number;
          }

          /*?OK?*/
          interface?Example2?{
          ????diff(one:?string,?two?:?string,?three?:?boolean):?number;
          }

          DOM 相關(guān)的類型

          Vue 和 React 框架的普及,讓大部分業(yè)務(wù)開發(fā)者不用直接操作 DOM ,變成了框架工程師。但 Web 是基于 DOM 的,可以不用,但千萬不要忘記。

          js 寫 DOM 操作非常簡單,不用關(guān)心類型,直接訪問屬性和方法即可。但用 ts 之后,就得關(guān)心 DOM 操作的相關(guān)類型。

          不光我們使用 ts ,微軟在設(shè)計(jì) ts 時(shí),也需要定義 DOM 操作相關(guān)的類型,放在 ts 的類庫中,這樣 ts 才能被 web 場景所使用。這些都定義在 lib.dom.d.ts 中。補(bǔ):還有 ES 語法的內(nèi)置類庫,也在同目錄下。

          PS:一門成熟可用的編程語言,最基本的要包括:語法 + 類庫 + 編譯器 + 運(yùn)行時(shí)(或者編譯器和運(yùn)行時(shí)統(tǒng)一為解釋器)。然后再說框架,工具,包管理器等這些外圍配置。

          Node Element 等類型

          這些都是現(xiàn)成的,W3C 早就定義好了的,我們直接回顧一下就可以。我覺得一張圖就可以很好的表達(dá),詳細(xì)的可以參考各自的 MDN 文檔。

          事件參數(shù)類型

          在使用 ts 之前,我并沒有特別關(guān)注事件參數(shù)類型(或者之前看過,后來不用,慢慢忘了),反正直接獲取屬性,拿來用就可以。

          document.body.addEventListener('click',?e1?=>?{
          ????// e1 的構(gòu)造函數(shù)是什么?
          })
          document.body.addEventListener('keyup',?e2?=>?{
          ????// e2 的構(gòu)造函數(shù)是什么?
          })

          于是我查了一下 MDN 的文檔,其實(shí)也很好理解,就是不同的事件,參數(shù)類型是不一樣的,當(dāng)然屬性、方法也就不一樣。下面列出我們常見的,所有的類型參考 MDN 這個(gè)文檔。

          事件參數(shù)類型
          click dbclick mouseup mousedown mousemove mouseenter mouseleaveMouseEve
          keyup keyrpess keydownKeyboardEvent
          compositionstart compositionupdate compositionend(輸入法)CompositionEvent
          focus blur focusin focusoutFocusEvent
          drag dropDragEvent
          paste cut copyClipboardEvent

          他們的繼承關(guān)系如下圖。其中 UIEvent 表示的是用戶在 UI 觸發(fā)的一些事件。因?yàn)槭录粌H僅是用戶觸發(fā)的,還有 API 腳本觸發(fā)的,所以要單獨(dú)拿出一個(gè) UIEvent ,作為區(qū)分。

          總結(jié)

          我感覺重點(diǎn)的就是那句話:ts 是一門靜態(tài)類型語言,但它要編譯成為 js 這個(gè)弱類型語言來執(zhí)行,所以它管得了編譯時(shí),卻管不了運(yùn)行時(shí)。這是很多問題的根本。

          目前看來,前端社區(qū)會慢慢往 ts 轉(zhuǎn)型,所以能熟練使用 ts 已經(jīng)是一名前端人員必備的技能。希望本文能給大家?guī)硪稽c(diǎn)點(diǎn)幫助。

          ??愛心三連擊

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

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

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

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

          瀏覽 93
          點(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>
                  欧美婷婷五月天 | 韩国一区二区三区在线 | 黄笔毛片大全免费观看 | 国产一级无码免费视频 | 色老板精品永久免费视频 |