手把手帶你用next搭建一個完善的react服務端渲染項目(集成antd、redux、樣式解決方案)

前言

本文參考了慕課網jokcy老師的React16.8+Next.js+Koa2開發Github全棧項目,也算是作個筆記吧。css

源碼地址

github.com/sl1673495/n…html

介紹

Next.js 是一個輕量級的 React 服務端渲染應用框架。前端

官網:nextjs.org
中文官網:nextjs.frontendx.cnnode

當使用 React 開發系統的時候,經常須要配置不少繁瑣的參數,如 Webpack 配置、Router 配置和服務器配置等。若是須要作 SEO,要考慮的事情就更多了,怎麼讓服務端渲染和客戶端渲染保持一致是一件很麻煩的事情,須要引入不少第三方庫。針對這些問題,Next.js提供了一個很好的解決方案,使開發人員能夠將精力放在業務上,從繁瑣的配置中解放出來。下面咱們一塊兒來從零開始搭建一個完善的next項目。react

項目的初始化

首先安裝 create-next-app 腳手架webpack

npm i -g create-next-app
複製代碼

而後利用腳手架創建 next 項目git

create-next-app next-github
cd next-github
npm run dev
複製代碼

能夠看到 pages 文件夾下的 index.jsgithub

生成的目錄結構很簡單,咱們稍微加幾個內容web

├── README.md
├── components // 非頁面級共用組件
│   └── nav.js
├── package-lock.json
├── package.json
├── pages // 頁面級組件 會被解析成路由
│   └── index.js
├── lib // 一些通用的js
├── static // 靜態資源
│   └── favicon.ico

複製代碼

啓動項目以後,默認端口啓動在 3000 端口,打開 localhost:3000 後,默認訪問的就是 index.js 裏的內容npm

把 next 做爲 Koa 的中間件使用。(可選)

若是要集成koa的話,能夠參考這一段。
在根目錄新建 server.js 文件

// server.js

const Koa = require('koa')
const Router = require('koa-router')
const next = require('next')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

const PORT = 3001
// 等到pages目錄編譯完成後啓動服務響應請求
app.prepare().then(() => {
  const server = new Koa()
  const router = new Router()

  server.use(async (ctx, next) => {
    await handle(ctx.req, ctx.res)
    ctx.respond = false
  })

  server.listen(PORT, () => {
    console.log(`koa server listening on ${PORT}`)
  })
})
複製代碼

而後把package.json中的dev命令改掉

scripts": { "dev": "node server.js", "build": "next build", "start": "next start" } 複製代碼

ctx.reqctx.res 是 node 原生提供的

之因此要傳遞 ctx.reqctx.res,是由於 next 並不僅是兼容 koa 這個框架,因此須要傳遞 node 原生提供的 reqres

集成 css

next 中默認不支持直接 import css 文件,它默認爲咱們提供了一種 css in js 的方案,因此咱們要本身加入 next 的插件包進行 css 支持

yarn add @zeit/next-css
複製代碼

若是項目根目錄下沒有的話
咱們新建一個next.config.js
而後加入以下代碼

const withCss = require('@zeit/next-css')

if (typeof require !== 'undefined') {
  require.extensions['.css'] = file => {}
}

// withCss獲得的是一個next的config配置
module.exports = withCss({})
複製代碼

集成 ant-design

yarn add antd
yarn add babel-plugin-import // 按需加載插件
複製代碼

在根目錄下新建.babelrc文件

{
  "presets": ["next/babel"],
  "plugins": [
    [
      "import",
      {
        "libraryName": "antd"
      }
    ]
  ]
}
複製代碼

這個 babel 插件的做用是把

import { Button } from 'antd'
複製代碼

解析成

import Button from 'antd/lib/button'
複製代碼

這樣就完成了按需引入組件

在 pages 文件夾下新建_app.js,這是 next 提供的讓你重寫 App 組件的方式,在這裏咱們能夠引入 antd 的樣式

pages/_app.js

import App from 'next/app'

import 'antd/dist/antd.css'

export default App
複製代碼

next 中的路由

利用Link組件進行跳轉

import Link from 'next/link'
import { Button } from 'antd'

const LinkTest = () => (
  <div> <Link href="/a"> <Button>跳轉到a頁面</Button> </Link> </div>
)

export default LinkTest
複製代碼

利用Router模塊進行跳轉

import Link from 'next/link'
import Router from 'next/router'
import { Button } from 'antd'

export default () => {
  const goB = () => {
    Router.push('/b')
  }

  return (
    <> <Link href="/a"> <Button>跳轉到a頁面</Button> </Link> <Button onClick={goB}>跳轉到b頁面</Button> </> ) } 複製代碼

動態路由

在 next 中,只能經過query來實現動態路由,不支持/b/:id 這樣的定義方法

首頁

import Link from 'next/link'
import Router from 'next/router'
import { Button } from 'antd'

export default () => {
  const goB = () => {
    Router.push('/b?id=2')
    // 或
    Router.push({
      pathname: '/b',
      query: {
        id: 2,
      },
    })
  }

  return <Button onClick={goB}>跳轉到b頁面</Button>
}
複製代碼

B 頁面

import { withRouter } from 'next/router'

const B = ({ router }) => <span>這是B頁面, 參數是{router.query.id}</span>
export default withRouter(B)
複製代碼

此時跳轉到 b 頁面的路徑是/b?id=2

若是真的想顯示成/b/2這種形式的話, 也能夠經過Link上的as屬性來實現

<Link href="/a?id=1" as="/a/1">
  <Button>跳轉到a頁面</Button>
</Link>
複製代碼

或在使用Router

Router.push(
  {
    pathname: '/b',
    query: {
      id: 2,
    },
  },
  '/b/2'
)
複製代碼

可是使用這種方法,在頁面刷新的時候會 404
是由於這種別名的方法只是在前端路由跳轉的時候加上的
刷新時請求走了服務端就認不得這個路由了

使用 koa 能夠解決這個問題

// server.js

const Koa = require('koa')
const Router = require('koa-router')
const next = require('next')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

const PORT = 3001
// 等到pages目錄編譯完成後啓動服務響應請求
app.prepare().then(() => {
  const server = new Koa()
  const router = new Router()

  // start
  // 利用koa-router去把/a/1這種格式的路由
  // 代理到/a?id=1去,這樣就不會404了
  router.get('/a/:id', async ctx => {
    const id = ctx.params.id
    await handle(ctx.req, ctx.res, {
      pathname: '/a',
      query: {
        id,
      },
    })
    ctx.respond = false
  })
  server.use(router.routes())
  // end

  server.use(async (ctx, next) => {
    await handle(ctx.req, ctx.res)
    ctx.respond = false
  })

  server.listen(PORT, () => {
    console.log(`koa server listening on ${PORT}`)
  })
})
複製代碼

Router 的鉤子

在一次路由跳轉中,前後會觸發
routeChangeStart
beforeHistoryChange
routeChangeComplete

若是有錯誤的話,則會觸發
routeChangeError

監聽的方式是

Router.events.on(eventName, callback)
複製代碼

自定義 document

  • 只有在服務端渲染的時候纔會被調用
  • 用來修改服務端渲染的文檔內容
  • 通常用來配合第三方 css in js 方案使用

在 pages 下新建_document.js,咱們能夠根據需求去重寫。

import Document, { Html, Head, Main, NextScript } from 'next/document'

export default class MyDocument extends Document {
  // 若是要重寫render 就必須按照這個結構來寫
  render() {
    return (
      <Html> <Head> <title>ssh-next-github</title> </Head> <body> <Main /> <NextScript /> </body> </Html>
    )
  }
}
複製代碼

自定義 app

next 中,pages/_app.js 這個文件中暴露出的組件會做爲一個全局的包裹組件,會被包在每個頁面組件的外層,咱們能夠用它來

  • 固定 Layout
  • 保持一些共用的狀態
  • 給頁面傳入一些自定義數據 pages/_app.js

給個簡單的例子,先別改_app.js 裏的代碼,不然接下來 getInitialProps 就獲取不到數據了,這個後面再處理。

import App, { Container } from 'next/app'
import 'antd/dist/antd.css'
import React from 'react'

export default class MyApp extends App {
  render() {
    // Component就是咱們要包裹的頁面組件
    const { Component } = this.props
    return (
      <Container> <Component /> </Container>
    )
  }
}
複製代碼

封裝 getInitialProps

getInitialProps 的做用很是強大,它能夠幫助咱們同步服務端和客戶端的數據,咱們應該儘可能把數據獲取的邏輯放在 getInitialProps 裏,它能夠:

  • 在頁面中獲取數據
  • 在 App 中獲取全局數據

基本使用

經過 getInitialProps 這個靜態方法返回的值 都會被當作 props 傳入組件

const A = ({ name }) => (
  <span>這是A頁面, 經過getInitialProps得到的name是{name}</span>
)

A.getInitialProps = () => {
  return {
    name: 'ssh',
  }
}
export default A
複製代碼

可是須要注意的是,只有 pages 文件夾下的組件(頁面級組件)纔會調用這個方法。next 會在路由切換前去幫你調用這個方法,這個方法在服務端渲染和客戶端渲染都會執行。(刷新前端跳轉)
而且若是服務端渲染已經執行過了,在進行客戶端渲染時就不會再幫你執行了。

異步場景

異步場景能夠經過 async await 來解決,next 會等到異步處理完畢 返回告終果後之後再去渲染頁面

const A = ({ name }) => (
  <span>這是A頁面, 經過getInitialProps得到的name是{name}</span>
)

A.getInitialProps = async () => {
  const result = Promise.resolve({ name: 'ssh' })
  await new Promise(resolve => setTimeout(resolve, 1000))
  return result
}
export default A
複製代碼

在_app.js 裏獲取數據

咱們重寫一些_app.js 裏獲取數據的邏輯

import App, { Container } from 'next/app'
import 'antd/dist/antd.css'
import React from 'react'

export default class MyApp extends App {
  // App組件的getInitialProps比較特殊
  // 能拿到一些額外的參數
  // Component: 被包裹的組件
  static async getInitialProps(ctx) {
    const { Component } = ctx
    let pageProps = {}

    // 拿到Component上定義的getInitialProps
    if (Component.getInitialProps) {
      // 執行拿到返回結果
      pageProps = await Component.getInitialProps(ctx)
    }

    // 返回給組件
    return {
      pageProps,
    }
  }

  render() {
    const { Component, pageProps } = this.props
    return (
      <Container> {/* 把pageProps解構後傳遞給組件 */} <Component {...pageProps} /> </Container> ) } } 複製代碼

封裝通用 Layout

咱們但願每一個頁面跳轉之後,均可以有共同的頭部導航欄,這就能夠利用_app.js 來作了。

在 components 文件夾下新建 Layout.jsx:

import Link from 'next/link'
import { Button } from 'antd'

export default ({ children }) => (
  <header> <Link href="/a"> <Button>跳轉到a頁面</Button> </Link> <Link href="/b"> <Button>跳轉到b頁面</Button> </Link> <section className="container">{children}</section> </header>
)
複製代碼

在_app.js 裏

// 省略
import Layout from '../components/Layout'

export default class MyApp extends App {
  // 省略

  render() {
    const { Component, pageProps } = this.props
    return (
      <Container> {/* Layout包在外面 */} <Layout> {/* 把pageProps解構後傳遞給組件 */} <Component {...pageProps} /> </Layout> </Container> ) } } 複製代碼

document title 的解決方案

例如在 pages/a.js 這個頁面中,我但願網頁的 title 是 a,在 b 頁面中我但願 title 是 b,這個功能 next 也給咱們提供了方案

pages/a.js

import Head from 'next/head'

const A = ({ name }) => (
  <> <Head> <title>A</title> </Head> <span>這是A頁面, 經過getInitialProps得到的name是{name}</span> </> ) export default A 複製代碼

樣式的解決方案(css in js)

next 默認採用的是 styled-jsx 這個庫
github.com/zeit/styled…

須要注意的點是:組件內部的 style 標籤,只有在組件渲染後纔會被加到 head 裏生效,組件銷燬後樣式就失效。

組件內部樣式

next 默認提供了樣式的解決方案,在組件內部寫的話默認的做用域就是該組件,寫法以下:

const A = ({ name }) => (
  <> <span className="link">這是A頁面</span> <style jsx> {` .link { color: red; } `} </style> </> ) export default A ) 複製代碼

咱們能夠看到生成的 span 標籤變成了

<span class="jsx-3081729934 link">這是A頁面</span>
複製代碼

生效的 css 樣式變成了

.link.jsx-3081729934 {
  color: red;
}
複製代碼

經過這種方式作到了組件級別的樣式隔離,而且 link 這個 class 假如在全局有定義樣式的話,也同樣能夠獲得樣式。

全局樣式

<style jsx global>
  {`
    .link {
      color: red;
    }
  `}
</style>
複製代碼

樣式的解決方案(styled-component)

首先安裝依賴

yarn add styled-components babel-plugin-styled-components
複製代碼

而後咱們在.babelrc 中加入 plugin

{
  "presets": ["next/babel"],
  "plugins": [
    [
      "import",
      {
        "libraryName": "antd"
      }
    ],
    ["styled-components", { "ssr": true }]
  ]
}
複製代碼

在 pages/_document.js 里加入 jsx 的支持,這裏用到了 next 給咱們提供的一個覆寫 app 的方法,其實就是利用高階組件。

import Document, { Html, Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet()
    // 劫持本來的renderPage函數並重寫
    const originalRenderPage = ctx.renderPage

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          // 根App組件
          enhanceApp: App => props => sheet.collectStyles(<App {...props} />), }) // 若是重寫了getInitialProps 就要把這段邏輯從新實現 const props = await Document.getInitialProps(ctx) return { ...props, styles: ( <> {props.styles} {sheet.getStyleElement()} </> ), } } finally { sheet.seal() } } // 若是要重寫render 就必須按照這個結構來寫 render() { return ( <Html> <Head /> <body> <Main /> <NextScript /> </body> </Html> ) } } 複製代碼

而後在 pages/a.js 中

import styled from 'styled-components'

const Title = styled.h1` color: yellow; font-size: 40px; `
const A = ({ name }) => (
  <> <Title>這是A頁面</Title> </> ) export default A 複製代碼

next 中的 LazyLoading

next 中默認幫咱們開啓了 LazyLoading,切換到對應路由纔會去加載對應的 js 模塊。

LazyLoading 通常分爲兩類

  • 異步加載模塊
  • 異步加載組件

首先咱們利用 moment 這個庫演示一下異步加載模塊的展現。

異步加載模塊

咱們在 a 頁面中引入 moment 模塊 // pages/a.js

import styled from 'styled-components'
import moment from 'moment'

const Title = styled.h1` color: yellow; font-size: 40px; `
const A = ({ name }) => {
  const time = moment(Date.now() - 60 * 1000).fromNow()
  return (
    <> <Title>這是A頁面, 時間差是{time}</Title> </> ) } export default A 複製代碼

這會帶來一個問題,若是咱們在多個頁面中都引入了 moment,這個模塊默認會被提取到打包後的公共的 vendor.js 裏。

咱們能夠利用 webpack 的動態 import 語法

A.getInitialProps = async ctx => {
  const moment = await import('moment')
  const timeDiff = moment.default(Date.now() - 60 * 1000).fromNow()
  return { timeDiff }
}
複製代碼

這樣只有在進入了 A 頁面之後,纔會下載 moment 的代碼。

異步加載組件

next 官方爲咱們提供了一個dynamic方法,使用示例:

import dynamic from 'next/dynamic'

const Comp = dynamic(import('../components/Comp'))

const A = ({ name, timeDiff }) => {
  return (
    <>
      <Comp />
    </>
  )
}

export default A

複製代碼

使用這種方式引入普通的 react 組件,這個組件的代碼就只會在 A 頁面進入後纔會被下載。

next.config.js 完整配置

next 回去讀取根目錄下的next.config.js文件,每一項都用註釋標明瞭,能夠根據本身的需求來使用。

const withCss = require('@zeit/next-css')

const configs = {
  // 輸出目錄
  distDir: 'dest',
  // 是否每一個路由生成Etag
  generateEtags: true,
  // 本地開發時對頁面內容的緩存
  onDemandEntries: {
    // 內容在內存中緩存的時長(ms)
    maxInactiveAge: 25 * 1000,
    // 同時緩存的頁面數
    pagesBufferLength: 2,
  },
  // 在pages目錄下會被當作頁面解析的後綴
  pageExtensions: ['jsx', 'js'],
  // 配置buildId
  generateBuildId: async () => {
    if (process.env.YOUR_BUILD_ID) {
      return process.env.YOUR_BUILD_ID
    }

    // 返回null默認的 unique id
    return null
  },
  // 手動修改webpack配置
  webpack(config, options) {
    return config
  },
  // 手動修改webpackDevMiddleware配置
  webpackDevMiddleware(config) {
    return config
  },
  // 能夠在頁面上經過process.env.customkey 獲取 value
  env: {
    customkey: 'value',
  },
  // 下面兩個要經過 'next/config' 來讀取
  // 能夠在頁面上經過引入 import getConfig from 'next/config'來讀取

  // 只有在服務端渲染時纔會獲取的配置
  serverRuntimeConfig: {
    mySecret: 'secret',
    secondSecret: process.env.SECOND_SECRET,
  },
  // 在服務端渲染和客戶端渲染均可獲取的配置
  publicRuntimeConfig: {
    staticFolder: '/static',
  },
}

if (typeof require !== 'undefined') {
  require.extensions['.css'] = file => {}
}

// withCss獲得的是一個nextjs的config配置
module.exports = withCss(configs)
複製代碼

ssr 流程

next 幫咱們解決了 getInitialProps 在客戶端和服務端同步的問題,

ssr渲染流程

next 會把服務端渲染時候獲得的數據經過NEXT_DATA這個 key 注入到 html 頁面中去。

好比咱們以前舉例的 a 頁面中,大概是這樣的格式

script id="__NEXT_DATA__" type="application/json">
      {
        "dataManager":"[]",
        "props":
          {
            "pageProps":{"timeDiff":"a minute ago"}
          },
        "page":"/a",
        "query":{},
        "buildId":"development",
        "dynamicBuildId":false,
        "dynamicIds":["./components/Comp.jsx"]
      }
      </script>
複製代碼

引入 redux (客戶端普通寫法)

yarn add redux

在根目錄下新建 store/store.js 文件

// store.js

import { createStore, applyMiddleware } from 'redux'
import ReduxThunk from 'redux-thunk'

const initialState = {
  count: 0,
}

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'add':
      return {
        count: state.count + 1,
      }
      break

    default:
      return state
  }
}

// 這裏暴露出的是建立store的工廠方法
// 每次渲染都須要從新建立一個store實例
// 防止服務端一直複用舊實例 沒法和客戶端狀態同步
export default function initializeStore() {
  const store = createStore(reducer, initialState, applyMiddleware(ReduxThunk))
  return store
}
複製代碼

引入 react-redux

yarn add react-redux
而後在_app.js 中用這個庫提供的 Provider 包裹在組件的外層 而且傳入你定義的 store

import { Provider } from 'react-redux'
import initializeStore from '../store/store'

...
render() {
    const { Component, pageProps } = this.props
    return (
      <Container> <Layout> <Provider store={initializeStore()}> {/* 把pageProps解構後傳遞給組件 */} <Component {...pageProps} /> </Provider> </Layout> </Container> ) } 複製代碼

在組件內部

import { connect } from 'react-redux'

const Index = ({ count, add }) => {
  return (
    <> <span>首頁 state的count是{count}</span> <button onClick={add}>增長</button> </> ) } function mapStateToProps(state) { const { count } = state return { count, } } function mapDispatchToProps(dispatch) { return { add() { dispatch({ type: 'add' }) }, } } export default connect( mapStateToProps, mapDispatchToProps )(Index) 複製代碼

利用 hoc 集成 redux 和 next

在上面 引入 redux (客戶端普通寫法) 介紹中,咱們簡單的和日常同樣去引入了 store,可是這種方式在咱們使用 next 作服務端渲染的時候有個很嚴重的問題,假如咱們在 Index 組件的 getInitialProps 中這樣寫

Index.getInitialProps = async ({ reduxStore }) => {
  store.dispatch({ type: 'add' })
  return {}
}
複製代碼

進入 index 頁面之後就會報一個錯誤

Text content did not match. Server: "1" Client: "0"
複製代碼

而且你每次刷新 這個 Server 後面的值都會加 1,這意味着若是多個瀏覽器同時訪問,store裏的count就會一直遞增,這是很嚴重的 bug。

這段報錯的意思就是服務端的狀態和客戶端的狀態不一致了,服務端拿到的count是 1,可是客戶端的count倒是 0,其實根本緣由就是服務端解析了 store.js 文件之後拿到的 store和客戶端拿到的 store 狀態不一致,其實在同構項目中,服務端和客戶端會持有各自不一樣的 store,而且在服務端啓動了的生命週期中 store 是保持同一份引用的,因此咱們必須想辦法讓二者狀態統一,而且和單頁應用中每次刷新之後store從新初始化這個行爲要一致。在服務端解析過拿到 store 之後,直接讓客戶端用服務端解析的值來初始化 store。

總結一下,咱們的目標有:

  • 每次請求服務端的時候(頁面初次進入,頁面刷新),store 從新建立。
  • 前端路由跳轉的時候,store 複用以前建立好的。
  • 這種判斷不能寫在每一個組件的 getInitialProps 裏,想辦法抽象出來。

因此咱們決定利用hoc來實現這個邏輯複用。

首先咱們改造一下 store/store.js,再也不直接暴露出 store 對象,而是暴露一個建立 store 的方法,而且容許傳入初始狀態來進行初始化。

import { createStore, applyMiddleware } from 'redux'
import ReduxThunk from 'redux-thunk'

const initialState = {
  count: 0,
}

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'add':
      return {
        count: state.count + 1,
      }
      break

    default:
      return state
  }
}

export default function initializeStore(state) {
  const store = createStore(
    reducer,
    Object.assign({}, initialState, state),
    applyMiddleware(ReduxThunk)
  )
  return store
}
複製代碼

在 lib 目錄下新建 with-redux-app.js,咱們決定用這個 hoc 來包裹_app.js 裏導出的組件,每次加載 app 都要經過咱們這個 hoc。

import React from 'react'
import initializeStore from '../store/store'

const isServer = typeof window === 'undefined'
const __NEXT_REDUX_STORE__ = '__NEXT_REDUX_STORE__'

function getOrCreateStore(initialState) {
  if (isServer) {
    // 服務端每次執行都從新建立一個store
    return initializeStore(initialState)
  }
  // 在客戶端執行這個方法的時候 優先返回window上已有的store
  // 而不能每次執行都從新建立一個store 不然狀態就無限重置了
  if (!window[__NEXT_REDUX_STORE__]) {
    window[__NEXT_REDUX_STORE__] = initializeStore(initialState)
  }
  return window[__NEXT_REDUX_STORE__]
}

export default Comp => {
  class withReduxApp extends React.Component {
    constructor(props) {
      super(props)
      // getInitialProps建立了store 這裏爲何又從新建立一次?
      // 由於服務端執行了getInitialProps以後 返回給客戶端的是序列化後的字符串
      // redux裏有不少方法 不適合序列化存儲
      // 因此選擇在getInitialProps返回initialReduxState初始的狀態
      // 再在這裏經過initialReduxState去建立一個完整的store
      this.reduxStore = getOrCreateStore(props.initialReduxState)
    }

    render() {
      const { Component, pageProps, ...rest } = this.props
      return (
        <Comp {...rest} Component={Component} pageProps={pageProps} reduxStore={this.reduxStore} /> ) } } // 這個實際上是_app.js的getInitialProps // 在服務端渲染和客戶端路由跳轉時會被執行 // 因此很是適合作redux-store的初始化 withReduxApp.getInitialProps = async ctx => { const reduxStore = getOrCreateStore() ctx.reduxStore = reduxStore let appProps = {} if (typeof Comp.getInitialProps === 'function') { appProps = await Comp.getInitialProps(ctx) } return { ...appProps, initialReduxState: reduxStore.getState(), } } return withReduxApp } 複製代碼

在_app.js 中引入 hoc

import App, { Container } from 'next/app'
import 'antd/dist/antd.css'
import React from 'react'
import { Provider } from 'react-redux'
import Layout from '../components/Layout'
import initializeStore from '../store/store'
import withRedux from '../lib/with-redux-app'
class MyApp extends App {
  // App組件的getInitialProps比較特殊
  // 能拿到一些額外的參數
  // Component: 被包裹的組件
  static async getInitialProps(ctx) {
    const { Component } = ctx
    let pageProps = {}

    // 拿到Component上定義的getInitialProps
    if (Component.getInitialProps) {
      // 執行拿到返回結果`
      pageProps = await Component.getInitialProps(ctx)
    }

    // 返回給組件
    return {
      pageProps,
    }
  }

  render() {
    const { Component, pageProps, reduxStore } = this.props
    return (
      <Container> <Layout> <Provider store={reduxStore}> {/* 把pageProps解構後傳遞給組件 */} <Component {...pageProps} /> </Provider> </Layout> </Container> ) } } export default withRedux(MyApp) 複製代碼

這樣,咱們就實現了在 next 中集成 redux。

相關文章
相關標籤/搜索