編寫高質量可維護的代碼:Awesome TypeScript

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"高質量可維護的代碼應具備可讀性高、結構清晰、低耦合、易擴展等特點。而原生的 JavaScript 由於其弱類型和沒有模塊化的缺點,不利於大型應用的開發和維護,因此,TypeScript 也就應運而生。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TypeScript 是 JavaScript 的一個超集,它的設計初衷並不是爲了替代 JavaScript,而是基於 JavaScript 做了一系列的增強,包括增加了靜態類型、接口、類、泛型、方法重載等等。所以,只要你有一定的 JavaScript 功底,那麼 TypeScript 上手就非常簡單。並且,你可以在 TypeScript 中愉快的使用 JavaScript 語法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下去,本文將給大家分享下,TypeScript 的重要特性以及在實際場景中的使用技巧,幫助大家更高效的編寫高質量可維護的代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Typescript VS Javascript"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"JavaScript"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript 是動態類型語言,在代碼編譯階段不會對變量進行類型檢測,從而會把潛在的類型錯誤帶到代碼執行階段。並且在遇到不同類型變量的賦值時,會自動進行類型轉換,帶來了不確定性,容易產生 Bug。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript 原生沒有命名空間,需要手動創建命名空間,來進行模塊化。並且,JavaScript 允許同名函數的重複定義,後面的定義可以覆蓋前面的定義。這也給我們開發和維護大型應用帶來了不便。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"TypeScript"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TypeScript 是靜態類型語言,通過類型註解提供編譯時的靜態類型檢查。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在代碼編譯階段會進行變量的類型檢測,提前暴露潛在的類型錯誤問題。並且在代碼執行階段,不允許不同類型變量之間的賦值。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"清晰的類型註解,不僅讓代碼的可讀性更好,同時也增強了 IDE 的能力,包括代碼補全、接口提示、跳轉到定義等等。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TypeScript 增加了模塊類型,自帶命名空間,方便了大型應用的模塊化開發。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TypeScript 的設計一種完全面向對象的編程語言,具備模塊、接口、類、類型註解等,可以讓我們的代碼組織結構更清晰。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過上述對比,可以看到 TypeScript 的出現很好的彌補了 JavaScript 的部分設計缺陷,給我們帶來了很大的便利,也提高了代碼的健壯性和擴展性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"重要特性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"數據類型"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基礎數據類型包括:Boolean、Number、String、Array、Enum、Any、Unknown、Tuple、Void、Null、Undefined、Never。下面選擇幾個 TypeScript 特有的類型進行詳解:"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Enum 枚舉:在編碼過程中,要避免使用硬編碼,如果某個常量是可以被一一列舉出來的,那麼就建議使用枚舉類型來定義,可以讓代碼更易維護。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"  \/\/ 包括 數字枚舉、字符串枚舉、異構枚舉(數字和字符串的混合)。\n  \/\/ 數字枚舉在不設置默認值的情況下,默認第一個值爲0,其他依次自增長\n  enum STATUS {\n    PENDING,\n    PROCESS,\n    COMPLETED,\n  }\n  let status: STATUS = STATUS.PENDING;  \/\/ 0\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Any 類型:不建議使用。Any 類型爲頂層類型,所有類型都可以被視爲 any 類型,使用 Any 也就等同於讓 TypeScript 的類型校驗機制失效。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Unknown 類型:Unknown 類型也是頂層類型,它可以接收任何類型,但它與 Any 的區別在於,它首次賦值後就確定了數據類型,不允許變量的數據類型進行二次變更。所以,在需要接收所有類型的場景下,優先考慮用 Unknown 代替 Any。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Tuple 元組:支持數組內存儲不同數據類型的元素,讓我們在組織數據的時候更靈活。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"let tupleType: [string, boolean];\ntupleType = [\"momo\", true];\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Void 類型:當函數沒有返回值的場景下,通常將函數的返回值類型設置爲 void。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"類型註解"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TypeScript 通過類型註解提供編譯時的靜態類型檢查,可以在編譯階段就發現潛在 Bug,同時讓編碼過程中的提示也更智能。使用方式很簡單,在 "},{"type":"codeinline","content":[{"type":"text","text":":"}]},{"type":"text","text":" 冒號後面註明變量的類型即可。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"const str: string = 'abc';"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"接口"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在面向對象編程的語言裏面,接口是實現程序解耦的關鍵,它只定義具體包含哪些屬性和方法,而不涉及任何具體的實現細節。接口是基於類之上,更進一步對實體或行爲進行抽象,會讓程序具備更好的擴展性。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"應用場景:比如我們在實現訂單相關功能的時候,需要對訂單進行抽象,定義一個訂單的接口,包括訂單基本信息以及對訂單的相關操作,然後基於這個接口來做進一步的實現。後續如果訂單的相關操作功能有變化,只需要重新定義一個類來實現這個接口即可。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"interface Animal {\nname: string;\ngetName(): string;\n}\nclass Monkey implements Padder {\nconstructor(private name: string) {\n  getName() {\n    return 'Monkey: ' + name;\n }\n}\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"類"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TypeScript 的類除了包括最基本的屬性和方法、getter 和 setter、繼承等特性,還新增了私有字段。私有字段不能在包含的類之外訪問,甚至不能被檢測到。Javascript 的類中是沒有私有字段的,如果想模擬私有字段的話,必須要用閉包來模擬。下面用一些示例來說明下類的使用:"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"屬性和方法"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"class Person {\n\/\/ 靜態屬性\nstatic name: string = \"momo\";\n\/\/ 成員屬性\ngender: string;\n\/\/ 構造函數\nconstructor(str: string) {\n  this.gender = str;\n}\n\/\/ 靜態方法\nstatic getName() {\n  return this.name;\n}\n\/\/ 成員方法\ngetGender() {\n  return 'Gender: ' + this.gender;\n}\n}\nlet person = new Person(\"female\");"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"getter 和 setter"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過 getter 和 setter 方法來實現數據的封裝和有效性校驗,防止出現異常數據。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"class Person {\nprivate _name: string;\nget name(): string {\n  return this._name;\n}\nset name(newName: string) {\n  this._name = newName;\n}\n}\nlet person = new Person('momo');\nconsole.log(person.name); \/\/ momo\nperson.name = 'new_momo';\nconsole.log(person.name); \/\/ new_momo\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"繼承"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"class Animal {\nname: string;\nconstructor(nameStr=:string) {\n  this.name = nameStr;\n}  \nmove(distanceInMeters: number = 0) {\n  console.log(`${this.name} moved ${distanceInMeters}m.`);\n}\n}\nclass Snake extends Animal {\nconstructor(name: string) {\n  super(name);\n} \nmove(distanceInMeters = 5) {\n  super.move(distanceInMeters);\n}\n}\nlet snake = new Snake('snake');\nsnake.move(); \/\/ 輸出:'snake moved 5m'"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"私有字段"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"私有字段以 "},{"type":"codeinline","content":[{"type":"text","text":"#"}]},{"type":"text","text":" 字符開頭。私有字段不能在包含的類之外訪問,甚至不能被檢測到。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"class Person {\n#name: string;\nconstructor(name: string) {\n  this.#name = name;\n}\ngreet() {\n  console.log(`Hello, ${this.#name}!`);\n}\n}\nlet person = new Person('momo');\nperson.#name;   \/\/ 訪問會報錯"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"泛型"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"應用場景:當我們需要考慮代碼的可複用性時,就需要用到泛型。讓組件不僅能夠支持當前的數據類型,同時也能支持未來的數據類型。泛型允許同一個函數接受不同類型參數,相比於使用 Any 類型,使用泛型來創建的組件可複用和易擴展性要更好,因爲泛型會保留參數類型。泛型可以應用於接口、類、變量。下面用一些示例來說明下泛型的使用:"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"泛型接口"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"  interface identityFn {\n    (arg: T): T;\n  }"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"泛型類"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"  class GenericNumber {\n    zeroValue: T;\n    add: (x: T, y: T) => T;\n  }\n  let myGenericNumber = new GenericNumber();\n  myGenericNumber.zeroValue = 0;\n  myGenericNumber.add = function (x, y) {\n    return x + y;\n  };"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"泛型變量"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用大寫字母 A-Z 定義的類型變量都屬於泛型,常見泛型變量如下:"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"T(Type):表示一個 TypeScript 類型"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"K(Key):表示對象中的鍵類型"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V(Value):表示對象中的值類型"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"E(Element):表示元素類型"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"交叉類型"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"交叉類型就是將多個類型合併爲一個類型。通過 "},{"type":"codeinline","content":[{"type":"text","text":"&"}]},{"type":"text","text":" 運算符定義。如下示例中,將 Person 類型和 Company 類型合併後,生成了新的類型 Staff,該類型同時具備這兩種類型的所有成員。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"interface Person {\nname: string;\ngender: string;\n}\ninterface Company {\ncompanyName: string;\n}\ntype Staff = Person & Company;\nconst staff: Staff = {\nname: 'momo',\ngender: 'female',\ncompanyName: 'ZCY'\n};"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"聯合類型"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"聯合類型就是由具有或關係的多個類型組合而成,只要滿足其中一個類型即可。通過 "},{"type":"codeinline","content":[{"type":"text","text":"|"}]},{"type":"text","text":" 運算符定義。如下示例中,函數的入參爲 String 或 Number 類型即可。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"function fn(param: string | number): void {\n  console.log(\"This is the union type\");\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"類型保護"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類型保護就是在我們已經識別到當前數據是某種數據類型的情況下,安全的調用這個數據類型對應的屬性和方法。常用的類型保護包括 "},{"type":"codeinline","content":[{"type":"text","text":"in"}]},{"type":"text","text":" 類型保護、"},{"type":"codeinline","content":[{"type":"text","text":"typeof"}]},{"type":"text","text":" 類型保護、"},{"type":"codeinline","content":[{"type":"text","text":"instanceof"}]},{"type":"text","text":" 類型保護和 "},{"type":"codeinline","content":[{"type":"text","text":"自定義"}]},{"type":"text","text":" 類型保護。具體見以下示例:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"in"}]},{"type":"text","text":" 類型保護"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"  interface Person {\n    name: string;\n    gender: string;\n  }\n  interface Employee {\n    name: string;\n    company: string;\n  }\n  type UnknownStaff = Person | Employee;\n  function getInfo(staff: UnknownStaff) {\n    if (\"gender\" in staff) {\n      console.log(\"Person info\");\n    }\n    if (\"company\" in staff) {\n      console.log(\"Employee info\");\n    }\n  }\n"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"typeof"}]},{"type":"text","text":" 類型保護"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"  function processData(param: string | number): unknown {\n   if (typeof param === 'string') {\n     return param.toUpperCase()\n    }\n    return param;\n  }\n"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"instanceof"}]},{"type":"text","text":" 類型保護:和 "},{"type":"codeinline","content":[{"type":"text","text":"typeof"}]},{"type":"text","text":" 類型用法相似,它主要是用來判斷是否是一個類的對象或者繼承對象的。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"  function processData(param: Date | RegExp): unknown {\n   if (param instanceof Date) {\n     return param.getTime();\n    }\n    return param;\n  }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"自定義"}]},{"type":"text","text":" 類型保護:通過類型謂詞 "},{"type":"codeinline","content":[{"type":"text","text":"parameterName is Type"}]},{"type":"text","text":" 來實現自定義類型保護。如下示例,實現了接口的請求參數的類型保護。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"  interface ReqParams {\n   url: string;\n    onSuccess?: () => void;\n    onError?: () => void;\n  }\n  \/\/ 檢測 request 對象包含參數符合要求的情況下,才返回 url\n  function validReqParams(request: unknown): request is ReqParams {\n   return request && request.url\n  }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"開發小技巧"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要連續判斷某個對象裏面是否存在某個深層次的屬性,可以使用 "},{"type":"codeinline","content":[{"type":"text","text":"?."}]}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"if(result && result.data && result.data.list) \/\/ JS\nif(result?.data?.list) \/\/ TS\n"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"聯合判斷是否爲空值,可以使用 "},{"type":"codeinline","content":[{"type":"text","text":"??"}]}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"let temp = (val !== null && val !== void 0 ? val : '1'); \/\/ JS\nlet temp = val ?? '1'; \/\/ TS\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不要完全依賴於類型檢查,必要時還是需要編寫兜底的防禦性代碼。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲類型報錯不會影響代碼生成和執行,所以原則上還是會存在 fn('str') 調用的可能性,所以需要 default 進行兜底的防禦性代碼。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"function fn(value:boolean){\n switch(value){\n   case true: \n     console.log('true');\n      break;\n    case false: \n      console.log('false');\n      break;\n    default: \n      console.log('dead code');\n  }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於函數,要嚴格控制返回值的類型."}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"\/\/ 推薦寫法\nfunction getLocalStorage(key: string): T | null {\n  const str = window.localStorage.getItem(key);\n  return str ? JSON.parse(str) : null;\n}\nconst data = getLocalStorage(\"USER_KEY\");"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"利用 new() 實現工廠模式"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TypeScript 語法實現工廠模式很簡單,只需先定義一個函數,並聲明一個構造函數的類型參數,然後在函數體裏面返回 c 這個類構造出來的對象即可。以下示例中,工廠函數構造出來的是 T 類型的對象。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"function create(c: { new(): T }): T {\n return new c();\n}\nclass Test {\n  constructor() {\n  }\n}\ncreate(Test);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"優先考慮使用 Unknown 類型而非 Any"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用 readonly 標記入參,保證參數不會在函數內被修改"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"function fn(arr:readonly number[] ){\n  let sum=0, num = 0;\n  while((num = arr.pop()) !== undefined){\n    sum += num;\n  }\n  return sum;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"優先考慮使用 Unknown 類型而非 Any"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用 readonly 標記入參,保證參數不會在函數內被修改"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"function fn(arr:readonly number[] ){\n  let sum=0, num = 0;\n  while((num = arr.pop()) !== undefined){\n    sum += num;\n  }\n  return sum;\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"建議開啓以下編譯檢查選項,便於在編譯環境發現潛在 Bug"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"{\n \"compilerOptions\": {\n \/* 嚴格的類型檢查選項 *\/\n \"strict\": true, \/\/ 啓用所有嚴格類型檢查選項\n \"noImplicitAny\": true, \/\/ 在表達式和聲明上有隱含的 any類型時報錯\n \"strictNullChecks\": true, \/\/ 啓用嚴格的 null 檢查\n \"noImplicitThis\": true, \/\/ 當 this 表達式值爲 any 類型的時候,生成一個錯誤\n \"alwaysStrict\": true, \/\/ 以嚴格模式檢查每個模塊,並在每個文件里加入 'use strict'\n \n \/* 額外的檢查 *\/\n \"noUnusedLocals\": true, \/\/ 有未使用的變量時,拋出錯誤\n \"noUnusedParameters\": true, \/\/ 有未使用的參數時,拋出錯誤\n \"noImplicitReturns\": true, \/\/ 並不是所有函數裏的代碼都有返回值時,拋出錯誤\n \"noFallthroughCasesInSwitch\": true,\/\/ 報告 switch 語句的 fallthrough 錯誤。(即,不允許 switch 的 case 語句貫穿)\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"頭圖:Unsplash"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:沫沫"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:https:\/\/mp.weixin.qq.com\/s\/gAwvcmSNYMwQKk6RY-GaEw"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:編寫高質量可維護的代碼:Awesome TypeScript"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來源:政採雲前端團隊 - 微信公衆號 [ID:Zoo-Team]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"轉載:著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章