你不知道的 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":"转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章