跳至主要內容

物件

物件可以在 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禁止在你的程式碼中使用它。