JavaScript
TypeScript
React
frontend

React SFCでもLifecycleフックを使う

React+Redux のスタックでアプリケーションを開発する際に、Presentational/Container でコンポーネントの責務分割をする設計手法1をとっていると、できるだけビューとなるコンポーネントは Stateless Functional Component (SFC) で書きたいという気持ちがありますよね。

とはいえ、必ずしもすべてのビューコンポーネントを関数にできるかというとちょっと難しいです。例えば、componentWillMountのタイミングでAPIを叩いてデータを予めフェッチしておくなどの処理は、どうしてもその処理がビューのライフサイクルに依存するため、ここだけ仕方なくクラス構文を使ったコンポーネントにせざるを得ない...などのモヤモヤ感が生まれます。

SFCにライフサイクルフックを提供するHOCを作る

このような場合には、特定のコンポーネントにライフサイクル・フックへのコールバックを登録できるような HOC2 ファクトリを作ってあげると、常にビューを SFC に維持することができます。

以下はTypeScriptで書いていますが、ジェネリクスがあるくらいでほぼほぼJSと同じです。

lifecycleHookRegister.tsx
import * as React from 'react';

export function withLifecycleHook<P>(
  Component: React.SFC<P>,
  hooks: {
    didMount?: (props: P) => void,
    willMount?: (props: P) => void
  }
) {
  return class StatefulComponent extends React.Component<P, {}> {
    componentDidMount() {
      if (hooks.didMount) {
        hooks.didMount(this.props);
      }
    }

    componentWillMount() {
      if (hooks.willMount) {
        hooks.willMount(this.props);
      }
    }

    render() {
      return (
        <div>
          {Component(this.props)}
        </div>
      );
    }
  };
}

この HOC ファクトリでやっていることは、引数としてcomponentDidMountcomponentDidUpdateのタイミングで実行する関数をコールバックで受け取り、動的に外部から設定できるようにしているだけの簡単なものです。

このような HOC ファクトリを作っておけば、ライフサイクルが必要な SFC なビュ―関数を必要に応じて食わせることで、ライフサイクルに応じた処理のあるコンポーネントを作ることができます。

const component: React.SFC<Props> = (props) => {
  return (
    <div>
      This is a stateless functional component!
    </div>
  );
};

export const YourComponent =
  withLifecycleHook<Props>
    (component, {
      willMount: (props: Props) => {
        props.callYourOwnAPI();
      }
    });

componentが関数型コンポーネントとしてただ JSX を返すだけの Presentational な責務だけを持ち、ライフサイクルに関連した処理がうまく切り出せていることが分かりますね。