国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

10個幫助你捕獲更多Bug的TypeScript建議

開發 前端
本文介紹了10個可以幫助你獲得更多Bug的TypeScript建議,一起來看一下吧。

[[321286]] 

1. 對TypeScript提供運行時檢查的思考

有一個對TypeScript常見的誤解是:一個變量只要標注了類型,那么它總是會檢查自己的數據類型是否與我們的預期一致。

與該誤解相呼應的想法會認為:對一個從后端返回的對象進行類型標注可以在代碼運行時執行檢查來確保對象類型的正確性。

然而這個想法是錯誤的!因為TypeScript最終是被編譯成JavaScript代碼,并且瀏覽器中運行的也是JavaScript。此時(譯者注:運行時)所有的類型信息都丟失了,所以TypeScript無法自動驗證類型。

理解這一點的一個好方法是查看編譯后的代碼: 

  1. interface Person {  
  2.   name: string;  
  3.   age: number;  
  4.  
  5. function fetchFromBackend(): Promise<Person> {  
  6.   return fetch('http://example.com')  
  7.       .then((res) => res.json())  
  8.  
  9. // 編譯后  
  10. function fetchFromBackend() {  
  11.   return fetch('http://example.com')  
  12.       .then(function(res) {  
  13.         return res.json(); 
  14.        })  

可以看到接口定義在編譯后已經完全消失了,而且這里也不會有任何驗證性的代碼。

不過你最好可以自己去執行運行時校驗,許多庫(譯者注:io-ts)能幫你做到這點。不過,請記住,這一定會帶來性能開銷。

* 考慮對所有外部提供的對象執行運行時檢查(例如從后端獲取的對象,JSON反序列化的對象等)

2. 不要將類型定義為any

使用TypeScript時,可以將變量或函數參數的類型聲明為any,但是這樣做也意味著該變量脫離了類型安全保障。

不過聲明為any類型也會有好處,在某種場景下很有幫助(例如將類型逐步添加到現有的JavaScript代碼庫中,譯者注:一般是將代碼庫從js升級到ts時)。但是它也像一個逃生艙口,會大大降低代碼的類型安全性。

當類型安全涵蓋盡可能多的代碼時,它是最有效的。否則,安全網中會存在漏洞,漏洞可能會通過漏洞傳播。例如:如果函數返回any,則使用其返回值的所有表達式類型也將變成any。

所以你應該盡量避免使用any類型。幸運的是,TypeScript3.0引入了類型安全的替代方案——unknown。可以將任何值賦給unknown類型的變量,但是不能將unknown類型的變量的值賦給任何變量(這點不同于any)。

如果你的函數返回的是unknown類型的值,則調用方需要執行檢查(使用類型保護),或至少將值顯式轉換為某個特定類型。(譯者注:如果對這段不理解,可以參考下這篇文章,unknown 類型 中的示例部分) 

  1. let foo: any;  
  2. // anything can be assigned to foo  
  3. foo = 'abc' 
  4. // foo can be assigned to anything  
  5. const x: number = foo 
  6. let bar: unknown;  
  7. // anything can be assigned to bar  
  8. bar = 'abc' 
  9. // COMPILE ERROR! Type 'unknown' is not assignable to type 'number'.  
  10. const y: number = bar

使用unknown類型有時會有些麻煩,但是這也會讓代碼更易于理解,并且讓你在開發時更加注意。

另外,你需要開啟noImplicitAny,每當編譯器推斷某個值的類型為any時就會拋出錯誤。換句話說,它讓你顯式的標注出所有會出現any的場景。

盡管最終目標還是消除有any的情況,但明確申明any仍然是有益的:例如在code review時可以更容易捕獲他們。

* 不要使用any類型并開啟noImplicitAny

3. 開啟strictNullChecks

你已經見過多少次這樣的報錯信息了? 

  1. TypeError: undefined is not an object 

我打賭有很多次了,JavaScript(甚至是軟件編程)中最常見的bug來源之一就是忘記處理空值。

在JavaScript中用null或undefined來表示空值。開發者們經常樂觀的認為給定的變量不會是空的,于是就忘記處理空值的情況。

  1. function printName(person: Person) {  
  2.   console.log(person.name.toUpperCase());  
  3.  
  4. // RUNTIME ERROR!  TypeError: undefined is not an object     
  5. // (evaluating 'person.name')   
  6. printName(undefined); 

通過開啟strictNullChecks,編譯器會迫使你去做相關的檢查,這對防止出現這種常見問題起到了重要的作用。

默認情況下,typescript的每個類型都包含null和undefined這兩個值。也就是說,null和undefined可以被賦值給任意類型的任何變量。

而開啟strictNullChecks會更改該行為。由于無法將undefined作為Person類型的參數傳遞,因此下方的代碼會在編譯時報錯。 

  1. // COMPILE ERROR!   
  2. // Argument of type 'undefined' is not assignable to parameter of type 'Person'. printName(undefined);  

那如果你確實就想將undefined傳遞給printName怎么辦?那你可以調整類型簽名,但是仍然會要求你處理undefined的情況。 

  1. function printName(person: Person | undefined) {  
  2.   // COMPILE ERROR!  
  3.   // Object is possibly 'undefined'.   
  4.      console.log(person.name.toUpperCase());  

你可以通過確保person是被定義的來修復這個錯誤: 

  1. function printName(person: Person | undefined) {   
  2.     if (person) {  
  3.         console.log(person.name.toUpperCase());  
  4.     }  
  5. }  

不幸的是,strictNullChecks默認是不開啟的,我們需要在tsconfig.json中進行配置。

另外,strictNullChecks是更通用的嚴格模式的一部分,可以通過strict標志啟用它。你絕對應該這樣做!因為編譯器的設置越嚴格,你就可以盡早發現更多bug。

* 始終開啟strictNullChecks

4. 開啟strictPropertyInitialization

strictPropertyInitialization是屬于嚴格模式標志集的另一個標志。尤其在使用Class時開啟strictPropertyInitialization很重要,它其實有點像是對strictNullChecks的擴展。

如果不開啟strictPropertyInitialization的話,TS會允許以下的代碼: 

  1. class Person {  
  2.   name: string;  
  3.   sayHello() {  
  4.     // RUNTIME ERROR!  
  5.     console.log( `Hello from ${this.name.toUpperCase()}`);  
  6.   }  
  7. }  

這里有個很明顯的問題:this.name沒有被初始化,因此在運行時調用sayHello就會報錯。

造成這個錯誤的根本原因是這個屬性沒有在構造函數里或使用屬性初始化器賦值,所以它(至少在最初)是undefined,因此他的類型就會變成string | undefined。

開啟strictPropertyInitialization會提示以下錯誤: 

  1. Property 'name' has no initializer and is not assigned in the constructor. 

當然,如果你在構造函數里或使用屬性初始化器賦值了,這個錯誤也就會消失。

* 始終開啟strictPropertyInitialization

5. 記得指定函數的返回類型

TypeScript使你可以高度依賴類型推斷,這意味著只要在TS能推斷類型的地方,你就不需要標注類型。

然而這就像一把雙刃劍,一方面,它非常方便,并且減少了使用TypeScript的麻煩。而另一方面,有時推斷的類型可能會和你的預期不一致,從而降低了使用靜態類型提供的保障。

在下方的例子中,我們沒有注明返回類型,而是讓TypeScript來推斷函數的返回值。 

  1. interface Person {  
  2.     name: string;  
  3.     age: number;  
  4.  
  5. function getName(person: Person | undefined) {  
  6.     if (person && person.name) {  
  7.         return person.name;  
  8.     } else if (!person) {  
  9.         return "no name";  
  10.     }  

乍看之下,我們可能認為我們的方法很安全,并且始終返回的是string類型,然而,當我們明確聲明該函數的(預期)返回類型時就會發現報了一個錯。 

  1. // COMPILE ERROR!   
  2. // Function lacks ending return statement and return type does not include 'undefined'.   
  3. function getName(person: Person | undefined): string   
  4.  
  5.     // ...   

順便說一句,這個錯誤只有當你開啟了strictNullChecks才會被檢測出來。

上述錯誤表明getName函數的返回值沒有覆蓋到一種情況:當person不為空,但是person.name為空的情況。這種情況所有if條件都不等于true,所以會返回undefined。

因此,TypeScript推斷此函數的返回類型為string | underfined,而我們聲明的卻是string。(譯者注:所以主動聲明函數返回值類型有助于幫我們提前捕捉一些不易察覺的bug)

* 始終標注函數的返回值類型

6. 不要將隱式類型變量存儲到對象中

TypeScript的類型檢查有時很微妙。

通常,當類型A至少具有和類型B相同的屬性,那么TypeScript就允許將類型A的對象賦值給類型B的變量。這意味著它可以包含其他屬性。 

  1. // 譯者舉例:  
  2. type A = {  
  3.     name: string;  
  4.     age: number;  
  5. };  
  6. type B = {  
  7.     name: string; 
  8. };  
  9. let a: A = {  
  10.     name: 'John',  
  11.     age: 12,  
  12. };  
  13. let b: B;  
  14. // compile success  
  15. b = a

然而如果直接傳遞的是對象字面量,其行為是不同的。只有目標類型包含相同的屬性時,TypeScript才會允許它(傳遞)。此時不允許包含其他屬性。 

  1. interface Person {  
  2.     name: string; 
  3.   
  4. function getName(person: Person): string | undefined {  
  5.     // ...  
  6.  
  7. // ok  
  8. getName({ name: 'John' });  
  9. // COMPILE ERROR  
  10. // Argument of type '{ name: string; age: number; }' is not assignable to parameter of type 'Person'.  
  11. getName({ name: 'John', age: 30 }); 

如果我們不是直接傳對象字面量,而是將對象存到常量里(再傳遞),這看起來沒有什么區別。然而這卻更改了類型檢查的行為: 

  1. const person = { name: 'John', age: 30 };   
  2. // OK   
  3. getName(person);  

傳遞額外的屬性可能會引起bug(例如當你想合并兩個對象時)。了解這個行為并且在可能的情況下,直接傳遞對象字面量。

* 請注意如何將對象傳遞給函數并且始終要考慮傳遞額外的屬性是否安全

7. 不要過度使用類型斷言

盡管TypeScript能對你的代碼進行很多推斷,但有時候你會比TypeScript更了解某個值的詳細信息。這時你可以通過類型斷言這種方式可以告訴編譯器,“相信我,我知道自己在干什么”。

比如說對一個從服務器請求回來的對象斷言,或者將一個子類型的對象斷言為父類型。

類型斷言需要保守使用。比如絕對不能在函數傳參類型不匹配時使用。

有一種更安全的使用類型斷言的方式:類型保護。類型保護是一個當返回true時能斷言其參數類型的函數。它可以提供代碼運行時的檢測,讓我們對傳入的變量是否符合預期這點上更有信心。

下面的代碼中,我們需要使用類型斷言,因為TypeScript不知道從后端返回的對象的類型。 

  1. interface Person {  
  2.     name: string;  
  3.     age: number;  
  4.  
  5. declare const fetchFromBackend: (url: string) => Promise<object> 
  6. declare const displayPerson: (person: Person) => void;  
  7. fetchFromBackend('/person/1').then((person) => displayPerson(person as Person)); 

我們可以通過使用類型保護,提供一個簡單的運行時檢查來讓代碼更完善。我們假設一個對象只要擁有了name和age屬性那么它的類型就是Person。 

  1. const isPerson = (obj: Object): obj is Person => 'name' in obj && 'age' in obj;  
  2. fetchFromBackend('/person/1').then((person) => {  
  3.   if(isPerson(person)) {  
  4.     // Type of `person` is `Person` here!  
  5.     displayPerson(person);  
  6.   }  
  7. }) 

你可以發現,多虧了類型保護,在if語句中person的類型已經可以被正確推斷了。

* 考慮使用類型保護來替代類型斷言

8. 不要對Partial類型使用擴展運算符

Partial是一個非常有用的類型,它的作用是將源類型的每個屬性都變成可選的。

Partial有個好的實際使用場景:當你有一個表示配置或選項的對象類型,并且想要創建一個該配置對象的子集來覆寫它。

你可能會寫出如下的代碼: 

  1. interface Settings {  
  2.   a: string;  
  3.   b: number;  
  4.  
  5. const defaultSettings: Settings = { /* ... */ };   
  6. function getSettings(overrides: Partial<Settings>): Settings {  
  7.   return { ...defaultSettings, ...overrides }; 

這看起來還不錯,但實際上揭示了TypeScript的類型系統中的一個漏洞。

看下方的代碼,result的類型是Settings,然而result.a的值卻是undefined了。 

  1. const result = getSettings({ a: undefined, b: 2 }); 

由于擴展Partial是一種常見的模式,并且TypeScript的目標之一是在嚴格性和便利性之間取得平衡,所以可以說是TypeScript本身的設計帶來了這種不一致性。但是,意識到該問題仍然非常重要。

* 除非你確定對象里不包含顯式的undefined,否則不要對Parital對象使用擴展運算符

9. 不要過于相信Record類型

這是TypeScript內置類型定義中的一個微妙情況的另一個示例。

Record定義了一個對象類型,其中所有key具有相同的類型,所有value具有相同的類型。 這非常適合表示值的映射和字典。

換句話說,Record<KeyType, ValueType> 等價于 { [key: KeyType]: ValueType }。

從下方代碼你可以看出,通過訪問record對象的屬性返回的值的類型應該和ValueType保持一致。然而你會發現這不是完全正確的,因為abc的值會是undefined。 

  1. const languages: Record<string, string> = {  
  2.     'c++': 'static',  
  3.     'java': 'static',  
  4.     'python': 'dynamic',  
  5. };  
  6. const abc: string = languages['abc']; // undefined 

這又是一個TypeScript選擇了便利性而不是嚴格性的例子。雖然大多數例子中這樣使用都是可以的,但是你仍然要小心些。

最簡單的修復方式就是使Record的第二個參數可選: 

  1. const languages: Partial<Record<string, string>> = {  
  2.     'c++': 'static',  
  3.     'java': 'static', 
  4.     'python': 'dynamic',  
  5. }; 
  6. const abc = languages['abc']; // abc is infer to string | underfined 

* 除非你確保沒問題,否則可以始終保持Record的值類型參數(第二個參數)可選

10. 不要允許出現不合格的類型聲明

在定義業務域對象的類型時,通常會遇到類似以下的情況: 

  1. interface Customer {  
  2.     acquisitionDate: Date;  
  3.     type: CustomerType;  
  4.     firstName?: string;  
  5.     lastName?: string;  
  6.     socialSecurityNumber?: string;  
  7.     companyName?: string;  
  8.     companyTaxId?: number;  

這個對象包含很多可選的對象。其中一些對象是當Customer表示人時(type === CustomerType.Individual)才有意義且必填,另外的則是當Custormer表示公司時(type === CustomerType.Institution)必填。

問題在于Customer類型不能反映這一點! 換句話說,它允許屬性的某些非法組合(例如,lastName和companyName都未定義)

這確實是有問題的。 你要么執行額外的檢查,要么使用類型斷言來消除基于type屬性值的某些字段的可選性。

幸運的是,有一個更好的解決方案——辨析聯合類型。辨析聯合類型是在聯合類型的基礎上增加了一個功能:在運行時可以區分不同的方案。

我們將Customer類型重寫為兩種類型:Individual和Institution的聯合,各自包含一些特定的字段,并且有一個共有字段:type,它的值是一個字符串。此字段允許運行時檢查,并且TypeScript知道可以專門處理它。 

  1. interface Individual {  
  2.   kind: 'individual';  
  3.   firstName: string; 
  4.   lastName: string;  
  5.   socialSecurityNumber: number;  
  6.  
  7. interface Institution {  
  8.   kind: 'institutional';  
  9.   companyName: string;  
  10.   companyTaxId: number;  
  11.  
  12. type Customer = Individual | Institution; 

辨析聯合類型真正酷的地方是TypeScript提供了內置的類型保護,可以讓你避免類型斷言。 

  1. function getCustomerName(customer: Customer) {  
  2.   if (customer.kind === 'individual') {  
  3.     // The type of `customer` id `Individual`  
  4.     return customer.lastName;  
  5.   } else {  
  6.     // The type of `customer` id `Institution`  
  7.     return customer.companyName;  
  8.   }  

* 當遇到復雜的業務對象時盡量考慮使用辨析聯合類型。這可以幫你創建更貼合現實場景的類型

文章到此結束了!我希望這個列表可以像幫助我一樣,幫助你捕獲許多討厭的bug。

接下來是這篇文章所有建議的總結:

  1.  考慮對所有外部提供的對象執行運行時檢查(例如從后端獲取的對象,JSON反序列化的對象等)
  2.  不使要用any類型并開啟noImplicitAny
  3.  始終開啟strictNullChecks
  4.  始終開啟strictPropertyInitialization
  5.  始終標注函數的返回值類型
  6.  請注意如何將對象傳遞給函數并且始終要考慮傳遞額外的屬性是否安全
  7.  考慮使用類型保護來替代類型斷言
  8.  除非你確定對象里不包含顯式的undefined,否則不要對Parital對象使用擴展運算符
  9.  除非你確保沒問題,否則可以始終保持Record的值類型參數(第二
  10.  當遇到復雜的業務對象時盡量考慮使用辨析聯合類型。 

 

責任編輯:龐桂玉 來源: segmentfault
相關推薦

2018-08-23 17:45:52

2019-09-16 08:26:13

Kubernetes工具Katacoda

2019-07-16 08:58:38

LinuxDocker軟件

2014-03-17 09:57:54

2023-06-19 15:36:30

JavaScrip技巧開發

2017-02-16 10:45:57

桌面Wiki

2021-09-07 05:48:47

個人信息數據安全法數據安全

2018-07-04 09:00:45

備份應用軟件

2022-10-08 14:26:09

開發Web工具

2022-03-01 20:20:18

云遷移云計算

2019-06-18 07:15:22

Linux拼寫look命令

2019-05-06 12:10:01

Windows 10Bug補丁

2024-09-04 14:00:16

2024-02-28 07:48:05

Rust項目框架

2023-06-27 10:09:59

ChatGPT大型語言模型

2023-09-12 14:38:08

開發JavaScript

2014-04-08 10:22:29

Android高效開發App

2015-06-12 11:22:52

程序員程序員跳槽

2013-05-16 10:27:03

技術學習新技術學習建議

2022-08-12 07:08:33

釣魚攻擊網絡釣魚
點贊
收藏

51CTO技術棧公眾號

好操啊在线观看免费视频| 中文字幕在线一区| 色一情一乱一伦一区二区三区丨| 99成人免费视频| 日韩在线导航| www.在线欧美| 三级ai视频| 91精品国产日韩91久久久久久| 手机电影在线观看| 久久久精品日本| av中字幕久久| 日本高清视频一区二区三区| 国产毛片精品国产一区二区三区| 欧美日韩在线不卡视频| 亚洲色图在线视频| 成人欧美一区| 国产亚洲精品va在线观看| 精品女人视频| 国产伦精品一区二区三区高清| 精品一区在线看| 四虎最新地址发布| 欧美女孩性生活视频| 成人免费一区| 91精品久久久久久久久久| 石原莉奈在线亚洲二区| 美女av免费在线观看| 亚洲少妇视频| 97国产精品久久| 国产视频欧美| 9久久婷婷国产综合精品性色 | 国产精品一线天粉嫩av| 国产精品国产三级国产专区53| 国产一区二区三区免费在线观看 | 国产精品欧美一级免费| 日本免费不卡| 亚洲精品有码在线| 国产日产精品一区二区三区四区的观看方式| 91九色在线免费视频| 精品亚洲国产成人av制服丝袜| www99热| 亚洲第一色中文字幕| 九色精品国产蝌蚪| 男人日女人的bb| 91国偷自产一区二区开放时间| av亚洲一区| 久久国产精品99久久久久久丝袜 | 一区二区三区导航| 尤物yw193can在线观看| 欧美一二三视频| 日韩av一级电影| 日本一区二区精品| 精品肉辣文txt下载| 国产精品偷伦视频免费观看国产 | 久久精品国产成人| 91久久夜色精品国产九色| 欧美激情国产精品日韩| 91精品国产色综合久久久蜜香臀| 99精品在免费线中文字幕网站一区 | 天堂一区二区三区四区| 视频在线一区二区三区| 亚洲国产另类av| 国产激情一区二区三区在线观看| 国产69精品久久777的优势| 国产裸舞福利在线视频合集| 欧美激情一区二区三级高清视频| 乱码第一页成人| 欧美色视频免费| 国产va免费精品高清在线观看| av中文字幕亚洲| 51漫画成人app入口| 国产精品高清一区二区三区| 亚洲人午夜精品天堂一二香蕉| 欧美色片在线观看| 亚洲图片在线观看| 欧美成人精品高清在线播放| 国产综合网站| 欧美视频免费一区二区三区| 日产精品久久久一区二区福利| 久久久久亚洲综合| 亚洲欧洲专区| 麻豆tv在线播放| 国产亚洲精品久久久久动| 麻豆精品视频在线| 日本在线观看高清完整版| 精品国产一区二区三区麻豆小说| 午夜激情久久久| 成人激情开心网| 神马午夜dy888| 国产成人亚洲综合青青| 亚洲人成精品久久久久| 网友自拍一区| 日本激情免费| 国产精品视频免费在线观看| 亚洲综合一二三区| 久久蜜桃av| 岛国最新视频免费在线观看| 91亚洲午夜在线| 国产欧美日韩在线观看视频| 色网在线免费观看| av在线中文| 亚洲男人第一av网站| 青青草91视频| h片在线观看| 一区二区欧美日韩| 精品视频中文字幕| 东方aⅴ免费观看久久av| 99re久久| 久久久久久久少妇| 欧美做受高潮1| 天天操天天色综合| 亚洲高清毛片| 国产理论在线| 免费高清在线观看免费| 97色在线视频观看| 亚洲3atv精品一区二区三区| 免费看日b视频| 久久久www成人免费精品| 国产欧美日韩不卡免费| 亚洲欧洲色图| 桃花色综合影院| 欧美午夜欧美| 在线观看成人黄色| 国产精品国模大尺度视频| 日韩在线中文| 日本精品600av| 欧美s码亚洲码精品m码| 欧洲成人在线观看| 精品视频资源站| 国产九九视频一区二区三区| 精品国产乱码久久久久久樱花| 国产中文字幕第一页| 国产传媒欧美日韩| 日韩av影视综合网| 日本一区二区不卡视频| 久久亚洲国产| 欧美aaa免费| 韩国日本美国免费毛片| 91精品在线播放| 亚洲国产三级网| 国产欧美中文在线| 综合日韩在线| 奇米777日韩| 午夜神马福利影院| 日韩av电影免费在线观看| 久久久成人精品| 午夜精品久久久久久久久| 男人的j进女人的j一区| 一区二区三区高清在线观看| 日本亚洲欧美| 国产曰肥老太婆无遮挡| 国产精品久久久久久久久久新婚| 欧美一区日韩一区| 亚洲国产精品传媒在线观看| 欧美久久一级| 天天综合在线观看| 国产爆初菊在线观看免费视频网站 | 亚洲综合小说| 色豆豆成人网| 在线电影av| 国产精品视频网站在线观看| 国产精品你懂得| 亚洲视频视频在线| 欧美日韩一区免费| 成人黄色国产精品网站大全在线免费观看| 日韩国产一区二区| 蜜桃视频成人m3u8| aaa欧美日韩| 久久久久久久一| 女同一区二区免费aⅴ| 风间由美一区二区三区在线观看| 精品久久久av| 欧美三级一级片| 欧美日韩一区二区三区不卡视频| 国产精品久久久久国产精品日日| 亚洲电影一级黄| 久久久亚洲国产| 无码精品国产一区二区三区免费| 在线一区av| 久久成人免费| 亚洲一级不卡视频| 国产精品黄色影片导航在线观看| 免费黄网站在线播放| 亚洲综合三区| 狠狠色狠狠色综合日日小说| 欧亚精品中文字幕| av色综合久久天堂av色综合在| 6080亚洲理论片在线观看| 中文字幕不卡在线播放| 国产在线精品日韩| 国产精品久久久久久久久免费高清| 成人动漫一区二区三区| www.亚洲人.com| 在线观看视频污| 看片一区二区| 婷婷综合视频| 国产婷婷色一区二区三区在线| 欧美性生活大片免费观看网址| 亚洲天堂av电影| 成人网在线免费观看| 日本国产在线播放|