Next.jsでのHOCの実装パターン

公開日: 2018年6月1日GitHub

モチベーション

  • Next.jsはgetInitialPropsというメソッドを定義することで、そのメソッドの返り値をコンポーネントのpropsを経由して渡すことができます
    • このgetInitialPropsは、SSR時であってもクライアントでもインスタンスの生成の前に呼ばれます(そしてgetInitialPropsの処理が完了したあとにページのconstructorにPropsとして渡される)
    • そのため、APIサーバーへのリクエストなどをしてSSRに必要なデータを取得するために使われる事が多いです。
  • 一方でHigh Order Component(以下HOC)をこのコンポーネントに適用しようすると、HOCによってページのコンポーネントがラップされてしまうため、子要素のgetInitialPropsが呼ばれず、コンポーネントに渡されるべきpropsが供給されないという問題が発生します。
  • この記事ではHOCを使っても子要素のgetInitialPropsが呼ばれるようにするためのHOCの書き方を紹介します。

解決策

  • Next.jsでHOCを書く場合は、返却されるコンポーネントにgetInitialPropsを実装する必要があります。
  • 新しく定義されたgetInitialPropsでやることは以下です
    • 子コンポーネントがgetInitialPropsを実装しているかチェック
    • もしそれが存在するならば実行し、returnします

実装例

シンプルな例

子コンポーネントをラップするだけ。 ログイン時のとき以外表示させないときにつかうHOC

1import * as React from 'react'
2import NotAuthorized from '../components/not_authorized'
3
4interface Props {
5 auth: {
6 isAuthenticated: boolean;
7 accessToken?: string;
8 userId?: string;
9 }
10}
11
12const withLogin = Page => class SecurePage extends React.Component<Props, {}> {
13 static async getInitialProps (ctx) {
14 // ポイントはここ
15 // 引数にとったコンポーネントに、getInitialPropsが定義されているならばそれを実行する
16 return Page.getInitialProps && ctx.auth.isAuthenticated && await Page.getInitialProps(ctx)
17 }
18
19 render () {
20 const { isAuthenticated } = this.props.auth
21 return isAuthenticated ? <Page {...this.props} /> : <NotAuthorized />
22 }
23}
24
25export default withLogin
26

複雑な例

HOCを返す高階関数の場合

以下はレイアウトファイルを指定するHOC。

1import {Component} from "react"
2
3const withLayout = Layout => {
4 return Page => {
5 return class WrapperComponent extends Component<{layoutProps: {}, pageProps: {}}, {}> {
6 static async getInitialProps(ctx) {
7 let pageProps = {}
8 let layoutProps = {}
9
10 // Layoutで使うpropsがあればここで取得する
11 if (Layout.getInitialProps) {
12 layoutProps = await Layout.getInitialProps(ctx)
13 }
14
15 // Pageで使うpropsがあればここで取得する
16 if (Page.getInitialProps) {
17 pageProps = await Page.getInitialProps(ctx)
18 }
19
20 // 両者を合成してreturnする。
21 return {
22 ...layoutProps,
23 ...pageProps
24 }
25 }
26
27 render () {
28 return (
29 <Layout {...this.props}>
30 <Page {...this.props} />
31 </Layout>
32 )
33 }
34 }
35 }
36}
37
38export default withLayout;
39

使い方の例

1withLayout(AdminLayout)(Page)
2
This site uses Google Analytics.
source code