ReactのHOCについて


Reactの機能の一つ、HOC(Higher-Order Component)についての説明をする。
これの理解により、適切なパフォーマンスチューリングや、記述量の削減に大きく貢献できる。

# HOCとは？
以下のようなコードについて考える。

```A.jsx
class A extends React.Component{
        render(){
            return (
                <div>Hello, world</div>
            );
        }
}

export default A;
```

```B.jsx
import A from 'A.jsx';

class B extends React.Component{
        render(){
            return (
                <A />
            );
        }
}
```
ここで、`<B />`の出力結果は、当然`Hello, world`になる。
ここで、以下のような関数を用意する。

```jsx
const sayHi = WrappedCompoent => {
        return class extends WrappedCompoent{
            componentWillMount(){
                    console.log('Hi!');
            }
        }
}
```
これを、`A`に適用してみる。

```A.jsx
class A extends React.Component{
        render(){
            return (
                <div>Hello, world</div>
            );
        }
}

export default sayHi(A);
```

ここで、`<B />`を評価すると、コンソールに`Hi!`と表示されるようになる。

`sayHi`関数について考えてみる。
`sayHi`関数は、コンポーネントを受け取って、コンポーネントを返す関数となっている。
渡されたコンポーネントクラスを継承し、メソッドをオーバーライドするクラスを返すことによって、「機能を拡張」している。

このように、「コンポーネントを受け取り、コンポーネントを返す関数」を、HOC(Higher-Order Component)という。

*(「『コンポーネントを受け取り、コンポーネントを返す関数』により、拡張されたコンポーネント」の方をHOCと呼ぶ方が語彙的には適切だと思うが、公式ドキュメントの方では上のような定義になっている。ここでは便宜上、公式の方の定義を参照する。)*



なお、`sayHi`関数の機能は、次のようにして実現することもできる。

```jsx
const say = message => WrappedCompoent => {
        return class extends WrappedCompoent{
            componentWillMount(){
                    console.log(message);
            }
        }
}
```
```A.jsx
...
export default say('Hi!')(A)
```

このように、HOCは高階関数として提供されることが多い。
詳しくは、[公式ドキュメント](https://reactjs.org/docs/higher-order-components.html)

## コンポーネントコンポジションとの違い

コンポーネントの機能の拡張には、コンポーネントコンポジション(OOPでいうところのDecoratorパターン)が使われることが多い。

例えば、文字のリストを表示する`<TextList />`に、スクロールバーを実装する場合は次のようになる。

```jsx
<ScrollBar>
    <TextList />
<ScrollBar />
```

ここで、`<ScrollBar />`は`<TextList />`の実装に依存しない(カプセル化を壊さない)ので、他のコンポーネントにもすぐに容易に実装できる。

これに対し、HOCはクラス継承を行うことにより実現できる、つまりHOCの対象となるコンポーネントの実装に依存する(カプセル化を壊す)

拡張対象のコンポーネントの実装に依存するか、しないかでコンポジションとHOCは使い分ける必要がある。
拡張性を高めるため、基本的にはコンポジションの方を優先する。

参考: [Composition vs Inheritance](https://reactjs.org/docs/composition-vs-inheritance.html)

# Recomposeの導入
HOC周りのヘルパメソッドを提供する、[Recomposeライブラリ](https://github.com/acdlite/recompose)を導入。
これのわかりやすい日本語のドキュメントは[ここ](https://qiita.com/locol23/items/ad9c7553176c239f0b15)

例えば、`compose`関数なら、
今まで以下のようになっていたものを、

```jsx
HOC1(HOC2(HOC3(Component)))
```
次のようにできる。

```jsx
const Enhance = compose(
    HOC1,
    HOC2,
    HOC3
);

Enhance(Component)
```

# HOCの使用例

## React-Redux
ReactとReduxを組み合わせて使う場合、
Reactの各コンポーネントの`props`と、Reduxの`state` **(ReactのStateと混同しないように！)** と`action creator` を繋げる必要がある。

ここで、
- Reduxの`state`を`props`に反映する  `mapStateToProps` 関数
- Reduxの`action creator`を`props`に反映する `mapDispatchToProps`関数
- それらを繋げる`connect`関数

を用意し、それらを組み合わせてHOCとして適用する必要がある。

例えば、`<A />`に対し、Reduxの`state`、`action creator`を繋げる場合は、次のようになる。

```jsx
connect(mapStateToProps, mapDispatchToProps)(A)
```

### mapStateToProps
`mapStateToProps`は**Reduxの**`state`を受け取り、`props`に追加したい`state`を返す関数である。
例えば、`state`内の`gender`を`props`に反映する場合は、`mapStateToProps`は次のようになる

```jsx
const mapStateToProps = state => ({
    gender: state.gender
});
```

これにより、コンポーネント内では`this.props.gender`としてアクセスできるようになる。

ところで、プロジェクト内の既存のコードだと、次のようになっている部分が多い

```jsx
const mapStateToProps = state => {
    return state;
};
```
しかし、これだとComponent側のpropsにstateの情報が全て反映されてしまい、関係のないstateが変わるたびにそのコンポーネントの描画処理が行われるため、パフォーマンスが悪化してしまう。
なので、上のように必要なものだけpropsに反映するようにする必要がある。

なお、mapStateToPropsを二重で適用したりする際などに、現在のpropsを参照する場合は、第二引数`ownProps`を宣言する。

```jsx
const mapStateToProps = (state, ownProps) => ({
    ...ownProps, 
    gender: state.gender
});
```

### mapDispatchToProps

`mapDispatchToProps`は、Reduxの`dispacther`を受け取り、`props`に追加したい`action creator`を返す関数である。
setGenderをpropsに渡す場合は、mapDispatchToPropsは次のようになる

```jsx
import  { setGender } from 'actions.jsx';

const mapDispatchToProps = dispatch => {
        return {
            actions: {
                    setGender: gender => dispatch(setGender(gender))
            }
        };
};
```

これは、bindActionCreatorsというヘルパ関数を使うことにより、次のように書ける。

```jsx
const mapDispatchToProps = dispatch => {
        return {
            actions: bindActionCreators({ setGender }, dispatch)
        };
};
```

これにより、コンポーネント内では`this.props.actions.setGender`としてアクセスできる。

ところで、プロジェクト内の既存のコードだと、次のようになっている部分が多い

```jsx
const mapDispatchToProps = dispatch => {
    return {
        actions: bindActionCreators(Actions, dispatch)
    };
};
```

これも同じく、不要なaction creatorをpropsに渡してしまっているので、パフォーマンスの低下に繋がる。


なお、mapStateToPropsを二重で適用したりする際などに、現在のpropsを参照する場合は、第二引数`ownProps`を宣言する。

```jsx
const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        actions: {
            ...ownProps.actions,
            ...bindActionCreators({ setGender }, dispatch);
        };
    };
}
```

参考: [5 way to Connect Redux Actions](https://blog.benestudio.co/5-ways-to-connect-redux-actions-3f56af4009c8)