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

240 阅读3分钟

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

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

技巧11: 认识到额外属性检查的限制

一. 额外属性检查

如果你将一个对象常量, 赋值给一有类型的变量, 就会启动额外属性检查. 也就是看对象常量有没有其他的额外的属性.

interface Room {
  numDoors: number;
  ceilingHeightFt: number;
}
const r: Room = {
  numDoors: 1,
  ceilingHeightFt: 10,
  elephant: 'present',
// ~~~~~~~~~~~~~~~~~~ Object literal may only specify known properties,
//                    and 'elephant' does not exist in type 'Room'
};

大家可能会感到奇怪: 这明明符合类型兼容(见 技巧4)啊! 为啥会报错呢? 我们先看另一个例子, 如果我们引入一个中间变量, 这个报错又会取消.

const obj = {
  numDoors: 1,
  ceilingHeightFt: 10,
  elephant: 'present',
};
const r: Room = obj;  // OK

这个报错能取消, 是因为中间变量obj符合类型兼容的原则.

那么两个例子的区别在于什么地方呢?

在第一个例子, 触发了额外属性检查, 用来捕捉一些重要的错误. 那他会捕捉哪些重要错误呢?

interface Options {
  title: string;
  darkMode?: boolean;
}
function createWindow(options: Options) {
  if (options.darkMode) {
    setDarkMode();
  }
  // ...
}
createWindow({
  title: 'Spider Solitaire',
  darkmode: true
// ~~~~~~~~~~~~~ Object literal may only specify known properties, but
//               'darkmode' does not exist in type 'Options'.
//               Did you mean to write 'darkMode'?
});

这段代码在js运行时不会报错. 但是他也无法正确执行: 正如ts所说: 属性名应该是darkMode, 而不是darkmode.

在我们之前了解到的结构类型检查不会报这个错. 因为Options定义十分宽泛. 宽泛到下面的代码也能通过:

const o1: Options = document;  // OK
const o2: Options = new HTMLAnchorElement;  // OK

因为documen有title的属性.

但是额外的属性检查能控制这个错误. 给有类型的变量赋值的时候, 额外属性检查: 对象常量不能有额外的属性. 这上面的例子中, document 和 new HTMLAnchorElement 不是对象常量. 所以不会触发额外属性检查, 但是{title, darkmode}是对象常量:

const o: Options = { darkmode: true, title: 'Ski Free' };
                  // ~~~~~~~~ 'darkmode' does not exist in type 'Options'...

这也就解释了, 加一个中间变量就不会触发额外属性检查.

const intermediate = { darkmode: true, title: 'Ski Free' };
const o: Options = intermediate;  // OK

当你是使用断言的时候不会触发额外属性检查:

const intermediate = { darkmode: true, title: 'Ski Free' };
const o: Options = intermediate;  // OK

所以我们又多了一个不使用断言的理由

如果我们不想要额外属性检查, 可以这样写:

interface Options {
  darkMode?: boolean;
  [otherOptions: string]: unknown;
}
const o: Options = { darkmode: true };  // OK

二. 弱类型的检查

和额外属性检查相似的有: 弱类型检查:

interface LineChartOptions {
  logscale?: boolean;
  invertedYAxis?: boolean;
  areaChart?: boolean;
}
const opts = { logScale: true };
const o: LineChartOptions = opts;
   // ~ Type '{ logScale: boolean; }' has no properties in common
   //   with type 'LineChartOptions'

从结构角度看: LineChartOptions应该包含所有对象. 但是对于像LineChartOptions这样的弱类型, ts增加了一个检查: 值类型和申明类型至少要有一个属性相同.

弱类型检查和额外检查相比:

  • 相同的是: 都能找到一般检查找不到的错误
  • 不同的是: 弱类型要求type的所有属性都是可选的, 就像LineChartOptions一样. 用中间变量不能绕过弱类型检查