与我交流
与我交流

Freshness

为了能让检查对象字面量类型更容易,TypeScript 提供 「Freshness」 的概念(它也被称为更严格的对象字面量检查)用来确保对象字面量在结构上类型兼容。

结构类型非常方便。考虑如下例子代码,它可以让你非常便利的从 JavaScript 迁移至 TypeScript,并且会提供类型安全:

function logName(something: { name: string }) {
  console.log(something.name);
}

const person = { name: 'matt', job: 'being awesome' };
const animal = { name: 'cow', diet: 'vegan, but has milk of own specie' };
const randow = { note: `I don't have a name property` };

logName(person); // ok
logName(animal); // ok
logName(randow); // Error: 没有 `name` 属性

但是,结构类型有一个缺点,它能误导你认为某些东西接收的数据比它实际的多。如下例,TypeScript 发出错误警告:

function logName(something: { name: string }) {
  console.log(something.name);
}

logName({ name: 'matt' }); // ok
logName({ name: 'matt', job: 'being awesome' }); // Error: 对象字面量只能指定已知属性,`job` 属性在这里并不存在。

WARNING

请注意,这种错误提示,只会发生在对象字面量上。

如果没有这种错误提示,我们可能会去寻找函数的调用 logName({ name: 'matt', job: 'being awesome' }),继而会认为 logName 可能会使用 job 属性做一些事情,然而实际上 logName 并没有使用它。

另外一个使用比较多的场景是与具有可选成员的接口一起使用,如果没有这样的对象字面量检查,当你输入错误单词的时候,并不会发出错误警告:

function logIfHasName(something: { name?: string }) {
  if (something.name) {
    console.log(something.name);
  }
}

const person = { name: 'matt', job: 'being awesome' };
const animal = { name: 'cow', diet: 'vegan, but has milk of own species' };

logIfHasName(person); // okay
logIfHasName(animal); // okay

logIfHasName({ neme: 'I just misspelled name to neme' }); // Error: 对象字面量只能指定已知属性,`neme` 属性不存在。

之所以只对对象字面量进行类型检查,因为在这种情况下,那些实际上并没有被使用到的属性有可能会拼写错误或者会被误用。

允许额外的属性

一个类型能够包含索引签名,以明确表明可以使用额外的属性:

let x: { foo: number, [x: string]: any };

x = { foo: 1, baz: 2 }; // ok, 'baz' 属性匹配于索引签名

用例:React State

Facebook ReactJS 为对象的 Freshness 提供了一个很好的用例,通常在组件中,你只使用少量属性,而不是传入所有,来调用 setState

// 假设
interface State {
  foo: string;
  bar: string;
}

// 你可能想做:
this.setState({ foo: 'Hello' }); // Error: 没有属性 'bar'

// 因为 state 包含 'foo' 与 'bar',TypeScript 会强制你这么做:
this.setState({ foo: 'Hello', bar: this.state.bar });

如果你想使用 Freshness,你可能需要将所有成员标记为可选,这仍然会捕捉到拼写错误:

// 假设
interface State {
  foo?: string;
  bar?: string;
}

// 你可能想做
this.setState({ foo: 'Hello' }); // Yay works fine!

// 由于 Freshness,你也可以防止错别字
this.setState({ foos: 'Hello' }}; // Error: 对象只能指定已知属性

// 仍然会有类型检查
this.setState({ foo: 123 }}; // Error: 无法将 number 类型赋值给 string 类型