メインコンテンツにスキップ

クラス

Flow における JavaScript クラスは、値と型の両方として機能します。クラスの名前を、そのインスタンスの型として使用できます。

1class MyClass {2  // ...3}4
5const myInstance: MyClass = new MyClass(); // Works!

これは、Flow のクラスが名目型付けされているためです。

つまり、同じ形状の 2 つのクラスには互換性がないということです。

1class A {2  x: number;3}4class B {5  x: number;6}7const foo: B = new A(); // Error!
8const bar: A = new B(); // Error!
7:16-7:22: Cannot assign `new A()` to `foo` because `A` [1] is incompatible with `B` [2]. [incompatible-type]
8:16-8:22: Cannot assign `new B()` to `bar` because `B` [1] is incompatible with `A` [2]. [incompatible-type]

また、オブジェクト型を使用してクラスのインスタンスを記述することはできません。

1class MyClass {2  x: number;3}4const foo: {x: number, ...} = new MyClass(); // Error!
4:31-4:43: Cannot assign `new MyClass()` to `foo` because `MyClass` [1] is not a subtype of object type [2]. Class instances are not subtypes of object types; consider rewriting object type [2] as an interface. [class-object-subtyping]

代わりに、これを実現するためにインターフェースを使用できます。

1class A {2  x: number;3}4class B {5  x: number;6}7
8interface WithXNum {9  x: number;10}11
12const foo: WithXNum = new A(); // Works!13const bar: WithXNum = new B(); // Works!14
15const n: number = foo.x; // Works!

クラス構文

Flow のクラスは、通常の JavaScript クラスとまったく同じですが、型が追加されています。

クラスメソッド

関数と同じように、クラスメソッドはパラメータ(入力)と戻り値(出力)の両方に注釈を付けることができます。

1class MyClass {2  method(value: string): number {3    return 0;4  }5}

また、通常の関数と同様に、クラスメソッドには this 注釈を含めることもできます。ただし、注釈が指定されていない場合、Flow は mixed の代わりにクラスインスタンス型(または静的メソッドの場合はクラス型)を推論します。明示的な this パラメータが提供されている場合は、クラスインスタンス型(または静的メソッドの場合はクラス型)のスーパータイプである必要があります。

1class MyClass {2  method(this: interface {x: string}) { /* ... */ } // Error!
3}
2:3-2:8: Cannot define method `method` [1] on `MyClass` because property `x` is missing in `MyClass` [2] but exists in interface type [3]. [prop-missing]

ただし、クラスプロパティとは異なり、クラスメソッドは、定義したクラスからバインド解除または再バインドすることはできません。したがって、以下はすべて Flow でエラーになります。

1class MyClass { method() {} }2const a = new MyClass();3a.method; // Error!
4const {method} = a; // Error!
5a.method.bind({}); // Error!
3:3-3:8: Cannot get `a.method` because property `method` [1] cannot be unbound from the context [2] where it was defined. [method-unbinding]
4:8-4:13: property `method` [1] cannot be unbound from the context [2] where it was defined. [method-unbinding]
5:3-5:8: Cannot get `a.method` because property `method` [1] cannot be unbound from the context [2] where it was defined. [method-unbinding]

メソッドは読み取り専用と見なされます。

1class MyClass {2  method() {}3}4
5const a = new MyClass();6a.method = function() {}; // Error!
6:3-6:8: Cannot assign function to `a.method` because property `method` is not writable. [cannot-write]

Flow は、ES2022 の機能であるプライベートメソッドをサポートしています。プライベートメソッドは、ハッシュ記号 # で始まります。

1class MyClass {2  #internalMethod() {3    return 1;4  }5  publicApi() {6    return this.#internalMethod();7  }8}9
10const a = new MyClass();11a.#internalMethod(); // Error!
12a.publicApi(); // Works!
11:3-11:17: Private fields can only be referenced from within a class.

Flow では、ほとんどの場合、メソッドに返り値の型注釈が必要です。これは、メソッド内で this を参照することが一般的であり、this はクラスのインスタンスとして型付けされているためです。しかし、クラスの型を知るには、そのメソッドの返り値の型を知る必要があります。

1class MyClass {2  foo() { // Error!
3 return this.bar();4 }5 bar() { // Error!
6 return 1;7 }8}
2:8-2:7: Missing an annotation on return. [missing-local-annot]
5:8-5:7: Missing an annotation on return. [missing-local-annot]
1class MyClassFixed {2  foo(): number { // Works!3    return this.bar();4  }5  bar(): number { // Works!6    return 1;7  }8}

クラスフィールド(プロパティ)

Flow でクラスフィールドを使用する場合は、最初に注釈を付ける必要があります。

1class MyClass {2  method() {3    this.prop = 42; // Error!
4 }5}
3:10-3:13: Cannot assign `42` to `this.prop` because property `prop` is missing in `MyClass` [1]. [prop-missing]

フィールドは、クラスの本体内で、フィールド名の後にコロン : と型を続けて注釈が付けられます。

1class MyClass {2  prop: number;3  method() {4    this.prop = 42;5  }6}

クラス定義の外で追加されたフィールドは、クラスの本体内で注釈を付ける必要があります。

1function func(x: number): number {2  return x + 1;3}4
5class MyClass {6  static constant: number;7  static helper: (number) => number;8  prop: number => number;9}10MyClass.helper = func11MyClass.constant = 4212MyClass.prototype.prop = func

Flow は、クラスプロパティ構文の使用もサポートしています。

1class MyClass {2  prop = 42;3}

この構文を使用する場合、型注釈を付ける必要はありません。ただし、必要な場合は、引き続き注釈を付けることができます。

1class MyClass {2  prop: number = 42;3}

分散注釈を使用して、クラスフィールドを読み取り専用(または書き込み専用)としてマークできます。これらはコンストラクター内でのみ書き込むことができます。

1class MyClass {2  +prop: number;3
4  constructor() {5    this.prop = 1; // Works!6  }7
8  method() {9    this.prop = 1; // Error!
10 }11}12 13const a = new MyClass();14const n: number = a.prop; // Works!15a.prop = 1; // Error!
9:10-9:13: Cannot assign `1` to `this.prop` because property `prop` is not writable. [cannot-write]
15:3-15:6: Cannot assign `1` to `a.prop` because property `prop` is not writable. [cannot-write]

Flow は、ES2022 の機能であるプライベートフィールドをサポートしています。プライベートフィールドは、ハッシュ記号 # で始まります。

1class MyClass {2  #internalValue: number;3
4  constructor() {5    this.#internalValue = 1;6  }7
8  publicApi() {9    return this.#internalValue;10  }11}12
13const a = new MyClass();14const x: number = a.#internalValue; // Error!
15const y: number = a.publicApi(); // Works!
14:21-14:34: Private fields can only be referenced from within a class.

クラスの拡張とインターフェースの実装

必要に応じて、別のクラスを extend できます。

1class Base {2  x: number;3}4
5class MyClass extends Base {6  y: string;7}

また、複数のインターフェースを実装することもできます。

1interface WithXNum {2  x: number;3}4interface Readable {5  read(): string;6}7
8class MyClass implements WithXNum, Readable {9  x: number;10  read(): string {11    return String(this.x);12  }13}

インターフェースのサブタイプであるために implement する必要はありませんが、そうすることで、クラスが要件を満たしていることを強制できます。

1interface WithXNum {2  x: number;3}4
5class MyClass implements WithXNum { // Error!
6}
5:7-5:13: Cannot implement `WithXNum` [1] with `MyClass` because property `x` is missing in `MyClass` [2] but exists in `WithXNum` [1]. [prop-missing]

クラスコンストラクター

クラスコンストラクターでクラスプロパティを初期化できます。

1class MyClass {2  foo: number;3
4  constructor() {5    this.foo = 1;6  }7}

this および super にアクセスする前に、派生クラスで最初に super(...) を呼び出す必要があります。

1class Base {2  bar: number;3}4
5class MyClass extends Base {6  foo: number;7
8  constructor() {9    this.foo; // Error
10 this.bar; // Error
11 super.bar; // Error
12 super();13 this.foo; // OK14 this.bar; // OK15 super.bar; // OK16 }17}
9:5-9:8: Must call `super` before accessing this [1] in a derived constructor. [reference-before-declaration]
10:5-10:8: Must call `super` before accessing this [1] in a derived constructor. [reference-before-declaration]
11:5-11:9: Must call `super` before accessing super [1] in a derived constructor. [reference-before-declaration]

ただし、Flow はコンストラクターですべてのクラスプロパティが初期化されることを強制しません。

1class MyClass {2  foo: number;3  bar: number;4
5  constructor() {6    this.foo = 1;7  }8
9  useBar() {10    (this.bar: number); // No errors.11  }12}

クラスジェネリクス

クラスには独自のジェネリクスを含めることもできます。

1class MyClass<A, B, C> {2  property: A;3  method(val: B): C {4    throw new Error();5  }6}

クラスジェネリクスはパラメーター化されています。クラスを型として使用する場合は、そのジェネリクスのそれぞれにパラメーターを渡す必要があります。

1class MyClass<A, B, C> {2  constructor(arg1: A, arg2: B, arg3: C) {3    // ...4  }5}6
7const val: MyClass<number, boolean, string> = new MyClass(1, true, 'three');

注釈内のクラス

注釈でクラスの名前を使用する場合、それはクラスのインスタンスを意味します。

class MyClass {}

const b: MyClass = new MyClass(); // Works!
const a: MyClass = MyClass; // Error!

注釈でクラスの型を参照できるようにする Class<T> の詳細については、こちらを参照してください。