前言
本文參考了慕課網jokcy老師的React16.8+Next.js+Koa2開發Github全棧項目,也算是做個筆記吧。
源碼位址
github.com/sl1673495/n…
介紹
Next.js 是一個輕量級的 React 服務端渲染應用架構。
官網:nextjs.org
中文官網:nextjs.frontendx.cn
當使用 React 開發系統的時候,常常需要配置很多繁瑣的參數,如 Webpack 配置、Router 配置和伺服器配置等。如果需要做 SEO,要考慮的事情就更多了,怎麼讓服務端渲染和用戶端渲染保持一緻是一件很麻煩的事情,需要引入很多第三方庫。針對這些問題,Next.js提供了一個很好的解決方案,使開發人員可以将精力放在業務上,從繁瑣的配置中解放出來。下面我們一起來從零開始搭建一個完善的next項目。
項目的初始化
首先安裝 create-next-app 腳手架
npm i -g create-next-app
複制
然後利用腳手架建立 next 項目
create-next-app next-github
cd next-github
npm run dev
複制
可以看到 pages 檔案夾下的 index.js
生成的目錄結構很簡單,我們稍微加幾個内容
├── README.md
├── components // 非頁面級共用元件
│ └── nav.js
├── package-lock.json
├── package.json
├── pages // 頁面級元件 會被解析成路由
│ └── index.js
├── lib // 一些通用的js
├── static // 靜态資源
│ └── favicon.ico
複制
啟動項目之後,預設端口啟動在 3000 端口,打開 localhost:3000 後,預設通路的就是 index.js 裡的内容
把 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.req
和
ctx.res
是 node 原生提供的
之是以要傳遞
ctx.req
和
ctx.res
,是因為 next 并不隻是相容 koa 這個架構,是以需要傳遞 node 原生提供的
req
和
res
內建 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
元件進行跳轉
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
子產品進行跳轉
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 在用戶端和服務端同步的問題,

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。