你不知道的 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":"在 2020 年的今天,TS 已經越來越火,不管是服務端(Node.js),還是前端框架(Angular、Vue3),都有越來越多的項目使用 TS 開發,作爲前端程序員,TS 已經成爲一項必不可少的技能,本文旨在介紹 TS 中的一些高級技巧,提高大家對這門語言更深層次的認知。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","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":"ECMAScript 的超集 (stage 3)"}]}]},{"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":"不引入額外開銷(零依賴,不擴展 js 語法,不侵入運行時)"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"編譯出通用的、易讀的 js 代碼"}]}]}]},{"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":"text","marks":[{"type":"strong"}],"text":"Type"},{"type":"text","text":" + "},{"type":"text","marks":[{"type":"strong"}],"text":"ECMAScript"},{"type":"text","text":" + "},{"type":"text","marks":[{"type":"strong"}],"text":"Babel-Lite"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Typescript 設計目標: https:\/\/github.com\/Microsoft\/TypeScript\/wiki\/TypeScript-Design-Goals"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","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":"增加了代碼的可讀性和可維護性"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"減少運行時錯誤,寫出的代碼更加安全,減少 BUG"}]}]},{"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":"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":"boolean"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"number"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"string"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"array"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"tuple"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"enum"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"void"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"null & undefined"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"any & unknown"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"never"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"any"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"unknown"}]},{"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":"any"}]},{"type":"text","text":": 任意類型"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"unknown"}]},{"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":"unknown"}]},{"type":"text","text":",但 "},{"type":"codeinline","content":[{"type":"text","text":"unknown"}]},{"type":"text","text":" 不能分配給其他基本類型,而 "},{"type":"codeinline","content":[{"type":"text","text":"any"}]},{"type":"text","text":" 啥都能分配和被分配。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"let foo: unknown\n\nfoo = true \/\/ ok\nfoo = 123 \/\/ok\n\nfoo.toFixed(2) \/\/ error\n\nlet foo1: string = foo \/\/ error"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"let bar: any\n\nbar = true \/\/ ok\nbar = 123 \/\/ok\n\nfoo.toFixed(2) \/\/ ok\n\nlet bar1:string  = bar \/\/ ok"}]},{"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":"any"}]},{"type":"text","text":" 就相當於完全丟失了類型檢查,所以大家儘量少用 "},{"type":"codeinline","content":[{"type":"text","text":"any"}]},{"type":"text","text":",對於未知類型可以用 "},{"type":"codeinline","content":[{"type":"text","text":"unknown"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"unknown 的正確用法"}]},{"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":"unknown"}]},{"type":"text","text":" 類型縮小爲更具體的類型範圍:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"function getLen(value: unknown): number {\n  if (typeof value === 'string') {\n    \/\/ 因爲類型保護的原因,此處 value 被判斷爲 string 類型\n   return value.length\n  }\n  \n  return 0\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個過程叫類型收窄(type narrowing)。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"never"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"never"}]},{"type":"text","text":" 一般表示哪些用戶無法達到的類型。在最新的 typescript 3.7 中,下面代碼會報錯:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\/\/ never 用戶控制流分析\nfunction neverReach (): never {\n  throw new Error('an error')\n}\n\nconst x = 2\n\nneverReach()\n\nx.toFixed(2)  \/\/ x is unreachable"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"never"}]},{"type":"text","text":" 還可以用於聯合類型的 "},{"type":"text","marks":[{"type":"strong"}],"text":"幺元"},{"type":"text","text":":"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"type T0 = string | number | never \/\/ T0 is string | number"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"函數類型"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"幾種函數類型的返回值類型寫法"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"function fn(): number {\n  return 1\n}\n\nconst fn = function (): number {\n  return 1\n}\n\nconst fn = (): number => {\n  return 1\n}\n\nconst obj = {\n  fn (): number {\n    return 1\n  }\n}\n"}]},{"type":"blockquote","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":"heading","attrs":{"align":null,"level":3},"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":"ts 中也有函數類型,用來描述一個函數:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"type FnType = (x: number, y: number) => number"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"完整的函數寫法"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"let myAdd: (x: number, y: number) => number = function(x: number, y: number): number {\n  return x + y\n}\n\n\/\/ 使用 FnType 類型\nlet myAdd: FnType = function(x: number, y: number): number {\n  return x + y\n}\n\n\/\/ ts 自動推導參數類型\nlet myAdd: FnType = function(x, y) {\n  return x + y\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"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":"js因爲是動態類型,本身不需要支持重載,ts爲了保證類型安全,支持了"},{"type":"text","marks":[{"type":"strong"}],"text":"函數簽名的類型重載"},{"type":"text","text":"。即:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"多個"},{"type":"codeinline","content":[{"type":"text","text":"重載簽名"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"和一個"},{"type":"codeinline","content":[{"type":"text","text":"實現簽名"}],"marks":[{"type":"strong"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ 重載簽名(函數類型定義)\nfunction toString(x: string): string;\nfunction toString(x: number): string;\n\n\/\/ 實現簽名(函數體具體實現)\nfunction toString(x: string | number) {\n  return String(x)\n}\n\nlet a = toString('hello') \/\/ ok\nlet b = toString(2) \/\/ ok\nlet c = toString(true) \/\/ error\n"}]},{"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":"如果定義了"},{"type":"codeinline","content":[{"type":"text","text":"重載簽名"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":",則"},{"type":"codeinline","content":[{"type":"text","text":"實現簽名"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"對外不可見"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"function toString(x: string): string;\n\nfunction toString(x: number): string {\n  return String(x)\n}\n\nlen(2) \/\/ error\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"實現簽名"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"必須兼容"},{"type":"codeinline","content":[{"type":"text","text":"重載簽名"}],"marks":[{"type":"strong"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"function toString(x: string): string;\nfunction toString(x: number): string; \/\/ error\n\n\/\/ 函數實現\nfunction toString(x: string) {\n  return String(x)\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"重載簽名"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"的類型不會合並"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ 重載簽名(函數類型定義)\nfunction toString(x: string): string;\nfunction toString(x: number): string;\n\n\/\/ 實現簽名(函數體具體實現)\nfunction toString(x: string | number) {\n  return String(x)\n}\n\nfunction stringOrNumber(x): string | number {\n  return x ? '' : 0\n}\n\n\/\/ input 是 string 和 number 的聯合類型\n\/\/ 即 string | number\nconst input = stringOrNumber(1)\n\ntoString('hello') \/\/ ok\ntoString(2) \/\/ ok\ntoString(input) \/\/ error"}]},{"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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ts 中的類型推斷是非常強大,而且其內部實現也是非常複雜的。"}]},{"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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ ts 推導出 x 是 number 類型\nlet x = 10\n"}]},{"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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ ts 推斷出 myObj 的類型:myObj: { x: number; y: string; z: boolean; }\nconst myObj = {\n  x: 1,\n  y: '2',\n  z: true\n}\n"}]},{"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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ ts 推導出函數返回值是 number 類型\nfunction len (str: string) {\n  return str.length\n}\n"}]},{"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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ ts 推導出 event 是 ProgressEvent 類型\nconst xhr = new XMLHttpRequest()\nxhr.onload = function (event) {}"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以有時候對於一些簡單的類型可以不用手動聲明其類型,讓 ts 自己去推斷。"}]}]},{"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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"typescript 的子類型是基於 "},{"type":"codeinline","content":[{"type":"text","text":"結構子類型"}]},{"type":"text","text":" 的,只要結構可以兼容,就是子類型。(Duck Type)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"class Point {\n  x: number\n}\n\nfunction getPointX(point: Point) {\n  return point.x\n}\n\nclass Point2 {\n  x: number\n}\n\nlet point2 = new Point2()\n\ngetPointX(point2) \/\/ OK\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"java"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"c++"}]},{"type":"text","text":" 等傳統靜態類型語言是基於 "},{"type":"codeinline","content":[{"type":"text","text":"名義子類型"}]},{"type":"text","text":" 的,必須顯示聲明子類型關係(繼承),纔可以兼容。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"public class Main {\n  public static void main (String[] args) {\n    getPointX(new Point()); \/\/ ok\n    getPointX(new ChildPoint()); \/\/ ok\n    getPointX(new Point1());  \/\/ error\n  }\n\n  public static void getPointX (Point point) {\n    System.out.println(point.x);\n  }\n\n  static class Point {\n    public int x = 1;\n  }\n\n  static class Point2 {\n    public int x = 2;\n  }\n    \n  static class ChildPoint extends Point {\n    public int x = 3;\n  }\n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"function getPointX(point: { x: number }) {\n  return point.x\n}\n\nconst point = {\n x: 1,\n  y: '2'\n}\n\ngetPointX(point) \/\/ OK"}]},{"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":"注意"},{"type":"text","text":": 如果直接傳入一個對象字面量是會報錯的:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"function getPointX(point: { x: number }) {\n  return point.x\n}\n\ngetPointX({ x: 1, y: '2' }) \/\/ error\n"}]},{"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":"這是 ts 中的另一個特性,叫做:  "},{"type":"codeinline","content":[{"type":"text","text":"excess property check"}]},{"type":"text","text":"  ,當傳入的參數是一個對象字面量時,會進行"},{"type":"text","marks":[{"type":"strong"}],"text":"額外屬性檢查。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"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":"text","marks":[{"type":"strong"}],"text":"逆變"},{"type":"text","text":"與"},{"type":"text","marks":[{"type":"strong"}],"text":"協變"},{"type":"text","text":"的概念,"},{"type":"text","marks":[{"type":"strong"}],"text":"逆變"},{"type":"text","text":"與"},{"type":"text","marks":[{"type":"strong"}],"text":"協變"},{"type":"text","text":"並不是 TS 中獨有的概念,在其他靜態語言中也有相關理念。"}]},{"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":"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":"A ≼ B"}]},{"type":"text","text":" 表示 A 是 B 的子類型,A 包含 B 的所有屬性和方法。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"A => B"}]},{"type":"text","text":" 表示以 A 爲參數,B 爲返回值的方法。"},{"type":"codeinline","content":[{"type":"text","text":"(param: A) => B"}]}]}]}]},{"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":"Animal"}]},{"type":"text","text":" 、 "},{"type":"codeinline","content":[{"type":"text","text":"Dog"}]},{"type":"text","text":" 、 "},{"type":"codeinline","content":[{"type":"text","text":"WangCai(旺財)"}]},{"type":"text","text":" ,那麼肯定存在下面的關係:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"WangCai ≼ Dog ≼ Animal \/\/ 即旺財屬於狗屬於動物"}]},{"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":"問題"},{"type":"text","text":":以下哪種類型是 "},{"type":"codeinline","content":[{"type":"text","text":"Dog => Dog"}]},{"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":"WangCai => WangCai"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"WangCai => Animal"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Animal  => Animal"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Animal  => WangCai"}]}]}]}]},{"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":"從代碼來看解答"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"class Animal {\n  sleep: Function\n}\n\nclass Dog extends Animal {\n  \/\/ 吠\n  bark: Function\n}\n\nclass WangCai extends Dog {\n  dance: Function\n}\n\n\nfunction getDogName (cb: (dog: Dog) => Dog) {\n  const dog = cb(new Dog())\n  dog.bark()\n}\n\n\/\/ 對於入參來說,WangCai 是 Dog 的子類,Dog 類上沒有 dance 方法, 產生異常。\n\/\/ 對於出參來說,WangCai 類繼承了 Dog 類,肯定會有 bark 方法\ngetDogName((wangcai: WangCai) => {\n  wangcai.dance()\n  return new WangCai()\n})\n\n\/\/ 對於入參來說,WangCai 是 Dog 的子類,Dog 類上沒有 dance 方法, 產生異常。\n\/\/ 對於出參來說,Animal 類上沒有 bark 方法, 產生異常。\ngetDogName((wangcai: WangCai) => {\n  wangcai.dance()\n  return new Animal()\n})\n\n\/\/ 對於入參來說,Animal 類是 Dog 的父類,Dog 類肯定有 sleep 方法。\n\/\/ 對於出參來說,WangCai 類繼承了 Dog 類,肯定會有 bark 方法\ngetDogName((animal: Animal) => {\n  animal.sleep()\n  return new WangCai()\n})\n\n\/\/ 對於入參來說,Animal 類是 Dog 的父類,Dog 類肯定有 sleep 方法。\n\/\/ 對於出參來說,Animal 類上沒有 bark 方法, 產生異常。\ngetDogName((animal: Animal) => {\n  animal.sleep()\n  return new Animal()\n})"}]},{"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":"Animal => WangCai"}]},{"type":"text","text":" 纔是 "},{"type":"codeinline","content":[{"type":"text","text":"Dog => Dog"}]},{"type":"text","text":" 的子類型,可以得到一個結論,對於函數類型來說,函數參數的類型兼容是反向的,我們稱之爲 "},{"type":"codeinline","content":[{"type":"text","text":"逆變"}]},{"type":"text","text":" ,返回值的類型兼容是正向的,稱之爲 "},{"type":"codeinline","content":[{"type":"text","text":"協變"}]},{"type":"text","text":" 。"}]},{"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}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實函數的參數可以轉化爲 "},{"type":"codeinline","content":[{"type":"text","text":"Tuple"}]},{"type":"text","text":" 的類型兼容性:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"type Tuple1 = [string, number]\ntype Tuple2 = [string, number, boolean]\n\nlet tuple1: Tuple1 = ['1', 1]\nlet tuple2: Tuple2 = ['1', 1, true]\n\nlet t1: Tuple1 = tuple2 \/\/ ok\nlet t2: Tuple2 = tuple1 \/\/ error"}]},{"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":"Tuple2 => Tuple1"}]},{"type":"text","text":" ,即長度大的是長度小的子類型,再由於函數參數的逆變特性,所以函數參數少的可以賦值給參數多的(參數從前往後需一一對應),從數組的 forEach 方法就可以看出來:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"[1, 2].forEach((item, index) => {\n console.log(item)\n}) \/\/ ok\n\n[1, 2].forEach((item, index, arr, other) => {\n console.log(other)\n}) \/\/ error"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"高級類型"}]},{"type":"heading","attrs":{"align":null,"level":3},"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","marks":[{"type":"strong"}],"text":"聯合類型(union type)表示多種類型的 “或” 關係"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"function genLen(x: string | any[]) {\n  return x.length\n}\n\ngenLen('') \/\/ ok\ngenLen([]) \/\/ ok\ngenLen(1) \/\/ error"}]},{"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":"交叉類型表示多種類型的 “與” 關係"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"interface Person {\n  name: string\n  age: number\n}\n\ninterface Animal {\n  name: string\n  color: string\n}\n\nconst x: Person & Animal = {\n  name: 'x',\n  age: 1,\n  color: 'red\n}"}]},{"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":"使用聯合類型表示枚舉"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"type Position = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'\n\nconst position: Position = 'UP'"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以避免使用 "},{"type":"codeinline","content":[{"type":"text","text":"enum"}]},{"type":"text","text":" 侵入了運行時。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"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":"ts 初學者很容易寫出下面的代碼:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"function isString (value) {\n  return Object.prototype.toString.call(value) === '[object String]'\n}\n\nfunction fn (x: string | number) {\n  if (isString(x)) {\n    return x.length \/\/ error 類型“string | number”上不存在屬性“length”。\n  } else {\n    \/\/ .....\n  }\n}"}]},{"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":"如何讓 ts 推斷出來上下文的類型呢?"}]},{"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":"1. 使用 ts 的 "},{"type":"codeinline","content":[{"type":"text","text":"is"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":" 關鍵詞"}]},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"function isString (value: unknown): value is string {\n  return Object.prototype.toString.call(value) === '[object String]'\n}\n\nfunction fn (x: string | number) {\n  if (isString(x)) {\n    return x.length\n  } else {\n    \/\/ .....\n  }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"2. typeof 關鍵詞"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 ts 中,"},{"type":"text","marks":[{"type":"strong"}],"text":"代碼實現"},{"type":"text","text":"中的 typeof 關鍵詞能夠幫助 ts 判斷出變量的基本類型:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"function fn (x: string | number) {\n  if (typeof x === 'string') { \/\/ x is string\n    return x.length\n  } else { \/\/ x is number\n    \/\/ .....\n  }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"3. instanceof 關鍵詞"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 ts 中,instanceof 關鍵詞能夠幫助 ts 判斷出構造函數的類型:"}]},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"function fn1 (x: XMLHttpRequest | string) {\n  if (x instanceof XMLHttpRequest) { \/\/ x is XMLHttpRequest\n    return x.getAllResponseHeaders()\n  } else { \/\/ x is string\n    return x.length\n  }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"4. 針對 null 和 undefined 的類型保護"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在條件判斷中,ts 會自動對 null 和 undefined 進行類型保護:"}]},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"function fn2 (x?: string) {\n  if (x) {\n    return x.length\n  }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"5. 針對 null 和 undefined 的類型斷言"}]},{"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":"typescript"},"content":[{"type":"text","text":"function fn2 (x?: string) {\n  return x!.length\n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"typeof 關鍵詞"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"typeof"}]},{"type":"text","text":" 關鍵詞除了做類型保護,還可以從"},{"type":"text","marks":[{"type":"strong"}],"text":"實現"},{"type":"text","text":"推出"},{"type":"text","marks":[{"type":"strong"}],"text":"類型,"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意:此時的 "},{"type":"codeinline","content":[{"type":"text","text":"typeof"}]},{"type":"text","text":" 是一個"},{"type":"text","marks":[{"type":"strong"}],"text":"類型關鍵詞"},{"type":"text","text":",只可以用在類型語法中。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"function fn(x: string) {\n  return x.length\n}\n\nconst obj = {\n  x: 1,\n  y: '2'\n}\n\ntype T0 = typeof fn \/\/ (x: string) => number\ntype T1 = typeof obj \/\/ {x: number; y: string }"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"keyof 關鍵詞"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"keyof"}]},{"type":"text","text":" 也是一個 "},{"type":"text","marks":[{"type":"strong"}],"text":"類型關鍵詞"},{"type":"text","text":" ,可以用來取得一個對象接口的所有 "},{"type":"codeinline","content":[{"type":"text","text":"key"}]},{"type":"text","text":" 值:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"interface Person {\n  name: string\n  age: number\n}\n\ntype PersonAttrs = keyof Person \/\/ 'name' | 'age'"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"in 關鍵詞"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"in"}]},{"type":"text","text":" 也是一個 "},{"type":"text","marks":[{"type":"strong"}],"text":"類型關鍵詞"},{"type":"text","text":", 可以對聯合類型進行遍歷,只可以用在 "},{"type":"text","marks":[{"type":"strong"}],"text":"type"},{"type":"text","text":" 關鍵詞下面。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"type Person = {\n  [key in 'name' | 'age']: number\n}\n\n\/\/ { name: number; age: number; }"}]},{"type":"heading","attrs":{"align":null,"level":3},"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":"[]"}]},{"type":"text","text":" 操作符可以進行索引訪問,也是一個 "},{"type":"text","marks":[{"type":"strong"}],"text":"類型關鍵詞"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"interface Person {\n  name: string\n  age: number\n}\n\ntype x = Person['name'] \/\/ x is string"}]},{"type":"heading","attrs":{"align":null,"level":3},"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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"type Copy = {\n  [key in keyof T]: T[key]\n}\n\ninterface Person {\n  name: string\n  age: number\n}\n\ntype Person1 = Copy"}]},{"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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"泛型相當於一個類型的參數,在 ts 中,泛型可以用在 "},{"type":"codeinline","content":[{"type":"text","text":"類"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"接口"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"方法"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"類型別名"}]},{"type":"text","text":" 等實體中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"小試牛刀"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"function createList(): T[] {\n  return [] as T[]\n}\n\nconst numberList = createList() \/\/ number[]\nconst stringList = createList() \/\/ string[]"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了泛型的支持,createList 方法可以傳入一個類型,返回有類型的數組,而不是一個 "},{"type":"codeinline","content":[{"type":"text","text":"any[]"}]},{"type":"text","text":"。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"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":"如果我們只希望 createList 函數只能生成指定的類型數組,該如何做,可以使用 "},{"type":"codeinline","content":[{"type":"text","text":"extends"}]},{"type":"text","text":" 關鍵詞來約束泛型的範圍和形狀。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"type Lengthwise = {\n  length: number\n}\n\nfunction createList(): T[] {\n  return [] as T[]\n}\n\nconst numberList = createList() \/\/ ok\nconst stringList = createList() \/\/ ok\nconst arrayList = createList() \/\/ ok\nconst boolList = createList() \/\/ error"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"any[]"}]},{"type":"text","text":" 是一個數組類型,數組類型是有 length 屬性的,所以 ok。"},{"type":"codeinline","content":[{"type":"text","text":"string"}]},{"type":"text","text":" 類型也是有 length 屬性的,所以 ok。但是 "},{"type":"codeinline","content":[{"type":"text","text":"boolean"}]},{"type":"text","text":" 就不能通過這個約束了。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"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":"codeinline","content":[{"type":"text","text":"extends"}]},{"type":"text","text":" 除了做約束類型,還可以做條件控制,相當於與一個三元運算符,只不過是針對 "},{"type":"text","marks":[{"type":"strong"}],"text":"類型"},{"type":"text","text":" 的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"表達式"},{"type":"text","text":":"},{"type":"codeinline","content":[{"type":"text","text":"T extends U ? X : Y"}]}]},{"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":"含義"},{"type":"text","text":":如果 T 可以被分配給 U,則返回 X,否則返回 Y。一般條件下,如果 T 是 U 的子類型,則認爲 T 可以分配給 U,例如:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"type IsNumber = T extends number ? true : false\n\ntype x = IsNumber  \/\/ false\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"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":"text","marks":[{"type":"strong"}],"text":"類型的函數"},{"type":"text","text":",可以做一些"},{"type":"text","marks":[{"type":"strong"}],"text":"類型運算"},{"type":"text","text":",輸入一個類型,輸出另一個類型,前文我們舉了個 "},{"type":"codeinline","content":[{"type":"text","text":"Copy"}]},{"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":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ 每一個屬性都變成可選\ntype Partial = {\n  [P in keyof T]?: T[P]\n}\n\n\/\/ 每一個屬性都變成只讀\ntype Readonly = {\n  readonly [P in keyof T]: T[P]\n}\n\n\/\/ 選擇對象中的某些屬性\ntype Pick = {\n  [P in K]: T[P];\n}\n\n\/\/ ......"}]},{"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 2.8 在 "},{"type":"codeinline","content":[{"type":"text","text":"lib.d.ts"}]},{"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":"Partial"}]},{"type":"text","text":" -- 將 "},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":" 中的所有屬性變成可選。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Readonly"}]},{"type":"text","text":" -- 將 "},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":" 中的所有屬性變成只讀。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Pick"}]},{"type":"text","text":" -- 選擇 "},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":" 中可以賦值給"},{"type":"codeinline","content":[{"type":"text","text":"U"}]},{"type":"text","text":"的類型。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Exclude"}]},{"type":"text","text":" -- 從"},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":"中剔除可以賦值給"},{"type":"codeinline","content":[{"type":"text","text":"U"}]},{"type":"text","text":"的類型。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Extract"}]},{"type":"text","text":" -- 提取"},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":"中可以賦值給"},{"type":"codeinline","content":[{"type":"text","text":"U"}]},{"type":"text","text":"的類型。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"NonNullable"}]},{"type":"text","text":" -- 從"},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":"中剔除"},{"type":"codeinline","content":[{"type":"text","text":"null"}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":"undefined"}]},{"type":"text","text":"。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"ReturnType"}]},{"type":"text","text":" -- 獲取函數返回值類型。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"InstanceType"}]},{"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":"所以我們平時寫 TS 時可以直接使用這些類型工具:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"interface ApiRes {\n  code: string;\n  flag: string;\n  message: string;\n  data: object;\n  success: boolean;\n  error: boolean;\n}\n\ntype IApiRes = Pick\n\n\/\/ {\n\/\/   code: string;\n\/\/   flag: string;\n\/\/   message: string;\n\/\/   data: object;\n\/\/ }"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"extends 條件分發"}]},{"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":"對於 T extends U ? X : Y 來說,還存在一個特性,當 T 是一個聯合類型時,會進行條件分發。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"type Union = string | number\ntype isNumber = T extends number ? 'isNumber' : 'notNumber'\n\ntype UnionType = isNumber \/\/ 'notNumber' | 'isNumber'"}]},{"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":"實際上,extends 運算會變成如下形式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"(string extends number ? 'isNumber' : 'notNumber') | (number extends number ? 'isNumber' : 'notNumber')"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Extract"}]},{"type":"text","text":" 就是基於此特性,再配合 "},{"type":"codeinline","content":[{"type":"text","text":"never"}]},{"type":"text","text":" 幺元的特性實現的:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"type Exclude = T extends K ? never : T\n\ntype T1 = Exclude  \/\/ number"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"infer"}]},{"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":"codeinline","content":[{"type":"text","text":"infer"}]},{"type":"text","text":" 可以對運算過程中的類型進行存儲,內置的"},{"type":"codeinline","content":[{"type":"text","text":"ReturnType"}]},{"type":"text","text":" 就是基於此特性實現的:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"type ReturnType = \n  T extends (...args: any) => infer R ? R : never\n\ntype Fn = (str: string) => number\n\ntype FnReturn = ReturnType \/\/ number"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"模塊"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"全局模塊 vs. 文件模塊"}]},{"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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const foo = 2"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時,如果我們創建了另一個文件,並寫下如下代碼,ts 認爲是正常的:"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const bar = foo \/\/ ok"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果要打破這種限制,只要文件中有 "},{"type":"codeinline","content":[{"type":"text","text":"import"}]},{"type":"text","text":" 或者 "},{"type":"codeinline","content":[{"type":"text","text":"export"}]},{"type":"text","text":" 表達式即可:"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"export const bar = foo \/\/ error"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"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":"Tpescript 有兩種模塊的解析策略:Node 和 Classic。當 tsconfig.json 中 "},{"type":"codeinline","content":[{"type":"text","text":"module"}]},{"type":"text","text":" 設置成 AMD、System、ES2015 時,默認爲 "},{"type":"codeinline","content":[{"type":"text","text":"classic"}]},{"type":"text","text":" ,否則爲 "},{"type":"codeinline","content":[{"type":"text","text":"Node"}]},{"type":"text","text":" ,也可以使用 "},{"type":"codeinline","content":[{"type":"text","text":"moduleResolution"}]},{"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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"import moduleB from 'moduleB'\n"}]},{"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":"Classic 模式的路徑尋址:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/root\/src\/folder\/moduleB.ts\n\/root\/src\/folder\/moduleB.d.ts\n\/root\/src\/moduleB.ts\n\/root\/src\/moduleB.d.ts\n\/root\/moduleB.ts\n\/root\/moduleB.d.ts\n\/moduleB.ts\n\/moduleB.d.ts\n"}]},{"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":"Node 模式的路徑尋址:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/root\/src\/node_modules\/moduleB.ts\n\/root\/src\/node_modules\/moduleB.tsx\n\/root\/src\/node_modules\/moduleB.d.ts\n\/root\/src\/node_modules\/moduleB\/package.json (如果指定了\"types\"屬性)\n\/root\/src\/node_modules\/moduleB\/index.ts\n\/root\/src\/node_modules\/moduleB\/index.tsx\n\/root\/src\/node_modules\/moduleB\/index.d.ts\n\n\/root\/node_modules\/moduleB.ts\n\/root\/node_modules\/moduleB.tsx\n\/root\/node_modules\/moduleB.d.ts\n\/root\/node_modules\/moduleB\/package.json (如果指定了\"types\"屬性)\n\/root\/node_modules\/moduleB\/index.ts\n\/root\/node_modules\/moduleB\/index.tsx\n\/root\/node_modules\/moduleB\/index.d.ts\n\n\/node_modules\/moduleB.ts\n\/node_modules\/moduleB.tsx\n\/node_modules\/moduleB.d.ts\n\/node_modules\/moduleB\/package.json (如果指定了\"types\"屬性)\n\/node_modules\/moduleB\/index.ts\n\/node_modules\/moduleB\/index.tsx\n\/node_modules\/moduleB\/index.d.ts"}]},{"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\/0DZ2f1dZue8-BATX0FQpSQ"}]},{"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},"content":[{"type":"text","text":"來源:微醫大前端技術 - 微信公衆號 [ID:wed_fed]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"轉載:著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章