LoginSignup
23
16

More than 3 years have passed since last update.

HOC(Heigher-Order-Component)ってどうなったの?

Last updated at Posted at 2019-11-01
1 / 20

公式非推奨になったの?

そんな噂を耳にしたので調べてみることにしました。
結果としてはそのような記述は見つからなかったのですが、
改めてHOCについてまとめてみたいと思います。


HOCとは

  • 日本語で高階コンポーネント
  • コンポーネントを引数にとり、コンポーネントを返す関数
  • ロジックを綺麗に分離できるとしてReact界隈でrecomposeと共に一時期流行
  • recomposeの開発終了とhooksの登場によって下火化
  • 身近なHOCはreact-reduxのconnect関数

事例1(アクセスログHOC)

// willMountのタイミングでアクセス解析関数を実行する
const withAccessLog = (Component) =>
  class extends React.Component {
    componentDidMount() {
      accessLogger.log();
    }
    render() {
      return <Component {...this.props} />
    }
  };

const Text = ({ text }) => <span>{text}</span>;
const WrappedText = withAccessLog(Text);
--
<WrappedText text="hoge" />

事例2(IE対応HOC)

// スクロールして頭がついたら固定してついてくるやつ化
const withScroll = (Component) => props => {
  let className = 'forModernBrowser';
  if (isIE()) {
    className = 'forIE'; // 実際はスクロール量とか高さとかを計算
  }
  return (
    <div className={className}>
      <Component {...props} />
    </div>
  );
}

const Text = ({ text }) => <span>{text}</span>;
const WrappedText = withScroll(Text);
--
<WrappedText text="hoge" />

事例3(開閉機能HOC)

const withToggle = (Component) =>
  class extends React.Component {
    constructor(props) {
      super(props); 
      this.state = { isOpen : false };
      this.toggleOpen = this.toggleOpen.bind(this);
    }
    toggleOpen() {
      this.setState(previousState => {
        return { isOpen : !previousState.isOpen };
      });
    }
    render() {
      return (
        <>
          <button onClick={this.toggleOpen}>open/close</button>
          {this.state.isOpen
            ? <Component {...this.props} toggleOpen={this.toggleOpen} />
            : null
          }
        </>
      );
    }
  };

const Text = ({ text }) => <span>{text}</span>;
const WrappedText = withToggle(Text);
--
<WrappedText text="hoge" />

HOCのメリット

  • ロジックの再利用性を高められる
  • 細かく分けられるので1関数1役割にしやすい
  • 残されたコンポーネントがviewに専念できる

→ デメテルの法則や、DRY、KISSを徹底しやすい


HOCのデメリット

  • HOCから渡されるprops名が重複すると使えない
    • HOCとラップされるコンポーネント間
    • HOCとHOC間(複数重ねる場合)
  • refを使ったHOCをさらにラップすると意図しないrefを取ってしまう
  • ラップされる側からみて、親から渡すpropsなのかHOCからくるpropsなのか判断できない
  • デバッグツールの表示が見にくくなりがち

→ わかりにくなり、気にすることが増える


使い所あるの?

  • もうないかもしれない、、、
  • ほとんどの場合下記のどれかでデメリット軽減で同等のものが書ける
    • コンポジション(コンポーネント化)
    • hooks化

→ 前述の事例を書き換えてみる


変換例1(アクセスログHOC)

// willMountのタイミングでアクセス解析関数を実行する
const withAccessLog = (Component) =>
  class extends React.Component {
    componentDidMount() {
      accessLogger.log();
    }
    render() {
      return <Component {...this.props} />
    }
  };

const Text = ({ text }) => <span>{text}</span>;
const WrappedText = withAccessLog(Text);
--
<WrappedText text="hoge" />

変換例1(アクセスログHOC)

ライフサイクルメソッドを使いたいだけなので、hooksの一つ、useEffectで置き換えます。


変換例1(アクセスログHOC)

// willMountのタイミングでアクセス解析関数を実行する
const useAccessLogger = () => (
  useEffect(() => {
    accessLogger.log();
  }, [])
);

const EquivalentText = ({ text }) => {
  useAccessLogger();
  return <span>{text}</span>;
}
--
<EquivalentText text="hoge" />

See the Pen convert sample 1 by @putan


変換例2(IE対応HOC)

// スクロールして頭がついたら固定してついてくるやつ化
const withScroll = (Component) => props => {
  let className = 'forModernBrowser';
  if (isIE()) {
    className = 'forIE'; // 実際はスクロール量とか高さとかを計算
  }
  return (
    <div className={className}>
      <Component {...props} />
    </div>
  );
}

const Text = ({ text }) => <span>{text}</span>;
const WrappedText = withScroll(Text);
--
<WrappedText text="hoge" />

変換例2(IE対応HOC)

こちらはコンポジション(コンポーネント化)で対応


変換例2(IE対応HOC)

// スクロールして頭がついたら固定してついてくるやつ化
const WithScroll = ({ children }) => {
  let className = 'forModernBrowser';
  if (isIE()) {
    className = 'forIE'; // 実際はスクロール量とか高さとかを計算
  }
  return (
    <div className={className}>
      {children}
    </div>
  );
}

const Text = ({ text }) => <span>{text}</span>;
--
<WithScroll>
  <Text text="hoge" />
</WithScroll>

See the Pen convert sample 2 by @putan


変換例3(開閉機能HOC)

const withToggle = (Component) =>
  class extends React.Component {
    constructor(props) {
      super(props); 
      this.state = { isOpen : false };
      this.toggleOpen = this.toggleOpen.bind(this);
    }
    toggleOpen() {
      this.setState(previousState => {
        return { isOpen : !previousState.isOpen };
      });
    }
    render() {
      return (
        <>
          <button onClick={this.toggleOpen}>open/close</button>
          {this.state.isOpen
            ? <Component {...this.props} />
            : null
          }
        </>
      );
    }
  };

const Text = ({ text }) => <span>{text}</span>;
const WrappedText = withToggle(Text);
--
<WrappedText text="hoge" />

変換例3(開閉機能HOC)

こちらはコンポジションとhooksを組み合わせて置き換えます。


変換例3(開閉機能HOC)

const WithToggle = ({ children }) => {
  const [isOpen, setIsOpen] = useState(false);
  const toggleOpen = () => setIsOpen(!isOpen);

  return (
    <>
      <button onClick={toggleOpen}>open/close</button>
      {isOpen
        ? children
        : null
      }
    </>
  );
}
const Text = ({ text }) => <span>{text}</span>;
--
<WithToggle>
  <Text text="hoge" />
</WithToggle>

See the Pen convert sample 3 by @putan


まとめ

  • HOCのメリデメを確認した
  • デメリットが上回っているので利用は避けたほうが無難そう
  • 個人的にはHOCが輝ける場所(条件)を探していきたい

資料

23
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
16