オブジェクト
オブジェクトは 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]
オブジェクトメソッド
オブジェクトのメソッド構文は、関数プロパティと同じ実行時の動作をします。これらの 2 つのオブジェクトは、実行時には同等です
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]
厳密なオブジェクト型と非厳密なオブジェクト型
.flowconfig
で exact_by_default=false
を設定していない限り、厳密なオブジェクト型がデフォルトです (バージョン 0.202 以降)。
非厳密なオブジェクト (...
で示されます) は、追加のプロパティを渡すことを許可します
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]
実行時にオブジェクト値をスプレッドすると、「own」プロパティ (つまり、オブジェクトに直接存在するプロパティであり、プロトタイプチェーン上ではないプロパティ) のみがスプレッドされます。オブジェクト型スプレッドも同じように機能します。このため、インターフェースをスプレッドすることはできません。インターフェースはプロパティが「own」であるかどうかを追跡しないためです
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
のエイリアスにすぎず、安全ではありません。不明確な型のリンターを使用して、コードでの使用を禁止できます。