JavaScript
flow
reactjs
flowtype

Reactにflowtypeを導入したまとめ

Reactで作っているwebアプリにflowを導入したので雑にまとめる。
書き方が間違っている部分は指摘していただけると嬉しい。

[2017.12.13] Flowのバージョンアップによって書き方変わった部分があるので修正しました。URLも変更しています。

flowについて

  • Facebook製のJavaScript静的型チェッカー
  • 言語ではなく外部ライブラリ
  • nullやundefinedにとても厳しい

参考サイト

公式
https://flow.org/en/docs/
チートシート
https://www.saltycrane.com/blog/2016/06/flow-type-cheat-sheet/

設定など

flowインストール

基本はこのページ参照。
https://flow.org/en/docs/install/

.flowconfig

読み込むファイルの拡張子はここで定義する。
optionsの先頭では、 import で絶対パスが使用できるように設定している。

.flowconfig
[ignore]
.*/node_modules/.*

[include]

[libs]
flow-typed

[options]
module.name_mapper='^\(.*\)$' -> '<PROJECT_ROOT>/src/\1'
module.file_ext=.js
module.file_ext=.jsx
module.file_ext=.css
module.file_ext=.scss
module.file_ext=.json

flowチェック

package.jsonにflowのscriptを追加して、ローカルのflowを使えるようにする。

package.json
  "scripts": {
    "flow": "flow",
  },
yarn flow

flow-typedインストール

外部ライブラリの型定義をしてくれるライブラリ。
https://github.com/flowtype/flow-typed

yarn global add flow-typed

skipオプションを付けないと、大量のstub(ゴミファイル)が生成されてしまう。
参考:https://github.com/flowtype/flow-typed/wiki/CLI-Commands-&-Flags#flags-1

flow-typed install --skip

ライブラリにflow-typedが対応しているか調べるには下記。

flow-typed search xxx

基本的な書き方

全体

flowで型チェックをしたいファイルの上部に下記のようなコメントアウトを入れる。

// @flow

Functional Component

Functional Componentの場合、下記のような書き方で propsdefaultProps(必要なら) を定義する。
https://flow.org/en/docs/react/components/#toc-stateless-functional-components
この書き方はES2015の分割代入を使っている。

// @flow

const Hoge = ({
  label = '',
  to = '',
  onClick = () => {},
  item = {},
}: {
  label: string,
  to: string,
  onClick: (item: {}, e: Event) => void,
  item: {},
}) => {
...
};

上記のような分割代入も可能ですが、下記のようにtype Props = {}と定義することも可能になりました。

// @flow
type Props = {
  label: string,
  to: string,
  onClick: (item: {}, e: Event) => void,
  item: {},
}

const Hoge = (props: Props) => {
...
};

Hoge.defaultProps = {
  label: 'hogehoge',
  to: 'hoge.com',
  onClick: () => {},
  item: {},
}

Component

通常のComponentの場合、下記のような書き方で propsdefaultProps を定義する。
defaultPropsの型定義は書かなくて良くなりました。
https://flow.org/en/docs/react/components/

// @flow


type Props = {
  children: React.Element<any>,
};

class Hoge extends Component<Props> {
  static defaultProps = {
    children: null,
  };

  constructor(props: Props) {
    super(props);
    (this: any).handleBar = this.handleBar.bind(this);
  }
...
}

stateにも型定義したいときは、下記のように書く。
https://flow.org/en/docs/react/components/#toc-adding-state

// @flow

type Props = {
  children: React.Element<any>,
};

type State = {
  foo: boolean,
}

class Hoge extends Component<DefaultProps, Props, State> {
  static defaultProps = {
    children: null,
  };

  state = {
    foo: true,
  }
...
}

この場合、constructor内にthis.stateは書かない。

DefaultPropsの扱いについて

DefaultPropsを定義しない場合、そのpropsは省略できない(isRequiredと同じような扱いになる)。
https://flow.org/en/docs/react/components/#toc-using-default-props

主な要素の型定義の書き方

要素タイプ flow書き方
文字 string
数字 number
配列(型あり) Array<number>
配列(型なし) Array<any>
配列(オブジェクト) Array<{ ... }>
オブジェクト {}
Object(中身の型を無視したいとき)
関数 ( e: Event ) => void
引数の型定義も必須
ReactElement React.Element<any>
イベント SyntheticEvent<T>

その他型定義は下記にまとまっている。
https://flow.org/en/docs/types/
https://flow.org/en/docs/react/types/

イベントの型定義はこちら
https://flow.org/en/docs/react/events/

その他

constructorでbindしているthisの書き方

constructor(props: Props) {
  super(props);
  (this: any).handleBar = this.handleBar.bind(this);
}

this.hogeの定義

props, state, メソッド以外のthis.hogeはコンストラクタ、ライフサイクルメソッドの下で型定義する。

constructor(props: Props) {
...
}

hoge: string

this.hoge = 'hogehoge';

Component下部にあるdispatch

const mapDispatchToProps = (dispatch: Dispatch<*>) => (
  {
    actions: bindActionCreators(Actions, dispatch),
  }
);

event.currentTarget

flowはcurrentTargetを直接参照できないので、型定義を挟む必要がある。
SyntheticEvent<T>でEventのevent.currentTargetの型定義できるようになりました!!!
HTMLButtonElement の部分はイベントを取得する要素に応じて変更する。
参考:https://flow.org/en/docs/react/events/

handleChange(e: SyntheticEvent<HTMLButtonElement>) {
  const data = { [e.currentTarget.name]: e.currentTarget.value };
  this.props.actions.editEnterpriseInfo(data);
}

Maybe Type

通常、flowではnullとundefinedは許容されない。
必要な部分はMaybeTypesを使用してnullとundefinedを許容できるようにする。
また、オブジェクトのプロパティを省略したいときはプロパティ名のほうに?をつける。(undefinedは可だけどnullは不可)
https://flow.org/en/docs/types/maybe/
https://flow.org/en/docs/types/primitives/#toc-optional-object-properties

// null, undefinedを許容
hoge: ?string

// 省略可能なオブジェクトのプロパティ
hoge: {
  foo?: string,
}

Globalな変数

declare var hoge: any;

まとめ

細かい部分でわからないことが多くつらかった。