Edited at

React 16.3で正式に導入されたContextを使ってcurrent_userを渡すHOCを作ったけど使わなかった

More than 1 year has passed since last update.


モチベーション:バケツリレーやめたい

ご近所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 までご連絡ください。