编写高质量可维护的代码:Awesome TypeScript

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"高质量可维护的代码应具备可读性高、结构清晰、低耦合、易扩展等特点。而原生的 JavaScript 由于其弱类型和没有模块化的缺点,不利于大型应用的开发和维护,因此,TypeScript 也就应运而生。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TypeScript 是 JavaScript 的一个超集,它的设计初衷并不是为了替代 JavaScript,而是基于 JavaScript 做了一系列的增强,包括增加了静态类型、接口、类、泛型、方法重载等等。所以,只要你有一定的 JavaScript 功底,那么 TypeScript 上手就非常简单。并且,你可以在 TypeScript 中愉快的使用 JavaScript 语法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下去,本文将给大家分享下,TypeScript 的重要特性以及在实际场景中的使用技巧,帮助大家更高效的编写高质量可维护的代码。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Typescript VS Javascript"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"JavaScript"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript 是动态类型语言,在代码编译阶段不会对变量进行类型检测,从而会把潜在的类型错误带到代码执行阶段。并且在遇到不同类型变量的赋值时,会自动进行类型转换,带来了不确定性,容易产生 Bug。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript 原生没有命名空间,需要手动创建命名空间,来进行模块化。并且,JavaScript 允许同名函数的重复定义,后面的定义可以覆盖前面的定义。这也给我们开发和维护大型应用带来了不便。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"TypeScript"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TypeScript 是静态类型语言,通过类型注解提供编译时的静态类型检查。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在代码编译阶段会进行变量的类型检测,提前暴露潜在的类型错误问题。并且在代码执行阶段,不允许不同类型变量之间的赋值。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"清晰的类型注解,不仅让代码的可读性更好,同时也增强了 IDE 的能力,包括代码补全、接口提示、跳转到定义等等。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TypeScript 增加了模块类型,自带命名空间,方便了大型应用的模块化开发。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TypeScript 的设计一种完全面向对象的编程语言,具备模块、接口、类、类型注解等,可以让我们的代码组织结构更清晰。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"经过上述对比,可以看到 TypeScript 的出现很好的弥补了 JavaScript 的部分设计缺陷,给我们带来了很大的便利,也提高了代码的健壮性和扩展性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"重要特性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"数据类型"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基础数据类型包括:Boolean、Number、String、Array、Enum、Any、Unknown、Tuple、Void、Null、Undefined、Never。下面选择几个 TypeScript 特有的类型进行详解:"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Enum 枚举:在编码过程中,要避免使用硬编码,如果某个常量是可以被一一列举出来的,那么就建议使用枚举类型来定义,可以让代码更易维护。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"  \/\/ 包括 数字枚举、字符串枚举、异构枚举(数字和字符串的混合)。\n  \/\/ 数字枚举在不设置默认值的情况下,默认第一个值为0,其他依次自增长\n  enum STATUS {\n    PENDING,\n    PROCESS,\n    COMPLETED,\n  }\n  let status: STATUS = STATUS.PENDING;  \/\/ 0\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Any 类型:不建议使用。Any 类型为顶层类型,所有类型都可以被视为 any 类型,使用 Any 也就等同于让 TypeScript 的类型校验机制失效。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Unknown 类型:Unknown 类型也是顶层类型,它可以接收任何类型,但它与 Any 的区别在于,它首次赋值后就确定了数据类型,不允许变量的数据类型进行二次变更。所以,在需要接收所有类型的场景下,优先考虑用 Unknown 代替 Any。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Tuple 元组:支持数组内存储不同数据类型的元素,让我们在组织数据的时候更灵活。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"let tupleType: [string, boolean];\ntupleType = [\"momo\", true];\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Void 类型:当函数没有返回值的场景下,通常将函数的返回值类型设置为 void。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"类型注解"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TypeScript 通过类型注解提供编译时的静态类型检查,可以在编译阶段就发现潜在 Bug,同时让编码过程中的提示也更智能。使用方式很简单,在 "},{"type":"codeinline","content":[{"type":"text","text":":"}]},{"type":"text","text":" 冒号后面注明变量的类型即可。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"const str: string = 'abc';"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"接口"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在面向对象编程的语言里面,接口是实现程序解耦的关键,它只定义具体包含哪些属性和方法,而不涉及任何具体的实现细节。接口是基于类之上,更进一步对实体或行为进行抽象,会让程序具备更好的扩展性。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"应用场景:比如我们在实现订单相关功能的时候,需要对订单进行抽象,定义一个订单的接口,包括订单基本信息以及对订单的相关操作,然后基于这个接口来做进一步的实现。后续如果订单的相关操作功能有变化,只需要重新定义一个类来实现这个接口即可。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"interface Animal {\nname: string;\ngetName(): string;\n}\nclass Monkey implements Padder {\nconstructor(private name: string) {\n  getName() {\n    return 'Monkey: ' + name;\n }\n}\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"类"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TypeScript 的类除了包括最基本的属性和方法、getter 和 setter、继承等特性,还新增了私有字段。私有字段不能在包含的类之外访问,甚至不能被检测到。Javascript 的类中是没有私有字段的,如果想模拟私有字段的话,必须要用闭包来模拟。下面用一些示例来说明下类的使用:"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"属性和方法"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"class Person {\n\/\/ 静态属性\nstatic name: string = \"momo\";\n\/\/ 成员属性\ngender: string;\n\/\/ 构造函数\nconstructor(str: string) {\n  this.gender = str;\n}\n\/\/ 静态方法\nstatic getName() {\n  return this.name;\n}\n\/\/ 成员方法\ngetGender() {\n  return 'Gender: ' + this.gender;\n}\n}\nlet person = new Person(\"female\");"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"getter 和 setter"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通过 getter 和 setter 方法来实现数据的封装和有效性校验,防止出现异常数据。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"class Person {\nprivate _name: string;\nget name(): string {\n  return this._name;\n}\nset name(newName: string) {\n  this._name = newName;\n}\n}\nlet person = new Person('momo');\nconsole.log(person.name); \/\/ momo\nperson.name = 'new_momo';\nconsole.log(person.name); \/\/ new_momo\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"继承"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"class Animal {\nname: string;\nconstructor(nameStr=:string) {\n  this.name = nameStr;\n}  \nmove(distanceInMeters: number = 0) {\n  console.log(`${this.name} moved ${distanceInMeters}m.`);\n}\n}\nclass Snake extends Animal {\nconstructor(name: string) {\n  super(name);\n} \nmove(distanceInMeters = 5) {\n  super.move(distanceInMeters);\n}\n}\nlet snake = new Snake('snake');\nsnake.move(); \/\/ 输出:'snake moved 5m'"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"私有字段"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"私有字段以 "},{"type":"codeinline","content":[{"type":"text","text":"#"}]},{"type":"text","text":" 字符开头。私有字段不能在包含的类之外访问,甚至不能被检测到。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"class Person {\n#name: string;\nconstructor(name: string) {\n  this.#name = name;\n}\ngreet() {\n  console.log(`Hello, ${this.#name}!`);\n}\n}\nlet person = new Person('momo');\nperson.#name;   \/\/ 访问会报错"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"泛型"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"应用场景:当我们需要考虑代码的可复用性时,就需要用到泛型。让组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型。泛型允许同一个函数接受不同类型参数,相比于使用 Any 类型,使用泛型来创建的组件可复用和易扩展性要更好,因为泛型会保留参数类型。泛型可以应用于接口、类、变量。下面用一些示例来说明下泛型的使用:"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"泛型接口"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"  interface identityFn {\n    (arg: T): T;\n  }"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"泛型类"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"  class GenericNumber {\n    zeroValue: T;\n    add: (x: T, y: T) => T;\n  }\n  let myGenericNumber = new GenericNumber();\n  myGenericNumber.zeroValue = 0;\n  myGenericNumber.add = function (x, y) {\n    return x + y;\n  };"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"泛型变量"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用大写字母 A-Z 定义的类型变量都属于泛型,常见泛型变量如下:"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"T(Type):表示一个 TypeScript 类型"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"K(Key):表示对象中的键类型"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V(Value):表示对象中的值类型"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"E(Element):表示元素类型"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"交叉类型"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"交叉类型就是将多个类型合并为一个类型。通过 "},{"type":"codeinline","content":[{"type":"text","text":"&"}]},{"type":"text","text":" 运算符定义。如下示例中,将 Person 类型和 Company 类型合并后,生成了新的类型 Staff,该类型同时具备这两种类型的所有成员。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"interface Person {\nname: string;\ngender: string;\n}\ninterface Company {\ncompanyName: string;\n}\ntype Staff = Person & Company;\nconst staff: Staff = {\nname: 'momo',\ngender: 'female',\ncompanyName: 'ZCY'\n};"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"联合类型"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"联合类型就是由具有或关系的多个类型组合而成,只要满足其中一个类型即可。通过 "},{"type":"codeinline","content":[{"type":"text","text":"|"}]},{"type":"text","text":" 运算符定义。如下示例中,函数的入参为 String 或 Number 类型即可。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"function fn(param: string | number): void {\n  console.log(\"This is the union type\");\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"类型保护"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"类型保护就是在我们已经识别到当前数据是某种数据类型的情况下,安全的调用这个数据类型对应的属性和方法。常用的类型保护包括 "},{"type":"codeinline","content":[{"type":"text","text":"in"}]},{"type":"text","text":" 类型保护、"},{"type":"codeinline","content":[{"type":"text","text":"typeof"}]},{"type":"text","text":" 类型保护、"},{"type":"codeinline","content":[{"type":"text","text":"instanceof"}]},{"type":"text","text":" 类型保护和 "},{"type":"codeinline","content":[{"type":"text","text":"自定义"}]},{"type":"text","text":" 类型保护。具体见以下示例:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"in"}]},{"type":"text","text":" 类型保护"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"  interface Person {\n    name: string;\n    gender: string;\n  }\n  interface Employee {\n    name: string;\n    company: string;\n  }\n  type UnknownStaff = Person | Employee;\n  function getInfo(staff: UnknownStaff) {\n    if (\"gender\" in staff) {\n      console.log(\"Person info\");\n    }\n    if (\"company\" in staff) {\n      console.log(\"Employee info\");\n    }\n  }\n"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"typeof"}]},{"type":"text","text":" 类型保护"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"  function processData(param: string | number): unknown {\n   if (typeof param === 'string') {\n     return param.toUpperCase()\n    }\n    return param;\n  }\n"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"instanceof"}]},{"type":"text","text":" 类型保护:和 "},{"type":"codeinline","content":[{"type":"text","text":"typeof"}]},{"type":"text","text":" 类型用法相似,它主要是用来判断是否是一个类的对象或者继承对象的。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"  function processData(param: Date | RegExp): unknown {\n   if (param instanceof Date) {\n     return param.getTime();\n    }\n    return param;\n  }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"自定义"}]},{"type":"text","text":" 类型保护:通过类型谓词 "},{"type":"codeinline","content":[{"type":"text","text":"parameterName is Type"}]},{"type":"text","text":" 来实现自定义类型保护。如下示例,实现了接口的请求参数的类型保护。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"  interface ReqParams {\n   url: string;\n    onSuccess?: () => void;\n    onError?: () => void;\n  }\n  \/\/ 检测 request 对象包含参数符合要求的情况下,才返回 url\n  function validReqParams(request: unknown): request is ReqParams {\n   return request && request.url\n  }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"开发小技巧"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要连续判断某个对象里面是否存在某个深层次的属性,可以使用 "},{"type":"codeinline","content":[{"type":"text","text":"?."}]}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"if(result && result.data && result.data.list) \/\/ JS\nif(result?.data?.list) \/\/ TS\n"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"联合判断是否为空值,可以使用 "},{"type":"codeinline","content":[{"type":"text","text":"??"}]}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"let temp = (val !== null && val !== void 0 ? val : '1'); \/\/ JS\nlet temp = val ?? '1'; \/\/ TS\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不要完全依赖于类型检查,必要时还是需要编写兜底的防御性代码。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因为类型报错不会影响代码生成和执行,所以原则上还是会存在 fn('str') 调用的可能性,所以需要 default 进行兜底的防御性代码。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"function fn(value:boolean){\n switch(value){\n   case true: \n     console.log('true');\n      break;\n    case false: \n      console.log('false');\n      break;\n    default: \n      console.log('dead code');\n  }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于函数,要严格控制返回值的类型."}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"\/\/ 推荐写法\nfunction getLocalStorage(key: string): T | null {\n  const str = window.localStorage.getItem(key);\n  return str ? JSON.parse(str) : null;\n}\nconst data = getLocalStorage(\"USER_KEY\");"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"利用 new() 实现工厂模式"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TypeScript 语法实现工厂模式很简单,只需先定义一个函数,并声明一个构造函数的类型参数,然后在函数体里面返回 c 这个类构造出来的对象即可。以下示例中,工厂函数构造出来的是 T 类型的对象。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"function create(c: { new(): T }): T {\n return new c();\n}\nclass Test {\n  constructor() {\n  }\n}\ncreate(Test);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"优先考虑使用 Unknown 类型而非 Any"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用 readonly 标记入参,保证参数不会在函数内被修改"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"function fn(arr:readonly number[] ){\n  let sum=0, num = 0;\n  while((num = arr.pop()) !== undefined){\n    sum += num;\n  }\n  return sum;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"优先考虑使用 Unknown 类型而非 Any"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用 readonly 标记入参,保证参数不会在函数内被修改"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"function fn(arr:readonly number[] ){\n  let sum=0, num = 0;\n  while((num = arr.pop()) !== undefined){\n    sum += num;\n  }\n  return sum;\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"建议开启以下编译检查选项,便于在编译环境发现潜在 Bug"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"{\n \"compilerOptions\": {\n \/* 严格的类型检查选项 *\/\n \"strict\": true, \/\/ 启用所有严格类型检查选项\n \"noImplicitAny\": true, \/\/ 在表达式和声明上有隐含的 any类型时报错\n \"strictNullChecks\": true, \/\/ 启用严格的 null 检查\n \"noImplicitThis\": true, \/\/ 当 this 表达式值为 any 类型的时候,生成一个错误\n \"alwaysStrict\": true, \/\/ 以严格模式检查每个模块,并在每个文件里加入 'use strict'\n \n \/* 额外的检查 *\/\n \"noUnusedLocals\": true, \/\/ 有未使用的变量时,抛出错误\n \"noUnusedParameters\": true, \/\/ 有未使用的参数时,抛出错误\n \"noImplicitReturns\": true, \/\/ 并不是所有函数里的代码都有返回值时,抛出错误\n \"noFallthroughCasesInSwitch\": true,\/\/ 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"头图:Unsplash"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:沫沫"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:https:\/\/mp.weixin.qq.com\/s\/gAwvcmSNYMwQKk6RY-GaEw"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:编写高质量可维护的代码:Awesome TypeScript"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章