对象
对象可以在 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 代码风格检查 禁止在您的代码中使用它。