【React】 コンポーネントを受け取ってコンポーネントを作る関数

高階関数は、関数を受け取る関数や、関数を返す関数でした。高階コンポーネントでは、コンポーネントを受け取ってコンポーネントを返すものになります。

高階関数の定義に当てはめると、コンポーネントを受け取ってそれ以外を返す関数も高階コンポーネントと言えそうですが、実質意味がありません。

高階コンポーネントの小さい例

まずは小さいサンプル的な実装例を見てみます。

App.jsx
import highOrderComponent from './components/highOrderComponent';
import MyComponent from './components/MyComponent';

export default function App() {
    // 高階コンポーネントを使用
    const WrappedComponent = highOrderComponent(MyComponent);

    return (
        <>
        <WrappedComponent />
        </>
    );
}
highOrderComponent.jsx
// コンポーネントを受け取って、コンポーネントを返す関数
export default function highOrderComponent(WrappedComponent) {
    return () => (
        <>
        <p>HOC</p>
        <WrappedComponent />
        </>
    );
};
MyComponent.jsx
export default function MyComponent() {
    return (
        <p>MyComponent</p>
    )
}

この例を見ると、highOrderComponentという関数は、コンポーネントを引数で受け取り、

() => (
    <>
    <p>HOC</p>
    <WrappedComponent />
    </>
)

というコンポーネントを返していることがわかります。

コンポーネントはPascalCase、関数名はcamelCaseという使い分けになっています。例えば

export default function highOrderComponent(wrappedComponent) {
    return () => (
        <>
        <wrappedComponent />
        </>
    );
};

のようにコンポーネント名をcamelCaseにすることはできません。Reactの仕様上、PascalCaseである必要があります。

実際の使用例

サンプル例で書き方を確認できたので、実際の例を見てみましょう。例えば、オンラインステータスを表示するコンポーネントを考えてみます。

ユーザのオンラインステータスは、例えばサイドのミニアイコンに表示されることもあれば、1対1のチャット画面の相手情報表示、フレンド一覧画面など、様々な場所で共通して利用されます。 では、それぞれのコンポーネントでステータスを埋め込むコードを毎回書くのでしょうか。それは非効率です。

そこで、高階コンポーネントを使います。

アイコン部分とステータス表示をセットにしたコンポーネントを作って、アバターの大きさの種類などをpropで渡せばいいじゃんと言われればそうなのですが、例ということで…

まずはアバターの情報を表示する部分 (オンラインステータス以外) を作ります。

components/chat/MiniAvatar.jsx
export default function MiniAvatar(props) {
    return (
        <>
        <img src={`https://.../public/avatars/${props.img}`} alt={`${props.name}'s avatar`} />
        </>
    )
}
components/chat/UserListAvatar.jsx
export default function MiniAvatar(props) {
    return (
        <>
        <img src={`https://.../public/avatars/${props.img}`} alt={`${props.name}'s avatar`} />
        <p>{props.name}</p>
        </>
    )
}

次にこれらのコンポーネントを表示するコンポーネントを作って返す関数を作成します。propsも渡せるようにしましょう。

components/withOnlineStatus.jsx
export default function withOnlineStatus(WrappedComponent, user) {
    return () => {
        const onlineStatus = fetchIsOnline(user.id); // 実装は略
        
        return (
            <>
            <div>
                <WrappedComponent img={user.img} name={user.name} />
                <p>{onlineStatus ? "Online" : "Offline"}</p>
            </div>
            </>
        );
    };
};

これで、コンポーネントを作成する関数ができました。WrappedComponentに入ってくるコンポーネントが、新しく作成されるコンポーネントに埋め込まれていることがわかります。また、引数で受け取った値をpropsとして渡しています。

次にこの関数を呼び出します。

App.jsx
import withOnlineStatus from './components/withOnlineStatus';
import MiniAvatar from './components/chat/MiniAvatar';
import UserListAvatar from './components/chat/UserListAvatar';

export default function App() {
    const [users] = useState([
        {
            id: 1,
            name: "Jhon",
            img: "1.png"
        }
    ]);
    
    const MiniAvatarComponent = withOnlineStatus(MiniAvatar, users[0]);
    const UserListAvatarComponent = withOnlineStatus(UserListAvatar, users[0]);
    
    // ...
}

withOnlineStatusの第1引数にコンポーネントの関数本体を、第2引数に適当な値を入れています。そうして返ってきたコンポーネントを一旦変数に入れています。

後は表示するだけです。

<>
<MiniAvatarComponent />
<UserListAvatarComponent />
</>

まとめ

高階コンポーネントは、コンポーネントを受け取ってコンポーネントを返す関数です。いくつかの種類のコンポーネント内に共通の処理を組み込みたい場合などに使用します。

処理をうまく分割して、使い回しがしやすくなるように工夫してみましょう。

react hoc thumb

役に立ったらシェアしよう!