FlowのGetting Started（React）

[Flow](https://flow.org)の[Getting Started](https://flow.org/en/docs/react/)を一通りさらった際のメモです。

# Setup Flow with React

今回は[Create React App](https://github.com/facebookincubator/create-react-app)でReactアプリの雛形を作ってしまいます。
これでアプリの雛形を作ってしまうと、Babelに対する設定も不要（`Create React App already supports Flow by default`）になるので、ひとまずFlowの機能を試すための最短手順になると思います。

公式に記載されているまんまですが、一応手順も貼っておきます。

```
create-react-app my-app && cd my-app
yarn add --dev flow-bin
yarn run flow init
```

# Visual Studio Codeのセットアップ

Getting Startedの手順からは外れますが、私はエディタにVS Codeを使っているので、FlowのExtensionをインストールしました。
以下を参考にさせていただきました。

* [Visual Studio CodeでFlowTypeのエラーを表示する](https://qiita.com/nyamogera/items/c3a9d1d022c933699ea2)

# Components

冒頭は`PropTypes`を使った例が示されていて、それがFlowだと「こうなりますよ」という体で書かれています。
生成したアプリでFlowの挙動を確認するため、以下のようにMyComponentを書いてみます。

```react:MyComponent
// @flow
import * as React from 'react';

type MyProps = {
  foo: number,
  bar?: string,
};

class MyComponent extends React.Component<MyProps> {
  render() {
    this.props.doesNotExist; // Error! You did not define a `doesNotExist` prop.

    return <div>{this.props.bar}</div>;
  }
}

export default MyComponent;
```

コメントにもあるように、`doesNotExist`というプロパティは`MyProps`というTypeには存在しないのでFlowに怒られます。

![スクリーンショット 2018-03-16 16.39.12.jpg](https://qiita-image-store.s3.amazonaws.com/0/165162/7023a5e8-8bd8-6f80-830a-dd1177e1fa53.jpeg)

次にAppを以下の様に書き換えます。

```react:App
// @flow
import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";
import MyComponent from "./MyComponent";

class App extends Component<{}> {
  render() {
    return (
      <div className="App">
        <MyComponent bar={42} />
      </div>
    );
  }
}

export default App;
```

`foo`は指定が必須であるのに指定されていない、と怒られます。
また、`bar`はstringを要求しているのに、numberが指定されている、と怒られます。

![スクリーンショット 2018-03-16 16.41.36.jpg](https://qiita-image-store.s3.amazonaws.com/0/165162/57135a72-8f82-ddd6-5ed7-2625d380267a.jpeg)

以下の様にすればエラーが消えます。

```react
<MyComponent foo={42} />
```

この他に、Stateについても型定義できたり、デフォルト値を指定できたり、`Functional Components`での書き方だったりの説明があります。

# Event Handling

イベントハンドラの型安全。

```react
// @flow
import * as React from "react";

class MyComponent extends React.Component<{}, { count: number }> {
  constructor() {
    super();
    this.state = {
      count: 0
    };
  }

  handleClick = (event: SyntheticEvent<HTMLButtonElement>) => {
    // To access your button instance use `event.currentTarget`.
    (event.currentTarget: HTMLButtonElement);

    this.setState(prevState => ({
      count: prevState.count + 1
    }));
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>Increment</button>
      </div>
    );
  }
}

export default MyComponent;
```

`event`の変数の型を`SyntheticEvent`とすることで、例えば、

```react
event.srcElement
```

のように非推奨となった属性を参照しようとすると、ちゃんと怒られるようになります。

# ref functions

```react
// @flow
import * as React from "react";

class MyComponent extends React.Component<{}> {
  // The `?` here is important because you may not always have the instance.
  myButton: ?HTMLButtonElement;

  render() {
    return <button ref={button => (this.myButton = button)}>Toggle</button>;
  }
}

export default MyComponent;
```

イベントハンドラの`event`引数に型を与えたように、DOM要素にも型安全を提供してくれるということです。
例えば、

```react
if(this.myButton) this.myButton.disabled = true;
```

というようにHTMLButtonElementの定義に適合するような使い方であれば怒られませんが、

```react
if(this.myButton) this.myButton.disabled = "aaa";
```

のように書くとFlowが指摘してくれます。
また、nullチェックもしないと、やはり指摘されます。
nullableとしているので、nullチェックなしのプロパティ参照に対して指摘してくれているわけです。

なお、ここでnullableにしている理由は以下の通りです。

> The ? in ?HTMLButtonElement is important. In the example above the first argument to ref will be HTMLButtonElement | null as React will call your ref callback with null when the component unmounts. 

# Higher-order Components

このセクションについては未解決の問題があるんですが、さらった内容を記載します。

ここでは、[recompose](https://github.com/acdlite/recompose)のmapPropsに相当するような関数をどう記述するかということが説明されています。
残念ながら完全な実装が記載されていないので、私は以下のような関数を書きました。

```react
function mapProps<PropsInput: {}, PropsOutput: {}>(
  mapperFn: PropsInput => PropsOutput
): (React.ComponentType<PropsOutput>) => React.ComponentType<PropsInput> {
  return WrappedComponent => {
    return class extends React.Component<PropsInput> {
      render() {
        return <WrappedComponent {...mapperFn(this.props)} />;
      }
    };
  };
}
```

ポイントはGeneric Typesで関数を記載することです。
上記の場合、`PropsInput`と`PropsOutput`の２つの型引数をとる関数になっています。

マッパー関数（`mapperFn`）のType Annotationは、`PropsInput => PropsOutput`です。

`mapProps`が返す関数は、`React.ComponentType<PropsOutput>`のコンポーネントを引数にとり、`React.Component<PropsInput>`のコンポーネント（Higher-order Components）を返します。

使う側は、これを満たすように`mapProps`を呼び出します。
まず、各コンポーネントが要求するPropsのTypeを定義しておきます。また、マッパー関数のTypeも定義しておきます。

```react
type PropsA = {
  foo: number
};
type PropsB = {
  bar: number
};
type MapperPropFn = PropsA => PropsB;
```

`mapProps`によりWrapされるコンポーネントは以下の通り。

```react
function MyComponent({ bar }: PropsB) {
  return <div>{bar}</div>;
}
```

マッパーは以下の様になります。
引数に渡される`props`には`foo`があり、返り値となるオブジェクトには`bar`を含めます。

```javascript
const propsMapper: MapperPropFn = props => {
  return { bar: props.foo + 1 };
};
```

`mapProps`の呼び出しと、HOCの利用は以下のようになります。

```react
const MyEnhancedComponent = mapProps(propsMapper)(MyComponent);
<MyEnhancedComponent foo={1} />;
```

`mapProps`の呼び出し部分ですが、Flowのエラーになってしまいます。

```
Missing type annotation for `PropsInput`.
```

これをどう解消すればよいのか、色々調べてもわかりませんでした。

もう１つの例として、プロパティを追加する関数が紹介されています。

```react
function injectProp<Props: {}>(
  WrappedComponent: React.ComponentType<Props>
): React.ComponentType<$Diff<Props, { foo?: number }>> {
  return function WrapperComponent(props: Props) {
    return <WrappedComponent {...props} foo={48} />;
  };
}
```

上の関数では、引数に渡されたコンポーネントに`foo`というプロパティを追加しています。
型引数は、前出の`mapProps`とは異なり、`Props`の1つだけを取ります。

引数に渡されるコンポーネントと、返すコンポーネントの差分を、

```
$Diff<Props, { foo?: number }>
```

というように表現しています。