跳至主要内容

对象

对象可以在 JavaScript 中以多种不同的方式使用。为了支持不同的用例,有许多方法可以对它们进行类型化。

  • 精确对象类型:具有精确属性集的对象,例如 {a: number}。我们建议使用精确对象类型而不是不精确对象类型,因为它们更精确,并且与其他类型系统功能(如 扩展)更好地交互。
  • 不精确对象类型:至少具有属性集的对象,但可能还有其他未知属性,例如 {a: number, ...}
  • 带有索引器的对象:可以用作从键类型到值类型的映射的对象,例如 {[string]: boolean}
  • 接口:接口与对象类型是分开的。只有它们可以描述类的实例。例如 interfaces {a: number}

对象类型尽可能地匹配 JavaScript 中对象的语法。使用花括号 {} 和使用冒号 : 分隔的名称-值对,用逗号 , 分隔。

1const obj1: {foo: boolean} = {foo: true};2const obj2: {3  foo: number,4  bar: boolean,5  baz: string,6} = {7  foo: 1,8  bar: true,9  baz: 'three',10};

可选对象类型属性

在 JavaScript 中,访问不存在的属性会评估为 undefined。这是 JavaScript 程序中常见的错误来源,因此 Flow 会将这些错误转换为类型错误。

1const obj = {foo: "bar"};2obj.bar; // Error!
2:5-2:7: Cannot get `obj.bar` because property `bar` is missing in object literal [1]. [prop-missing]

如果您有一个有时没有属性的对象,可以通过在对象类型中的属性名称后添加问号 ? 来将其设为可选属性

1const obj: {foo?: boolean} = {};2
3obj.foo = true;    // Works!4obj.foo = 'hello'; // Error!
4:11-4:17: Cannot assign `'hello'` to `obj.foo` because string [1] is incompatible with boolean [2]. [incompatible-type]

除了它们的设置值类型之外,这些可选属性还可以是 void 或完全省略。但是,它们不能是 null

1function acceptsObject(value: {foo?: string}) { /* ... */ }2
3acceptsObject({foo: "bar"});     // Works!4acceptsObject({foo: undefined}); // Works!5acceptsObject({});               // Works!6
7acceptsObject({foo: null});      // Error!
7:21-7:24: Cannot call `acceptsObject` with object literal bound to `value` because null [1] is incompatible with string [2] in property `foo`. [incompatible-call]

要使对象类型中的所有属性都可选,可以使用 Partial 实用程序类型

1type Obj = {2  foo: string,3};4
5type PartialObj = Partial<Obj>; // Same as `{foo?: string}`

要使对象类型中的所有属性都必填,可以使用 Required 实用程序类型

1type PartialObj = {2  foo?: string,3};4
5type Obj = Required<PartialObj>; // Same as `{foo: string}`

只读对象属性

您可以将 方差 注解添加到您的对象属性。

要将属性标记为只读,可以使用 +

1type Obj = {2  +foo: string,3};4
5function func(o: Obj) {6  const x: string = o.foo; // Works!7  o.foo = 'hi'; // Error!
8}
7:5-7:7: Cannot assign `'hi'` to `o.foo` because property `foo` is not writable. [cannot-write]

要使对象类型中的所有对象属性都为只读,可以使用 $ReadOnly 实用程序类型

1type Obj = {2  foo: string,3};4
5type ReadOnlyObj = $ReadOnly<Obj>; // Same as `{+foo: string}`

您还可以使用 - 将属性标记为只写

1type Obj = {2  -foo: string,3};4
5function func(o: Obj) {6  const x: string = o.foo; // Error!
7 o.foo = 'hi'; // Works!8}
6:23-6:25: Cannot get `o.foo` because property `foo` is not readable. [cannot-read]

对象方法

对象中的方法语法与函数属性具有相同的运行时行为。这两个对象在运行时是等效的

1const a = {2  foo: function () { return 3; }3};4const b = {5  foo() { return 3; }6}

但是,尽管它们的运行时行为等效,但 Flow 对它们的检查略有不同。特别是,使用方法语法编写的对象属性是 只读;Flow 不允许您向它们写入新值。

1const b = {2  foo() { return 3; }3}4b.foo = () => { return 2; } // Error!
4:3-4:5: Cannot assign function to `b.foo` because property `foo` is not writable. [cannot-write]

此外,对象方法不允许在它们的代码体中使用 this,以保证其 this 参数的简单行为。最好通过名称引用对象,而不是使用 this

1const a = {2  x: 3,3  foo() { return this.x; } // Error!
4}5const b = {6 x: 3,7 foo(): number { return b.x; } // Works!8}
3:18-3:21: Cannot reference `this` from within method `foo` [1]. For safety, Flow restricts access to `this` inside object methods since these methods may be unbound and rebound. Consider replacing the reference to `this` with the name of the object, or rewriting the object as a class. [object-this-reference]

对象类型推断

注意:空对象字面量的行为在版本 0.191 中发生了变化 - 有关更多详细信息,请参阅此 博客文章

当您创建对象值时,它的类型在创建点设置。您不能添加新属性,也不能修改现有属性的类型。

1const obj = {2  foo: 1,3  bar: true,4};5
6const n: number  = obj.foo; // Works!7const b: boolean = obj.bar; // Works!8
9obj.UNKNOWN; // Error - prop `UNKNOWN` is not in the object value
10obj.foo = true; // Error - `foo` is of type `number`
9:5-9:11: Cannot get `obj.UNKNOWN` because property `UNKNOWN` is missing in object literal [1]. [prop-missing]
10:11-10:14: Cannot assign `true` to `obj.foo` because boolean [1] is incompatible with number [2]. [incompatible-type]

如果您提供类型注解,则可以将对象值中缺少的属性添加为可选属性

1const obj: {2  foo?: number,3  bar: boolean,4} = {5  // `foo` is not set here6  bar: true,7};8
9const n: number | void = obj.foo; // Works!10const b: boolean = obj.bar; // Works!11
12if (b) {13  obj.foo = 3; // Works!14}

您还可以为特定属性提供更宽泛的类型

1const obj: {2  foo: number | string,3} = {4  foo: 1,5};6
7const foo: number | string = obj.foo; // Works!8obj.foo = "hi"; // Works!

如果提供适当的类型注解,空对象可以解释为 字典

1const dict: {[string]: number} = {}; // Works!

您可能需要向对象字面量添加类型注解,如果它递归地引用自身(超出简单情况)

1const Utils = { // Error
2 foo() {3 return Utils.bar();4 },5 bar() {6 return 1;7 }8};9 10const FixedUtils = { // Works!11 foo(): number {12 return FixedUtils.bar();13 },14 bar(): number {15 return 1;16 }17};
1:7-1:11: Cannot compute a type for `Utils` because its definition includes references to itself [1]. Please add an annotation to these definitions [2] [3] [recursive-definition]

精确和不精确对象类型

精确对象类型是默认类型(从版本 0.202 开始),除非您在 .flowconfig 中设置了 exact_by_default=false

不精确对象(用 ... 表示)允许传入额外的属性

1function method(obj: {foo: string, ...}) { /* ... */ }2
3method({foo: "test", bar: 42}); // Works!

注意:这是因为 "宽度子类型化"

但精确对象类型不会

1function method(obj: {foo: string}) { /* ... */ }2
3method({foo: "test", bar: 42}); // Error!
3:8-3:29: Cannot call `method` with object literal bound to `obj` because property `bar` is missing in object type [1] but exists in object literal [2]. [prop-missing]

如果您已设置 exact_by_default=false,则可以通过在花括号内添加一对“竖线”或“管道”来表示精确对象类型

1const x: {|foo: string|} = {foo: "Hello", bar: "World!"}; // Error!
1:28-1:56: Cannot assign object literal to `x` because property `bar` is missing in object type [1] but exists in object literal [2]. [prop-missing]

精确对象类型的交叉 可能无法按预期工作。如果您需要组合精确对象类型,请使用 对象类型扩展

1type FooT = {foo: string};2type BarT = {bar: number};3
4type FooBarT = {...FooT, ...BarT};5const fooBar: FooBarT = {foo: '123', bar: 12}; // Works!6
7type FooBarFailT = FooT & BarT;8const fooBarFail: FooBarFailT = {foo: '123', bar: 12}; // Error!
8:33-8:53: Cannot assign object literal to `fooBarFail` because property `bar` is missing in `FooT` [1] but exists in object literal [2]. [prop-missing]
8:33-8:53: Cannot assign object literal to `fooBarFail` because property `foo` is missing in `BarT` [1] but exists in object literal [2]. [prop-missing]

对象类型扩展

就像您可以扩展对象值一样,您也可以扩展对象类型

1type ObjA = {2  a: number,3  b: string,4};5
6const x: ObjA = {a: 1, b: "hi"};7
8type ObjB = {9  ...ObjA,10  c: boolean,11};12
13const y: ObjB = {a: 1, b: 'hi', c: true}; // Works!14const z: ObjB = {...x, c: true}; // Works!

您必须小心扩展不精确对象。结果对象也必须是不精确的,并且扩展的不精确对象可能具有未知属性,这些属性可以以未知方式覆盖之前的属性

1type Inexact = {2  a: number,3  b: string,4  ...5};6
7type ObjB = { // Error!
8 c: boolean,
9 ...Inexact,
10};
11 12const x: ObjB = {a:1, b: 'hi', c: true};
7:13-10:1: Flow cannot determine a type for object type [1]. `Inexact` [2] is inexact, so it may contain `c` with a type that conflicts with `c`'s definition in object type [1]. Try making `Inexact` [2] exact. [cannot-spread-inexact]
9:6-9:12: inexact `Inexact` [1] is incompatible with exact object type [2]. [incompatible-exact]

具有 索引器 的对象也存在相同的问题,因为它们也具有未知键

1type Dict = {2  [string]: number,3};4
5type ObjB = { // Error!
6 c: boolean,
7 ...Dict,
8};
9 10const x: ObjB = {a: 1, b: 2, c: true};
5:13-8:1: Flow cannot determine a type for object type [1]. `Dict` [2] cannot be spread because the indexer string [3] may overwrite properties with explicit keys in a way that Flow cannot track. Try spreading `Dict` [2] first or remove the indexer. [cannot-spread-indexer]

在运行时扩展对象值只会扩展“自身”属性,即直接位于对象上的属性,而不是原型链。对象类型扩展的工作方式相同。因此,您不能扩展 接口,因为它们不跟踪属性是“自身”还是否。

1interface Iface {2  a: number;3  b: string;4}5
6type ObjB = { // Error!
7 c: boolean,
8 ...Iface,
9};
10 11const x: ObjB = {a: 1, b: 'hi', c: true};
6:13-9:1: Flow cannot determine a type for object type [1]. `Iface` [2] cannot be spread because interfaces do not track the own-ness of their properties. Try using an object type instead. [cannot-spread-interface]

对象作为映射

JavaScript 包含一个 Map 类,但使用对象作为映射仍然很常见。在这种情况下,对象很可能在其整个生命周期中添加属性并检索属性。此外,属性键甚至可能在静态情况下未知,因此编写类型注解将是不可能的。

对于这样的对象,Flow 提供了一种特殊类型的属性,称为“索引器属性”。索引器属性允许使用与索引器键类型匹配的任何键进行读写。

1const o: {[string]: number} = {};2o["foo"] = 0;3o["bar"] = 1;4const foo: number = o["foo"];

索引器可以可选地命名,用于文档目的

1const obj: {[user_id: number]: string} = {};2obj[1] = "Julia";3obj[2] = "Camille";4obj[3] = "Justin";5obj[4] = "Mark";

当对象类型具有索引器属性时,属性访问假定具有注释的类型,即使对象在运行时在该插槽中没有值。与数组一样,程序员有责任确保访问是安全的。

1const obj: {[number]: string} = {};2obj[42].length; // No type error, but will throw at runtime

索引器属性可以与命名属性混合使用

1const obj: {2  size: number,3  [id: number]: string4} = {5  size: 06};7
8function add(id: number, name: string) {9  obj[id] = name;10  obj.size++;11}

您可以使用 方差 注解将索引器属性标记为只读(或只写)

1type ReadOnly = {+[string]: number};2type WriteOnly = {-[string]: number};

键、值和索引访问

您可以使用 $Keys 实用程序类型提取对象类型的键

1type Obj = {2  foo: string,3  bar: number,4};5
6type T = $Keys<Obj>;7
8function acceptsKeys(k: T) { /* ... */ }9
10acceptsKeys('foo'); // Works!11acceptsKeys('bar'); // Works!12acceptsKeys('hi'); // Error!
12:13-12:16: Cannot call `acceptsKeys` with `'hi'` bound to `k` because property `hi` is missing in `Obj` [1]. [prop-missing]

您可以使用 $Values 实用程序类型提取对象类型的值

1type Obj = {2  foo: string,3  bar: number,4};5
6type T = $Values<Obj>;7
8function acceptsValues(v: T) { /* ... */ }9
10acceptsValues(2); // Works!11acceptsValues('hi'); // Works!12acceptsValues(true); // Error!
12:15-12:18: Cannot call `acceptsValues` with `true` bound to `v` because: [incompatible-call] Either boolean [1] is incompatible with string [2]. Or boolean [1] is incompatible with number [3].

您可以使用 索引访问类型 获取对象类型特定属性的类型

1type Obj = {2  foo: string,3  bar: number,4};5
6type T = Obj['foo'];7
8function acceptsStr(x: T) { /* ... */ }9
10acceptsStr('hi'); // Works!11acceptsStr(1); // Error!
11:12-11:12: Cannot call `acceptsStr` with `1` bound to `x` because number [1] is incompatible with string [2]. [incompatible-call]

任意对象

如果您想安全地接受任意对象,您可以使用几种模式。

空的不精确对象 {...} 接受任何对象

1function func(obj: {...}) {2  // ...3}4
5func({}); // Works!6func({a: 1, b: "foo"}); // Works!

对于绑定到接受任何对象的 泛型,它通常是正确的选择

1function func<T: {...}>(obj: T) {2  // ...3}4
5func({}); // Works!6func({a: 1, b: "foo"}); // Works!

但是,您无法访问 {...} 上的任何属性。

您还可以尝试使用具有 mixed 值的 字典,这将允许您访问任何属性(并产生 mixed 类型的结果)

1function func(obj: {+[string]: mixed}) {2  const x: mixed = obj['bar'];3}4
5func({}); // Works!6func({a: 1, b: "foo"}); // Works!

类型 Object 只是 any 的别名,是不安全的。您可以使用 unclear-type 代码风格检查 禁止在您的代码中使用它。