React の Higher-order Components の利用方法

# 前書き
この資料は2016/12/15 に行われた[【ランサーズ×Mozilla×freee】React実践！勉強会](https://eventdots.jp/event/607346)で発表した際の資料を改修したものです。
(qitta に投稿するのが初めての場合、2MBまでしか画像をアップできないので、編集してます)
間違いなどがあれば、コメントや編集リクエストなどをいただければありがたいです。

---
# 目次
- 対象読者
- HOC とは何か？
- HOC の基礎
- 実際に利用されているライブラリについて
- HOC の具体的な実装例

- - -
# 対象読者
- Reactを利用してアプリケーションを作成したことがある人
- ReduxやFluxなどのフレームワークを利用したことがある人

- - -
# HOC とは何か？
A higher-order component is just a function that takes an existing component and returns another component that wraps it.
(引用:[Mixins Are Dead. Long Live Composition](https://goo.gl/A613XJ))

- コンポーネントを引数にして、カスタマイズしたコンポーネントを返す**関数**のこと
- 既存のコンポーネントに新しい機能を追加することが可能

---
# HOCの基礎
[sebmarkbage/Enhance.js](https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775)の例

```js:Enhance.js
import { Component } from "React";
// 関数の引数にコンポーネントを受け取る
// function Enhance(ComposedComponent)　{ return xxx } と同じ
export var Enhance = ComposedComponent => class extends Component {
  constructor() {
    this.state = { data: null };
  }
  componentDidMount() {
    this.setState({ data: 'Hello' });
  }
  render() {
    // 引数で受け取った ComposedComponent に props を渡して返す
    return <ComposedComponent {...this.props} data={this.state.data} />;
  }
};
```
- 引数に受け取った ComposedComponent に props などをマージして return

---
# 要点を抜き出すと
![image](https://qiita-image-store.s3.amazonaws.com/0/26095/71f7237a-fb0d-4596-e6d0-27a0867578e9.png)

---
# 利用する側の実装
```js:HigherOrderComponent.js
import { Enhance } from "./Enhance";

class MyComponent {
  render() {
    // MyCompoent で Enhancer の data を参照できるようになった
    if (!this.data) return <div>Waiting...</div>;
    return <div>{this.data}</div>;
  }
}

export default Enhance(MyComponent); // Enhanced component
```
- Enhance(MyComponent)　で　MyComponent　に HOC を適応
- 関数でラップすることで、 data　というプロパティが新たに追加された

---
# 要点を抜き出すと
![image](https://qiita-image-store.s3.amazonaws.com/0/26095/85d0e684-d57e-459b-0c4b-8a899e71e0bd.png)

---
# HOCの基礎：イメージ
![image](https://qiita-image-store.s3.amazonaws.com/0/26095/69afb7bc-d5f4-c777-6a6c-fce458994ec1.png)
- コンポーネントに関数を適応し、機能が合成されたコンポーネントを返す
- propsを新しく加えたり、ライフサイクルメソッドを追加することも可能

---
# 最近よく見かける HOC を利用しているライブラリとその実装
- react-redux
 - [connect()](https://github.com/reactjs/react-redux/blob/68a4f2b78671329e10bfca87f8f1c82cc8690459/src/components/connect.js)
- material-ui
 - [muiThemeable()](https://github.com/fis-components/material-ui/blob/master/src/muiThemeable.jsx)
- redux-form
 - [reduxForm()](https://github.com/erikras/redux-form/blob/master/src/reduxForm.js)
- react-router
 - [withRouter()](https://github.com/ReactTraining/react-router/blob/6eeb7ad358f987520f5b519e48bdd31f725cbade/modules/withRouter.js)
- radium
 - [enhanceWithRadium()](https://github.com/FormidableLabs/radium/blob/9c61f148d51a611af24705fee7f4d9734d336526/src/enhancer.js)

---
# material-uiでの例
- Google Material Design を react の component で利用出来るライブラリ
![image](https://qiita-image-store.s3.amazonaws.com/0/26095/29812adc-f75f-c042-2f98-b2ad59c4b5b8.png)

---
- [material-ui / themes](http://www.material-ui.com/#/customization/themes)
- テーマの色設定などは、muiTheme　という形で1つのファイルで管理している
- textColor などの定義を、自作のコンポーネントでも使いたい場合・・・
- muiThemeable　という HOC を呼び出す

```js:muiTheme.js
import { cyan500, pinkA200, darkBlack } from '../colors';
import spacing from '../spacing';

export default {
  spacing: spacing,
  fontFamily: 'Roboto, sans-serif',
  palette: {
    primary1Color: cyan500,
    accent1Color: pinkA200,
    textColor: darkBlack,
    // 省略...
  },
};
```
---
# material-ui / muiThemeable
- マテリアルUIの textColor などの定義を、自作のコンポーネント内で呼び出す

```js:muiThemeableを使ってコンポーネント内でThemeを呼び出す
import React from 'react';
import muiThemeable from 'material-ui/styles/muiThemeable';

const MyComponent = (props) => (
  <span style={{color: props.muiTheme.palette.textColor}}>
    Hello World!
  </span>
);

export default muiThemeable()(MyComponent);
```

- muiThemeable()(MyComponent)の部分がHOC
- props に muiTheme.palette.textColor が追加されて、自作のコンポーネント内でも扱えるようになった

- - -
- [muiThemeable.js](https://github.com/callemall/material-ui/blob/ccf712c5733508784cd709c18c29059542d6aad1/src/styles/muiThemeable.js) の中身を簡略化したもの

```js:muiThemeable.js
import React, {PropTypes} from 'react';
import getMuiTheme from './getMuiTheme';

export default function muiThemeable() {
  // 省略...
  return (Component) => {
    const MuiComponent = (props, context) => {
      const {muiTheme = getMuiTheme()} = context;
      
      return <Component muiTheme={muiTheme} {...props} />;
    };
    // 省略...
    return MuiComponent;
  };
}
```
Component を引数にとって、 muiTheme という props を持った MuiComponent という新しいコンポーネントを return している

---
# 形としてみると
![image](https://qiita-image-store.s3.amazonaws.com/0/26095/665a6422-e1ed-cab2-f80a-93f69f4a5f49.png)

- - -
# ReduxForm での例
- [ReduxForm](http://redux-form.com/6.3.1/)
- 入力フォームなどを redux のフローで扱ってくれるライブラリ
![image](https://qiita-image-store.s3.amazonaws.com/0/26095/1bed2058-1ccd-94d5-0601-d038415e3a0d.png)

- - -
# ReduxForm でHOCを利用する
簡単なプロフィールフォームの例

```js
import React, { Component } from 'react';
import { reduxForm, Field } from 'redux-form';

class ProfileForm extends Component {
  render() {
    const { handleSubmit } = this.props;
    return (
      <form onSubmit={handleSubmit}>
        <Field name="firstName" component="input" type="text"/>
      </form>
    );
  }
}

ProfileForm = reduxForm({
  form: 'profile'
})(ProfileForm);

export default ProfileForm;
```
```
reduxForm({})(ラップされるコンポーネント)
```
という構成。

---
# ReduxForm でHOCを利用する

![image](https://qiita-image-store.s3.amazonaws.com/0/26095/e522b981-a8a1-2701-41f0-51a368f66411.png)

---
- [reduxForm.js](https://github.com/erikras/redux-form/blob/master/src/reduxForm.js) の中身を簡略化したもの

```js
import { Component, createElement } from 'react'
// 省略...
const createReduxForm = structure =>
    // 省略...
    return WrappedComponent => {
        // 省略...
        return class ReduxForm extends Component {
            render() {
                return createElement(WrappedComponent, {})
            }
        }
    }
}
export default createReduxForm
```

---
# 要点を抜き出すと
![image](https://qiita-image-store.s3.amazonaws.com/0/26095/224a7f15-276f-efb6-2796-fea37072438a.png)

---
# ここまでのまとめ1
React 関連のライブラリを使っていて

```js
Enhancer(WrappedComponent)
```

というような、コンポーネントを引数に取る構成の記述を見かけた場合は HOC の実装を使っているのではないかと考えると理解しやすい。

---
# ここまでのまとめ２
1. 関数がコンポーネントを引数にとって
2. 何らかの処理を加えて
    - 新しい props の注入
    - 共通のライフサイクル処理
3. 引数にとったコンポーネントに合成して返す

案外、 HOC の実装はパッと見で複雑でも、元のコンポーネントに新しく props を注入したりしているだけかもしれません。



---
# HOC の 2 つのパターン
[React Higher Order Components in depth](http://postd.cc/react-higher-order-components-in-depth/)の記事によると、HOC には２つのパターンがあります

1. 先ほどまでの props を新しく追加する例は **Props Proxy**
2. PropsProxy 以外には **Inheritance Inversion**

実際の実装を見ながら順に説明していきます。

---
# HOC の具体的な実装例1
- ボタンのバリエーションを HOC で表現する
- 以下のようなプライマリカラーの効いたボタンを作りたいとき
![image](https://qiita-image-store.s3.amazonaws.com/0/26095/25ce35ce-ca05-4b05-60b3-593213b3dcee.png)

--- 
# HOC の具体的な実装例1
- createPrimaryButton という関数で、HOCを作成
- material-ui の RaisedButton コンポーネントを拡張

```js:プライマリボタンを作成する例
import React, { Component } from 'react';
import RaisedButton from 'material-ui/RaisedButton';

function createPrimaryButton(WrappedComponent) {

    return class designedButtonComponent extends Component {

        render() {
            return (<WrappedComponent {...this.props} primary={true} />);
        }
    }
}
export const PrimaryButton = createPrimaryButton(RaisedButton);
```

--- 
# HOC の具体的な実装例1： HOC を作成
![image](https://qiita-image-store.s3.amazonaws.com/0/26095/ddc63e2b-579b-7bf9-5b1d-9f986b9ef0ca.png)

- コンポーネントに与えられた props をそのまま受け渡しつつ、新しく primary={true} という props　を追加する

----
# HOC の具体的な実装例1： 呼び出す側

- PrimaryButton という名前で呼び出すことで、利用者側も理解しやすいコードになる
- material-ui の　label などのプロパティも同時に利用出来る

```js:ボタンの利用例
import { PrimaryButton } from './buttons';
export const renderSomePageWithButton = buttonAction =>
<div>
    <PrimaryButton
        label={'プライマリボタン'} //ボタンに表示するテキスト
        onTouchTap={buttonAction}
    />
</div>
```

---
# HOC の具体的な実装例1
- createButton に引数をつけて、バリエーションを増やす
- 先ほどの HOC に color, size, shape などの引数を追加
 
```js:buttons.js
import React, { Component }from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import { buttonColor, buttonSize, buttonShape } from './button_styles';

function createButton(WrappedComponent, color, size, shape) {
    return class designedButtonComponent extends Component {
        render() {
            return (<WrappedComponent
                {...this.props}
                {...buttonColor[color]}
                {...buttonSize[size]}
                {...buttonShape[shape]}
            />);
        }
    }
}
export const PrimaryButtonFullWidth = createButton(RaisedButton, 'primary', 'fullWidth', 'original');
export const SecondaryButtonRounded = createButton(RaisedButton, 'secondary', 'original', 'round');
export const AccentButtonHalfWidth = createButton(RaisedButton, 'accent', 'halfWidth', 'original');
```

---
# HOC の具体的な実装例1： 呼び出す側
```js:ボタンのバリエーションの利用例

import {
    PrimaryButtonFullWidth,
    AccentButtonHalfWidth,
    NSecondaryButtonRounded } from './buttons';
export const renderSomePageWithButton = buttonAction =>
<div>
    <NPrimaryButtonFullWidth label={'プライマリボタン幅MAX'} />
    <NSecondaryButtonRounded label={'角丸セカンダリボタン'} />
    <NAccentButtonHalfWidth label={'アクセントボタン幅半分'} />
</div>
```

- - -
# HOCを使ったコンポーネントのパターン実装のメリット

- HOC を用いれば、コンポーネントに新しく props を追加できる
- `PrimaryButtonFullWidth`など、限定的な名前を持たせることで、コードがリーダブルになる
- FlowTypeなどで型を持たせると、さらにリーダブルになる

```js
// @flow
type ColorType =
    'primary'
    | 'secondary'
    | 'accent'
;
function createButton(WrappedComponent: any, color:ColorType) {...}
```

- - -
#　HOC の具体的な実装例2
- 閲覧するのにログインが必要なコンポーネントをHOCで制御
- Redux で、 session.userId でログインユーザを管理している前提
- loggedInRequired()というHOCを作ってみる

---
#　HOC の具体的な実装例2: 利用する側

- ログインが必要な箇所は、HOCでラップしてあげる
- 「ログインしていません」のようなダイアログを出したい

```js:ラップされる側のコンポーネントの利用イメージ
import loggedInRequired from '../loggedInRequired.js'
// 省略...
class ProjectContainer extends Component {
// 省略...
}
export default loggedInRequired(
    connect(mapStateToProps, mapDispatchToProps)
    (ProjectContainer)
);
```

---
#　HOC の具体的な実装例2: HOCを作成する側

```js
export default function loggedInRequired(WrappedComponent) {
    class loggedInRequiredComponent extends WrappedComponent {

        componentWillMount() {
            // react-redux のステートには this.store.getState() でアクセスできます
            if (!this.store.getState().session.userId) {
                // ログインしていないユーザが見た時のアクションを書く
            }
        }
        render() {
            if (!this.store.getState().session.userId) {
                // ログインしていないユーザが閲覧したらレンダリングしない
                return null;
            }
            return super.render();
        }
    }
    return loggedInRequiredComponent;
}
```
- さっきまでと異なるのは、 WrappedComponent を return するのではなく、 extends している点
- Inheritance Inversion というパターン
- ~~react-redux のステートには this.state.storeState でアクセスできます~~ -> this.store.getState() に修正しました。（react-redux v5系以降）

---
# HOC の具体的な実装例2で行っていること
ラップしたコンポーネント自身を extends している。(Inheritance Inversion)

- HOCのライフサイクルメソッドを、ラップしたコンポーネントに適応
- HOCから、ラップしたコンポーネントの state や props に thisでアクセス
- 条件によって、レンダリングしないように、 render をハイジャックできる

ログインが必要なコンポーネントは、このHOCを利用すれば良いため、他のコンポーネント内にif文などを書かなくて良くなり、コンポーネント自身の責務に集中出来る。


---
# おまけ：複数のHOCを合成して使うテクニック
コンポーネントを複数のHOCでラップしたい場合
- ~~loggedInRequired(connect(mapStateToProps, mapDispatchToProps)(ProfileForm))~~ のように書くのか？
- redux の compose や lodash.js の _.compose を利用するとスッキリかけます

```js
import { compose } from 'redux';
import { reduxForm } from 'redux-form';

class ProfileForm extends Component {
...
}
ProfileForm = compose(
    loggedInRequired,
    connect(mapStateToProps, mapDispatchToProps),
    reduxForm({ form: 'ProfileForm' })
)(ProfileFormContainer)

export default ProfileForm
```
- 関数の合成については[こちらの記事](http://qiita.com/mori-dev@github/items/27a35a8573b52595e612#%E9%96%A2%E6%95%B0%E5%90%88%E6%88%90%E3%81%AE%E4%BE%8B5)が分かりやすいと思います。

- - -
# まとめ
- HOC　はコンポーネントを引数にして、カスタマイズしたコンポーネントを返す関数のこと
- material-ui や redux-form など、様々なライブラリで利用されている
- 組み合わせることで、既存のコンポーネントに新しい機能を追加することが可能
- アプリケーション内で共通に利用する機能や、パーツをまとめるのに便利
- 慣れればコードを読みやすくかける



- - -
#### 参考資料
- [Mixins Are Dead. Long Live Composition](https://goo.gl/A613XJ)
- [sebmarkbage/Enhance.js](https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775)
- [muiThemeable.js](https://github.com/callemall/material-ui/blob/ccf712c5733508784cd709c18c29059542d6aad1/src/styles/muiThemeable.js)
- [Reactの Higher-order Components (HoCs) という概念に関するメモ](http://qiita.com/inuscript/items/6eb6076a7e5237a2911a)
- [React Higher Order Components in depth](http://postd.cc/react-higher-order-components-in-depth/)
  - [ReactのHigher Order Components詳解 : 実装の2つのパターンと、親Componentとの比較](http://postd.cc/react-higher-order-components-in-depth/)(日本語訳)
- [React.js/redux のコンポーネントの機能拡張、ストアの機能拡張でよくみかけるアプローチの紹介](http://qiita.com/mori-dev@github/items/27a35a8573b52595e612)