React の様々なコードを読む中で、頻繁にみる HOC に関して、整理するためにまとめを行った。
Higher-Order Component(HOC) とは
- Higher Order Components はComponentのラッパーみたいなもので、よくHOCと略される
- 日本語に訳すると「高階コンポーネント」であり、高階関数である
- 高階コンポーネントとは、あるコンポーネントを受け取って新規のコンポーネントを返す関数である
- 既存のコンポーネントに新しい機能を追加することができる
高階関数は「関数を引数または戻り値として扱う関数」のことである。関数を引数として扱うのは、コールバック関数がよく例に挙げられる。HOC は、「コンポーネントを引数にとってコンポーネントを返す関数」であり、一見高階関数ではないように思える。しかしながら、コンポーネント自体が関数であり、Props を引数として受け取り、React DOM を返している。そのため、HOC は高階関数といえる。
const EnhancedComponent = higherOrderComponent(WrappedComponent)
HOC を利用することのメリットは、1 つの場所にロジックを定義し、多数のコンポーネントを横断してロジックを共有可能にするような抽象化ができることが挙げられる。
雛形
実際に、HOC を定義する場合は、以下のような雛形となる。
import React, { Component } from 'react' export const HOC = (WrappedComponent) => { return class HigherOrderComponent extends Component { // 共通の処理を記述する render() { return <WrappedComponent ... /> } } }
利用例
参考ライブラリ
- material-ui / muiThemeable
- react-router / withRouter
Private Route
ログインしているアカウントしか閲覧できないページなどを制御したい場合のラッパーコンポーネントを実装する場合にも利用できる。
import React from 'react' import Router from 'next/router' const login = '/login?redirected=true' const checkUserAuthentication = () => { return { auth: null }; // change null to { isAdmin: true } for test it. } export default WrappedComponent => { const hocComponent = ({ ...props }) => <WrappedComponent {...props} /> hocComponent.getInitialProps = async ({ res }) => { const userAuth = await checkUserAuthentication() if (!userAuth?.auth) { // Handle server-side and client-side rendering. if (res) { res?.writeHead(302, { Location: login }) res?.end() } else { Router.replace(login) } } else if (WrappedComponent.getInitialProps) { const wrappedProps = await WrappedComponent.getInitialProps(userAuth) return { ...wrappedProps, userAuth } } return { userAuth } } return hocComponent }
import React from 'react' import withPrivateRoute from '../components/withPrivateRoute' const Dashboard = () => { return <div>This is a Dashboard page which is private.</div> } Dashboard.getInitialProps = async props => { console.info('##### Congratulations! You are authorized! ######', props) return {} } export default withPrivateRoute(Dashboard)
引用元:How to simply create a Private Route in Next.js | by Ali Eslamifard | Medium
注意事項
HOC は副作用のない純関数でなければいけない
HOC は入力のコンポーネントを改変したり、振る舞いをコピーするのに継承を利用したりしない。HOC は元のコンポーネントをコンテナコンポーネント内にラップすることで組み合わせるのみである。renderメソッド内部でHOCを使用しないこと
必ずコンポーネント定義の外で HOC を適用してください。render内に HOC を作った場合、毎回新しい参照のComponentができ、そのコンポーネント以下のツリーは全て毎回再描画されることになってしまう。静的メソッドは必ずコピーする、またはエクスポートする
HOC をコンポーネントに適用すると、新しいコンポーネントは元のコンポーネントの静的メソッドを 1 つも持っていないということになってしまう。そのため、コンテナコンポーネントを返す前にメソッドをコピーするか、hoist-non-react-statics を使用することで、全ての非 React の静的メソッドを自動的にコピーする必要がある。または、静的メソッドをコンポーネントと分離して、静的メソッドでエクスポートする必要がある。ref 属性は渡されない
HOC から出力されたコンポーネントの要素に ref 属性を追加する場合、ref 属性はラップされた内側のコンポーネントではなく、最も外側のコンテナコンポーネントを参照する。
まとめ
React のコンポーネント設計で重要な HOC について整理をしてみた。アプリケーション内で共通に利用する機能や、パーツをまとめるのに、非常に便利であると思えた。
それでは、ステキな開発ライフを。