一起养成写作习惯!这是我参与「掘金日新计划 · 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) 都会进行类型检查.