会社製品の開発環境にFlowを導入したので、コーディング規約おさらいついでに書きます。
コンポーネント
コンポーネントクラスは React.Component<Props, State> を継承するようにします。 State は省略可能ですが、 Props は省略できません。
type Props = {
someValue: number,
};
class Component extends React.Component<Props> {}
children など、Reactが描画可能なものは Node で定義しましょう。
import { type Node as ReactNode } from 'react';
type Props = {
children: ReactNode,
};
render など、Reactが定義しているメソッドの型定義を書く必要はありません。なぜなら親クラス側で定義しているからです。
class Component extends React.Component<Props> {
render() {
// ...
}
}
propTypes はもう不要です。サーバーからのレスポンスなど一部ランタイムでしか判明しないものがありますが、そこさえ型定義してキャストしてしまえば型に守られた世界が待ってます。
type ResponseBody = {
isSuccess: boolean,
results: {
employeeName: string,
}
};
ESLintを使っているのであれば、 propTypes の警告はオフにしてしまいましょう。
{
"react/prop-types": "off"
}
Reduxを使った開発では、よくコンテナから親コンポーネントを介して子コンポーネントにPropsを伝搬します。
<ChildComponent
{...this.props}
childOwnProp=""
/>
のように、面倒がって親のPropsをまるごと伝搬しがちですが、Flow導入にあたってこれをやめます。(汚いし...)
子コンポーネントからPropsの型を受け取り、それを親へと伝搬することで、伝搬漏れに気付けるようにしましょう。
export type Props = {
// childOwnProp: PropTypes.string.isRequired,
childOwnProp: string,
// childOwnOptionalProp: PropTypes.string,
childOwnOptionalProp: ?string,
}
const ChildComponent = (props: Props) => <div>...</div>;
export default ChildComponent;
親のコンポーネントの型定義はこう書くことができます。
import ChildComponent, { type Props as ChildComponentProps } from './ChildComponent';
export type Props = ChildComponentProps & {
parentProp: string,
};
const Component = (props: Props) => <div>
<ChildComponent childOwnProp={props.childOwnProp} />
...
</div>;
子コンポーネントにPropを追加したいときなどは、小コンポーネントのPropsの型定義を先に編集します。
あとは Flowのエラーが伝搬漏れを警告してくれますので、それに従えば簡単にリファクタできます。 超便利!
イベント
SyntheticEvent<*> を使いましょう。 * はイベントを発火するエレメントの型にします。( HTMLButtonElement など )
ただし、これを指定してもイベントを監視するエレメントを縛っているだけなので、 SyntheticEvent<HTMLElement> でも十分です。
Reduxで開発しているとよくある、イベントが発火されたらコンポーネントのPropを渡しつつPropから伝搬されたアクションを発火、みたいなやつが一番面倒くさいです。
アクションをラップしてコンポーネントのPropを渡す関数をレンダリングの外部で定義しなければならず、外部で定義するとコンポーネントのインスタンスにアクセスできなくなります。
冗長さはどうにもなっていませんが、こうします。
import React, { type SyntheticMouseEvent } from 'react';
// ...
class Component extends React.Component<Props> {
onClickButton: (SyntheticMouseEvent<HTMLButtonElement>) => void;
constructor() {
super();
// ここでバインドしてしまいます。
// 2つ目の this.onClickButton は this.__proto__.onClickButton の意味合いです。
this.onClickButton = this.onClickButton.bind(this);
}
onClickButton(event: SyntheticMouseEvent<HTMLButtonElement>) {
// if (event.currentTarget.disabled) { ... }
this.props.onClickButton(this.props.inputValue);
}
render() {
return (<button type="button" onClick={this.onClickButton}>...</button>);
}
}