【React】ダブルクリックによる処理の重複を防ぐ機能を提供するHOC

Reactの`onClick`イベントが設置されているコンポーネントをダブルクリックすると、シングルクリックイベントが立て続けに2回実行されます。
ちょっとそれでは困る場合の為に、連続したクリックを1回にまとめるコンポーネントを作成しました。
また、複数個所で気軽に実装できるよう`Higher-Order Component (HOC)`の形式にしてみました。

# Higher-Order Components とは
APIとして提供されている機能ではなく、コンポーネントに関するロジックを再利用するためのテクニックの一つです。
[クラスの継承](https://ja.wikipedia.org/wiki/%E7%B6%99%E6%89%BF_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0))よりも[ミックスイン](https://ja.wikipedia.org/wiki/Mixin)に近い印象ですが、ロジックがより隠蔽されています。
本家サイトによるとミックスインは混乱の元になるそうで、`Composition`もしくは`HOC`の利用が推奨されるそうです。
クラスの継承はFaceBook内では導入事例が無いとのこと。
JavaScriptは[オーバーライド](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%82%A4%E3%83%89)を防ぐ仕組みが無いので、事故を防ぐには運用ルールやコメントなどによるカバーが必要になってくるからでしょうか。
また、継承ではFunctionコンポーネントも使えませんね。

[Higher-Order Components | React](https://reactjs.org/docs/higher-order-components.html)
[Composition vs Inheritance | React](https://reactjs.org/docs/composition-vs-inheritance.html)
[Mixins Considered Harmful | React](https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html)

# HOCの基本
JavaScriptには、関数を引数や戻り値として扱う「[高階関数 (Higher-Order Function)](https://ja.wikipedia.org/wiki/%E9%AB%98%E9%9A%8E%E9%96%A2%E6%95%B0)」というものがあります。
HOCはコンポーネントを引数や戻り値として扱うので、「高階コンポーネント (Higher-Order Component)」と呼ばれます。
React-Reduxに登場するconnect関数などがこのHOCに該当します。

```jsx:HOCの定義
const hocHoge = (WrappedComponent) => {

  // WrappedComponentに機能を付加したComponentを生成して返す
  return class extends React.Component {
    constructor(props) {
      // 必要な情報を親から受け取ったり独自に持ったり...
      super(props);
      this.state = {
        list: []
      };
    }
    // 付加したい機能
    anyFunction() {
      const settings = this.props.settings;
      // ...なんやかやデータ操作して更新
      this.setState({
        list: newList
      });
    }
    render() {
      // 渡す必要のないpropsは中抜きする
      const { settings, ...otherProps } = this.props;

      // anyFunctionの成果物と、残りのpropsを渡す
      return (
        <WrappedComponent list={this.state.list} {...otherProps} />
      );
    }
  }
};
```

```jsx:HOCの利用
// ChildComponentを渡して、機能を追加された新たなコンポーネントを取得する
const EnhancedComponent = hocHoge(function ChildComponent({ list, text }) {
  // listの出どころなんて知らない
  return (
    <ul>
      {list.map((value, index) => (
        <li key={index}>
          {value}
          <span>{text}</span>
        </li>
      ))}
    </ul>
  );
});

// EnhancedComponentを利用する
function ParentComponent() {
  // settingsの行く末なんて知らない
  return (
    <div>
      <EnhancedComponent
        settings={anyData}
        text={anyText}
      />
    </div>
  );
}
```

`hocHoge()`関数に`ChildComponent`を渡すと、機能が付加された`EnhancedComponent`を取得できます。
`ParentComponent`は付加機能に必要なデータ`settings`と`ChildComponent`が直接必要としているデータ`text`を、区別無く渡しています。
`ChildComponent`は、付加機能で生成されたデータ`list`と`text`を区別無く`props`の一つとして受け取っています。
途中の処理は隠蔽されており、双方共に付加機能に対して特別な注意を払う必要はありません。

# 関心事の分離
公式サイトによると、HOCは横断的関心事のために使う（Use HOCs For [Cross-Cutting Concerns](https://en.wikipedia.org/wiki/Cross-cutting_concern)）とあります。
横断的関心事とは、ビジネスロジックなどに基づいて分割した複数のモジュールが共通して"関心"（何をやりたいのか）を持っている事柄を指します。
例えば、データAを取得して表示するコンポーネントと、データBを取得して表示するコンポーネント、必要とするデータも描画する内容も異なりますが、WebAPIを利用してデータを取得したいという部分においては関心事が一致しています。
この関心事が横断的関心事です。
そしてこの関心事をうまい具合に切り出して（[関心の分離](https://ja.wikipedia.org/wiki/%E9%96%A2%E5%BF%83%E3%81%AE%E5%88%86%E9%9B%A2)）共有化する方法がHOCです。

↓横断的関心事についてはこちらの説明が分かりやすかったです。
[アスペクト指向の基礎とさまざまな実装 | ITmedia エンタープライズ](https://www.itmedia.co.jp/im/articles/0410/20/news086.html)

# 連続クリックをまとめる機能を提供するHOC
隠蔽した処理の結果を渡すのではなくメソッドを渡していますし、ライフサイクルメソッドも利用していないので、HOCの使いどころを間違えているのかな？と一瞬悩みました。
しかし特定の機能の提供であり、コンポーネントの組み合わせ(Composition)とは異なりますし、ミックスインの代替案がHOCということならばHOCかなということで、HOCにしました。
（メソッドを渡すこと自体は公式サイトのサンプルにも事例があります。）
そういう時はこうすると良いよ的な情報がありましたら、コメントで頂けると幸いです。

```jsx:HOCの定義
/**
 * 複数のクリックを一つにまとめるHOC
 * @param {ReactComponent} WrappedComponent ラップされるコンポーネント
 * @return {ReactComponent} ラップ後コンポーネント
 */
const joinClickHandler = (WrappedComponent) => {

  // ラップするコンポーネントの定義
  class JoinClickHandler extends React.Component {
    constructor(props) {
      super(props);
      this.timer = null;
    }
    /**
     * クリックハンドラ
     * @param {Object} e eventオブジェクト
     * @param {Function} callBack コールバック関数
     */
    handleClick(e, callBack) {

      e.persist(); //eventオブジェクトを切り離す

      clearTimeout(this.timer);
      this.timer = setTimeout(callBack, 200, e);
    }
    // ラップされるコンポーネントに、引き継いだpropsとハンドラを渡す
    render() {
      return (
        <WrappedComponent
          {...this.props}
          handleClick={this.handleClick.bind(this)}
        />
      );
    }
  }
  // DevTool上で確認する際の名前を定義
  JoinClickHandler.displayName = `JoinClickHandler(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;

  // ラップしたコンポーネントを返す
  return JoinClickHandler;
};
```

```jsx:HOCの利用
/**
 * joinClickHandlerを利用したボタン
 */
const JoinClickButton = joinClickHandler(function JoinClickButton({ handleClick }) {

  const callBack = (e) => {
    console.log(e.target.textContent);
  };

  return (
    <button onClick={e => handleClick(e, callBack)}>
      JoinClick
    </button>
  );
});
```

200ミリ秒以内のクリックが続いた場合、最後の1回にだけ反応します。
`e.persist()`は、Reactのイベントオブジェクトを非同期処理に渡す際に必要な処理です。
このイベントオブジェクトは基本的に使いまわされており、イベントハンドラが終われば各プロパティは無効化されてしまいます。
非同期処理に渡す場合は、事前に`e.persist()`を実行して切り離す必要があります。
もちろん`preventDefault()`や`stopPropagation()`は、`persist()`の前に実行する必要があります。

利用する側では、名前付きFunctionコンポーネントを定義して`joinClickHandler()`関数に引き渡しています。
`handleClick`メソッドは`props`の一つとして、親コンポーネントからもらうその他の`props`と一緒に受け取ります。

[SyntheticEvent ＞ Event Pooling | React](https://reactjs.org/docs/events.html#event-pooling)

# React標準ハンドラとの比較サンプル
Reactの`onClick`をそのまま利用した場合との挙動の違いを確認するサンプルコードです。
さらに、上記の`joinClickHandler`を拡張して、対応するクリック回数を指定できるHOC`multiClickHandler`も作ってみました。（実際に使う機会はなさそう…）

<p class="codepen" data-height="500" data-theme-id="0" data-default-tab="js,result" data-user="bo_bee" data-slug-hash="yZbzRZ" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid black; margin: 1em 0; padding: 1em;" data-pen-title="JoinClickComponent (ReactHOC)">
  <span>See the Pen <a href="https://codepen.io/bo_bee/pen/yZbzRZ/">
  JoinClickComponent (ReactHOC)</a> by Bo_bee (<a href="https://codepen.io/bo_bee">@bo_bee</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# 参考情報
[React](https://reactjs.org/)
[Handling Events | React](https://reactjs.org/docs/handling-events.html)
[Double Click and Click on ReactJS Component | Stack Overflow](https://stackoverflow.com/questions/35491425/double-click-and-click-on-reactjs-component)
[Reactでシングルクリックとダブルクリックを区別して使いたい](https://naughtldy.hatenablog.jp/entry/2018/02/27/160000)
[Higher-Order Component（HOC）の使い方と使用上の注意点](https://qiita.com/shoichiimamura/items/92f66c19900e394a725e)

トリプルより後はなんて呼ぶのだろうと思って調べたところ、過去に一度も聞いた覚えの無い名前が並んでいました。

|要素の数|特殊名|英名|
|:--|:--|:--|
|1|シングル|single|
|2|ダブル|double|
|3|トリプル|triple|
|4|クオドループル|quadruple|
|5|クインチュープル|quintuple|
|6|セクスチュープル|sextuple|
|7|セプチュプル|septuple|
|8|オクチュプル|octuple|
|9|ノニュプル|nonuple|
|10|デキュプル|decuple|
|100|センチュプル|centuple|

from:[タプル | Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%BF%E3%83%97%E3%83%AB)