物件
物件可以在 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
的別名,是不安全的。你可以使用不明確類型 linter禁止在你的程式碼中使用它。