モチベーション:バケツリレーやめたい
ご近所SNSマチマチのフロントエンドはReact + Flowtypeで実装されています。ReduxやMobXなどの状態管理のアーキテクチャを採用していないので、current_user
などの様々なコンポーネントが必要とするプロパティは親コンポーネントから子コンポーネント、子から孫、とバケツリレー式に渡しています。
バケツリレー、current_user
について型安全になるので非常にありがたいのですが、当然面倒ではあります。React 16.3でContextが正式に導入されたので、これを使えばcurrent_user
のバケツリレーをやめられるかも?と期待に胸を膨らませつつcurrent_user
を渡すHigher Order Componentを作ってみました。
Context使ってcurrent_user
を渡すHOCを書いた
HOCはこんな感じ
// @flow
import * as React from "react";
type UserObject = {
id: number,
name: string
}
// `React.createContext`でContextのインスタンスを作る。
// デフォルト値はNon-nullableであってほしいんだけど、Contextのインスタンスを作る時点でcurrent_userはわからないので仕方ない
const defaultValue: ?UserObject = null;
const UserContext = React.createContext(defaultValue);
// `current_user`を持つオブジェクト型を`current_user`なしにする型
type WithoutCurrentUser<T> = $Diff<
T & { current_user: UserObject },
{ current_user: UserObject }
>;
// `current_user`をとるコンポーネントをとり、Contextで包んで`current_user`不要のコンポーネントを返すHOC
function withCurrentUser<Props>(
Component: React.ComponentType<Props>
): React.ComponentType<WithoutCurrentUser<Props>> {
return props => (
<UserContext.Consumer>
{user => {
if (user) {
return <Component {...props} current_user={user} />;
} else {
throw "userが無かった…";
}
}}
</UserContext.Consumer>
);
}
export { withCurrentUser };
export default UserContext;
withCurrentUser
をこのようにコンポーネントに対して適用して、
import { withCurrentUser } from "./UserContext";
class ComponentNeedsCurrentUser extends React.Component<Props, State> {
// ...
}
export withCurrentUser(ComponentNeedsCurrentUser);
このようにUserContext.Provider
で包むと、コンポーネントにcurrent_user
が渡ります。
<UserContext.Provider value={current_user}>
<ComponentNeedsCurrentUser
foo={'foo'}
/>
</UserContext.Provider>
お気づきかと思いますが、UserContext.Consumer
の中で例外を吐いています。これはContextは定義する時点で初期値が必要なのでcurrent_user
がNullableになってしまうことへの苦しい対応です。
Nullableになってしまった
バケツリレーをしている間はcurrent_user
の渡し忘れは型検査の段階で気づけたのですが、Contextを使うと実行時までわかりません。UserContext.Provider
で包み忘れたwithCurrentUser
なContextが実行時に例外を吐く、という事態が発生してしまいます。型で検知できたバグが実行時エラーになってしまうのは大きなデメリットです。これは許容できないよねーということで、採用はやめました。
おわりに
HOCの型付けが大変でした。
参考リンクは下記の通りです。公式ドキュメントにHOCの作り方も書いてあって、この記事のHOCもそれを踏襲しています。
ちなみにマチマチではエンジニアを募集しています。これからReact Nativeでアプリを作る予定です。パートタイムも大歓迎なので、興味がある方は是非 @fujimura までご連絡ください。