[译]<<Effective TypeScript>> 高效TypeScript62个技巧 技巧24

752 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情

本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.

技巧24: 如果使用别名,请保持连贯性

当你为你的值引入一个新的名字:

const borough = {name: 'Brooklyn', location: 
[40.688, -73.979]};
const loc = borough.location;

你已经创造了一个别名, 改变别名上的值, 原始的值也会改变.

> loc[0] = 0;
> borough.location
[0, -73.979]

对于所有语言编译器的作者,别名是他们非常痛恨的. 因为别名让语言做控制流分析非常困难. 如果你能更谨慎的使用别名, ts能更好的理解你的代码, 同时也能帮你发现更多的错误.

假如你有一个数据结构表示多边形:

interface Coordinate {
  x: number;
  y: number;
}

interface BoundingBox {
  x: [number, number];
  y: [number, number];
}

interface Polygon {
  exterior: Coordinate[];
  holes: Coordinate[][];
  bbox?: BoundingBox;
}

多边形的几何形状由 exterior 和holes 决定.bbox作为一个优化的属性,可能不存在. bbox可以用来加快检测一个点是否在多边形内部:

function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
  if (polygon.bbox) {
    if (pt.x < polygon.bbox.x[0] || pt.x > polygon.bbox.x[1] ||
        pt.y < polygon.bbox.y[1] || pt.y > polygon.bbox.y[1]) {
      return false;
    }
  }

  // ... more complex check
}

这段代码有效但是有点重复: polygon.bbox在三行内出现了5次.所以这里可以分解出一个中间变量来减少重复:

function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
  const box = polygon.bbox;
  if (polygon.bbox) {
    if (pt.x < box.x[0] || pt.x > box.x[1] ||
        //     ~~~                ~~~  Object is possibly 'undefined'
        pt.y < box.y[1] || pt.y > box.y[1]) {
        //     ~~~                ~~~  Object is possibly 'undefined'
      return false;
    }
  }
  // ...
}

这段代码为什么会报错? 因为你分解出一个box, 也就是polygon.bbox的别名. 这阻碍了ts进行控制流分析.

你可以查看box,polygon.bbox的类型, 看发生了什么:

function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
  polygon.bbox  // Type is BoundingBox | undefined
  const box = polygon.bbox;
  box  // Type is BoundingBox | undefined
  if (polygon.bbox) {
    polygon.bbox  // Type is BoundingBox
    box  // Type is BoundingBox | undefined
  }
}

属性检查器对 polygon.bbox的类型进行了缩小, 而不是缩小box, 因此出现了这个错误. 所以这才有了我们使用别名一定要注意的:如果使用别名,请保持连贯性. 一直使用box能修复这个问题:

function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
  const box = polygon.bbox;
  if (box) {
    if (pt.x < box.x[0] || pt.x > box.x[1] ||
        pt.y < box.y[1] || pt.y > box.y[1]) {  // OK
      return false;
    }
  }
  // ...
}

ts 检查器很开心. 但是人类就有些看不懂了, 对于同一个东西,却有两个名字: box , bbox. 这就是没有区别的区别(见技巧 36)

对象的解构语法能解决这个问题:

function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
  const {bbox} = polygon;
  if (bbox) {
    const {x, y} = bbox;
    if (pt.x < x[0] || pt.x > x[1] ||
        pt.y < x[0] || pt.y > y[1]) {
      return false;
    }
  }
  // ...
}

别名系统还会在js运行时引起混乱:

onst {bbox} = polygon;
if (!bbox) {
  calculatePolygonBbox(polygon);  // Fills in polygon.bbox
  // Now polygon.bbox and bbox refer to different values!
}

ts 对于控制流分析很有用, 但是对于属性分析, 你应该保持谨慎.

function fn(p: Polygon) { /* ... */ }

polygon.bbox  // Type is BoundingBox | undefined
if (polygon.bbox) {
  polygon.bbox  // Type is BoundingBox
  fn(polygon);
  polygon.bbox  // Type is still BoundingBox
}

每次调用fn(polygon) 都会进行类型检查.