TypeScript 程序员晋级的 11 个必备技巧


TypeScript 程序员晋级的 11 个必备技巧

文章插图
当你学习TypeScript时,你的第一印象可能会欺骗你:这不就是JAVAScript注解的一种方式吗?不就是编译器用来帮助我找到潜在bug的吗?
虽然这种说法没错,但随着你对TypeScript不断了解,你会发现这门编程语言最不可思议的力量在于编写、推断和操作数据类型 。
本文总结的一些技巧,可以帮助大家充分发挥TypeScript的潜力 。
#1 用集合的概念思考问题数据类型是程序员日常要处理的概念,但要简洁地定义它却出奇地困难 。然而我发现集合非常适合用作概念模型 。
刚开始学习TypeScript时,我们常常会发现用TypeScript编写类型的方式很不自然 。举一个非常简单的例子:
type Measure = { radius: number };type Style = { color: string };// typed { radius: number; color: string }type Circle = Measure & Style;如果你在逻辑AND的意义上解释运算符&,可能会认为Circle是一个虚拟类型,因为它是两种类型的结合,没有任何重叠的字段 。这不是TypeScript的工作方式 。此时通过集合的概念思考更容易推断出正确的行为:
  • 每个类型都是一系列值的集合 。
  • 有些集合是无限的:例如string、object;有些是有限的:例如bool,undefined,...
  • unknown?是通用集(包括所有值),而never是空集(包括无值) 。
  • 类型Measure是包含radius数字字段的所有对象的集合 。style也是如此 。
  • &?运算符创建一个交集:Measure & Style表示包含radius和color的对象集,这实际上是一个较小的集合,字段更常用 。
  • 同理,|运算符创建一个并集:一个较大的集合,但常用字段可能较少(如果组合两个对象类型的话) 。
集合还有助于了解可分配性:仅当值的类型是目标类型的子集时,才允许赋值:
type ShapeKind = 'rect' | 'circle';let foo: string = getSomeString();let shape: ShapeKind = 'rect';// disallowed because string is not subset of ShapeKindshape = foo;// allowed because ShapeKind is subset of stringfoo = shape;#2 了解声明类型和收窄类型TypeScript中一个非常强大的功能是基于控制流的自动类型收窄 。这意味着变量在代码位置的任何特定点都有两种与之关联的类型:声明类型和收窄类型 。
function foo(x: string | number) {if (typeof x === 'string') {// x's type is narrowed to string, so .length is validconsole.log(x.length);// assignment respects declaration type, not narrowed typex = 1;console.log(x.length); // disallowed because x is now number} else {...}}#3 使用可区分的联合类型而不是可选字段当定义一组多态类型(如Shape)时,很容易这样开始写代码:
type Shape = {kind: 'circle' | 'rect';radius?: number;width?: number;height?: number;}function getArea(shape: Shape) {return shape.kind === 'circle' ?Math.PI * shape.radius! ** 2: shape.width! * shape.height!;}需要非空断言(访问radius、width和height?时),因为kind和其他字段之间没有建立关系 。相反,可区分的联合类型是一个更好的解决方案:
type Circle = { kind: 'circle'; radius: number };type Rect = { kind: 'rect'; width: number; height: number };type Shape = Circle | Rect;function getArea(shape: Shape) {return shape.kind === 'circle' ?Math.PI * shape.radius ** 2: shape.width * shape.height;}从以上代码可以看出,类型收窄消除了强制类型转换的需要 。
#4 使用类型谓词避免类型断言如果你以正确的方式使用TypeScript的话,你会发现自己很少使用显式类型断言(比如value as SomeType);但是,有时你可能会冲动地写出诸如这样的代码:
type Circle = { kind: 'circle'; radius: number };type Rect = { kind: 'rect'; width: number; height: number };type Shape = Circle | Rect;function isCircle(shape: Shape) {return shape.kind === 'circle';}function isRect(shape: Shape) {return shape.kind === 'rect';}const myShapes: Shape[] = getShapes();// error because typescript doesn't know the filtering// narrows typingconst circles: Circle[] = myShapes.filter(isCircle);// you may be inclined to add an assertion:// const circles = myShapes.filter(isCircle) as Circle[];更优雅的解决方案是将isCircle和isRect?更改为返回类型谓词,这样就可以帮助TypeScript在filter调用后进一步收窄类型:
function isCircle(shape: Shape): shape is Circle {return shape.kind === 'circle';}function isRect(shape: Shape): shape is Rect {return shape.kind === 'rect';}...// now you get Circle[] type inferred correctlyconst circles = myShapes.filter(isCircle);


推荐阅读