ReactのコンポーネントにFlowの型をつける

以前の記事でFlowの導入が完了し、Reactを使って書いたコードに型を付けることが出来るようになった。
ここからは実際に、Reactのコンポーネントに型を付けていく。

Flow導入の記事はこちら。

なお、この記事を書いている環境のFlowのバージョンは、0.46.0

対象となるコンポーネントを用意

以下のようなコンポーネントを作成した。
このコンポーネントに、型を付けていく。

class App extends React.Component {
  render() {
    return (
      <div>{this.props.name}</div>
    );
  }
}

<App name="Tom" />のようにnameをpropsとして渡して使うだけの、シンプルなコンポーネント

最初に、このファイルの一番上に// @flowと記述する。
この記述があるファイルのみが、Flowによるチェックの対象となる。

propsに型を付ける

まずはprops.nameに、型をつけてみる。
下記のようにprops:と記述すればいい。

class App extends React.Component {
  props: {
    name: string,
  };
  render() {
    return (
      <div>{this.props.name}</div>
    );
  }
}

プロパティの名前: 型と定義するのが、基本。
型の種類は多数あり、公式ドキュメントで確認できる。
Type Annotations | Flow

上記ではnameの型として文字列を指定しており、<App name={true} />のように文字列以外を渡してFlowを実行すると、エラーになる。

次に、Appを次のように書き換えてみる。

class App extends React.Component {
  props: {
    name: string,
  }
  render() {
    return (
      <div>
        {this.props.name}<br />
        {this.props.age}
      </div>
    );
  }
}

そして、<App name="Tom" age={30} />としてnameageを渡す。

この状態でFlowを実行するとどうなるか。
ageの型が定義されていないとして、エラーになる。
これを解消するには、ageの型も設定すればいい。

props: {
  name: string,
  age: number,
}

このように、型を指定すれば、エラーは消える。
ではこの状態で<App name="Tom" />としてageを渡さなかった場合、どうなるか。
やはりエラーになる。

上記の設定だと、「ageにはかならずnumberが渡される」ことを意味しており、他のデータ型が渡されたときはもちろん、値が渡されなかったときにもエラーとなる。

このような、値が渡されたときの型は決まっているが、値が渡されないこともある、というケースでは、以下のように設定すればよい。

props: {
  name: string,
  age?: number,
}

?がついているが、これはOptional Propertyというもので、このプロパティは文字通りオプショナルになる。
つまり、値を渡しても渡さなくてもよい。
これで、先程のエラーは消える。

Optional object type properties

論理和

要するにOR
これは何も難しいことはなく、プロパティの名前: 型 | 型とすればいい。

type Prop = {
  name: string,
  age: number | string,
};

このように書くと、ageは数値か文字列のいずれか、という指定になる。
そのため<App name="Tom" age="30" />でもエラーにならない。

独自の型定義

自分で型を定義することも出来る。
typeで宣言して、定義する。

下記では、Propという型を定義し、それをApppropsに設定している。

type Prop = {
  name: string,
  age: number,
};

class App extends React.Component {
  props: Prop;
  render() {
    return (
      <div>
        {this.props.name}<br />
        {this.props.age}
      </div>
    );
  }
}

型の定義が長くなるようであれば、このほうが可読性が増すかもしれない。

何より、型を再利用することが出来る。
同じデータ構造が何度も出てくるのなら当然、一箇所で定義して使いまわしたほうがいい。
exportすることも出来るので、ファイルをまたがって使うことが出来る。

stateに型を付ける

Appを拡張してstateやメソッドを追加し、stateに型を付けたのが、以下。

type Prop = {
  name: string,
  age: number,
};

type State = {
  isYouth: boolean,
};

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isYouth: this.isYouth() };
  }
  state: State;
  props: Prop;
  isYouth() {
    return this.props.age < 30;
  }
  render() {
    return (
      <div>
        {this.props.name}<br />
        {this.props.age}
      </div>
    );
  }
}

stateの場合も、propsの場合と何も変わらない。

メソッドにも型をつける

先程の状態でFlowを実行してもエラーにはならないが、メソッドにも型をつけたほうが望ましい。

まず、constructor
引数として渡されるpropsの型を、Propとして指定している。

constructor(props: Prop) {
  super(props);
  this.state = { isYouth: this.isYouth() };
}

次にisYouth。返り値は真偽値であると指定している。

isYouth(): boolean {
  return this.props.age < 30;
}

Stateless Functinal Componenens

Reactのコンポーネントは関数で定義することも出来るが、それに対しては、次のように型を付ける。

const App = (props: { name: string, age: number }) => (
  <div>
    {props.name}<br />
    {props.age}
  </div>
);

参考資料