一個將接下來的事件帶到下來的。
內容:
為靜態應用程序設置REDUX非常簡單:必須創建一個提供給所有頁面的單個Redux Store。
但是,涉及Next.js靜態站點生成器或服務器端渲染時,由於服務器上需要另一個商店實例以渲染Redux連接的組件,因此事物開始變得複雜。
此外,在頁面的getInitialProps期間,也可能需要訪問Redux Store 。
這是next-redux-wrapper派上用場的地方:它會自動為您創建商店實例,並確保它們都具有相同的狀態。
此外,它允許在單個頁面級別正確處理諸如App.getInitialProps (使用pages/_app時)之getStaticProps getServerSideProps複雜案例。
無論您想使用該Store ,庫提供了統一的接口。 JS生命週期方法。
在Next.js示例中https://github.com/vercel/next.js/blob/canary/examples/with-redux-thunk/store.js#l23在導航時正在替換。如果更換store ,Redux即使使用記憶的選擇器(來自recompose的createSelector )也將重新渲染組件:https://codesandbox.io/s/redux-store-change-kzs8q,這可能會通過造成所有內容的巨大重新啟動,甚至沒有變化,這可能會影響應用程序的性能。該庫確保store保持不變。
npm install next-redux-wrapper react-redux --save請注意, next-redux-wrapper需要react-redux作為同伴依賴性。
實時示例:https://codesandbox.io/s/next-redux-wrapper-demo-7n2t5。
所有示例均用打字稿編寫。如果您使用的是普通的JavaScript,則僅省略類型聲明。這些示例使用Vanilla Redux,如果您使用的是Redux Toolkit,請參閱專用示例。
Next.js有幾種數據獲取機制,該庫可以附加到其中任何一個。但是首先,您必須編寫一些常見的代碼。
請注意,您的還原器必須具有HYDRATE動作處理程序。 HYDRATE操作處理程序必須適當地核對現有狀態(如果有)之上的水合狀態。此行為是在此庫6版中添加的。稍後我們將討論這一特殊行動。
創建一個名為store.ts的文件:
// store.ts
import { createStore , AnyAction , Store } from 'redux' ;
import { createWrapper , Context , HYDRATE } from 'next-redux-wrapper' ;
export interface State {
tick : string ;
}
// create your reducer
const reducer = ( state : State = { tick : 'init' } , action : AnyAction ) => {
switch ( action . type ) {
case HYDRATE :
// Attention! This will overwrite client state! Real apps should use proper reconciliation.
return { ... state , ... action . payload } ;
case 'TICK' :
return { ... state , tick : action . payload } ;
default :
return state ;
}
} ;
// create a makeStore function
const makeStore = ( context : Context ) => createStore ( reducer ) ;
// export an assembled wrapper
export const wrapper = createWrapper < Store < State > > ( makeStore , { debug : true } ) ; // store.js
import { createStore } from 'redux' ;
import { createWrapper , HYDRATE } from 'next-redux-wrapper' ;
// create your reducer
const reducer = ( state = { tick : 'init' } , action ) => {
switch ( action . type ) {
case HYDRATE :
return { ... state , ... action . payload } ;
case 'TICK' :
return { ... state , tick : action . payload } ;
default :
return state ;
}
} ;
// create a makeStore function
const makeStore = context => createStore ( reducer ) ;
// export an assembled wrapper
export const wrapper = createWrapper ( makeStore , { debug : true } ) ; wrapper.useWrappedStore強烈建議使用pages/_app一次包裝所有頁面,否則由於潛在的種族條件,您可能會Cannot update component while rendering another component :
import React , { FC } from 'react' ;
import { Provider } from 'react-redux' ;
import { AppProps } from 'next/app' ;
import { wrapper } from '../components/store' ;
const MyApp : FC < AppProps > = ( { Component , ... rest } ) => {
const { store , props } = wrapper . useWrappedStore ( rest ) ;
return (
< Provider store = { store } >
< Component { ... props . pageProps } />
</ Provider >
) ;
} ;您也可以使用基於類的組件來使用Legacy HOC,而不是使用wrapper.useWrappedStore 。
class MyApp extends App時提供通用的getInitialProps該應用程序將由包裝器拾取,因此您不得擴展App ,因為您將被選為自動靜態優化:https:///err.sh/next.js/opt-ut-opt-ut--aut-auto-static-static-patic-optimization。只需像上面的示例中導出常規功能組件。
import React from 'react' ;
import { wrapper } from '../components/store' ;
import { AppProps } from 'next/app' ;
class MyApp extends React . Component < AppProps > {
render ( ) {
const { Component , pageProps } = this . props ;
return < Component { ... pageProps } /> ;
}
}
export default wrapper . withRedux ( MyApp ) ; 每次用戶打開具有getStaticProps或getServerSideProps的頁面時, HYDRATE操作將被派發。這可能在初始頁面加載和常規頁面導航期間發生。此操作的payload將在靜態生成或服務器端渲染時包含state ,因此您的還原器必須正確將其與現有客戶端狀態合併。
最簡單的方法是使用服務器和客戶端狀態分離。
另一種方法是使用https://github.com/benjamine/jsondiffpatch分析差異並正確應用:
import { HYDRATE } from 'next-redux-wrapper' ;
// create your reducer
const reducer = ( state = { tick : 'init' } , action ) => {
switch ( action . type ) {
case HYDRATE :
const stateDiff = diff ( state , action . payload ) as any ;
const wasBumpedOnClient = stateDiff ?. page ?. [ 0 ] ?. endsWith ( 'X' ) ; // or any other criteria
return {
... state ,
... action . payload ,
page : wasBumpedOnClient ? state . page : action . payload . page , // keep existing state or use hydrated
} ;
case 'TICK' :
return { ... state , tick : action . payload } ;
default :
return state ;
}
} ;或像這樣(來自Next.js repo中的with-redux-wrapper示例):
const reducer = ( state , action ) => {
if ( action . type === HYDRATE ) {
const nextState = {
... state , // use previous state
... action . payload , // apply delta from hydration
} ;
if ( state . count ) nextState . count = state . count ; // preserve count value on client side navigation
return nextState ;
} else {
return combinedReducer ( state , action ) ;
}
} ; createWrapper函數接受makeStore作為其第一個參數。 makeStore函數應每次調用新的Redux Store實例返回新的Redux Store實例。這裡不需要記憶,它是在包裝器內部自動完成的。
createWrapper還可以選擇接受配置對像作為第二個參數:
debug (可選,布爾值):啟用調試記錄serializeState和deserializeState :用於序列化和絕望狀態的自定義函數,請參閱自定義序列化和避免化。當調用makeStore時,將為next.js上下文提供,該上下文可以是NextPageContext或AppContext或getStaticProps或getServerSideProps上下文,具體取決於您要包裝的生命週期功能。
其中一些上下文( getServerSideProps總是, NextPageContext , AppContext有時如果在服務器上呈現頁面)可以具有請求和響應相關的屬性:
req ( IncomingMessage )res ( ServerResponse )儘管可以在makeStore中創建服務器或客戶端特定邏輯,但我強烈建議它們沒有不同的行為。這可能會導致錯誤和校驗不匹配,從而破壞服務器渲染的全部目的。
本節介紹如何附加到GetStaticProps生命週期功能。
讓我們在pages/pageName.tsx中創建一個頁面:
import React from 'react' ;
import { NextPage } from 'next' ;
import { useSelector } from 'react-redux' ;
import { wrapper , State } from '../store' ;
export const getStaticProps = wrapper . getStaticProps ( store => ( { preview } ) => {
console . log ( '2. Page.getStaticProps uses the store to dispatch things' ) ;
store . dispatch ( {
type : 'TICK' ,
payload : 'was set in other page ' + preview ,
} ) ;
} ) ;
// you can also use `connect()` instead of hooks
const Page : NextPage = ( ) => {
const { tick } = useSelector < State , State > ( state => state ) ;
return < div > { tick } < / div>;
} ;
export default Page ; import React from 'react' ;
import { useSelector } from 'react-redux' ;
import { wrapper } from '../store' ;
export const getStaticProps = wrapper . getStaticProps ( store => ( { preview } ) => {
console . log ( '2. Page.getStaticProps uses the store to dispatch things' ) ;
store . dispatch ( {
type : 'TICK' ,
payload : 'was set in other page ' + preview ,
} ) ;
} ) ;
// you can also use `connect()` instead of hooks
const Page = ( ) => {
const { tick } = useSelector ( state => state ) ;
return < div > { tick } </ div > ;
} ;
export default Page ;getStaticProps的頁面時, HYDRATE操作都會被派發。此操作的payload將在靜態生成時包含state ,它將沒有客戶端狀態,因此您的還原器必須將其與現有客戶狀態正確合併。在服務器和客戶端狀態分離中有關此信息的更多信息。
儘管您可以包裝單個頁面(而不是包裝pages/_app ),但不建議您使用使用部分中的最後一段。
本節介紹如何連接到getServersideProps生命週期功能。
讓我們在pages/pageName.tsx中創建一個頁面:
import React from 'react' ;
import { NextPage } from 'next' ;
import { connect } from 'react-redux' ;
import { wrapper , State } from '../store' ;
export const getServerSideProps = wrapper . getServerSideProps ( store => ( { req , res , ... etc } ) => {
console . log ( '2. Page.getServerSideProps uses the store to dispatch things' ) ;
store . dispatch ( { type : 'TICK' , payload : 'was set in other page' } ) ;
} ) ;
// Page itself is not connected to Redux Store, it has to render Provider to allow child components to connect to Redux Store
const Page : NextPage < State > = ( { tick } ) => < div > { tick } < / div>;
// you can also use Redux `useSelector` and other hooks instead of `connect()`
export default connect ( ( state : State ) => state ) ( Page ) ; import React from 'react' ;
import { connect } from 'react-redux' ;
import { wrapper } from '../store' ;
export const getServerSideProps = wrapper . getServerSideProps ( store => ( { req , res , ... etc } ) => {
console . log ( '2. Page.getServerSideProps uses the store to dispatch things' ) ;
store . dispatch ( { type : 'TICK' , payload : 'was set in other page' } ) ;
} ) ;
// Page itself is not connected to Redux Store, it has to render Provider to allow child components to connect to Redux Store
const Page = ( { tick } ) => < div > { tick } </ div > ;
// you can also use Redux `useSelector` and other hooks instead of `connect()`
export default connect ( state => state ) ( Page ) ;getServerSideProps的頁面時, HYDRATE操作都會被派發。此操作的payload將在服務器端渲染時包含state ,它將沒有客戶端狀態,因此您的還原器必須將其與現有客戶端狀態合併。在服務器和客戶端狀態分離中有關此信息的更多信息。
儘管您可以包裝單個頁面(而不是包裝pages/_app ),但不建議您使用使用部分中的最後一段。
Page.getInitialProps import React , { Component } from 'react' ;
import { NextPage } from 'next' ;
import { wrapper , State } from '../store' ;
// you can also use `connect()` instead of hooks
const Page : NextPage = ( ) => {
const { tick } = useSelector < State , State > ( state => state ) ;
return < div > { tick } < / div>;
} ;
Page . getInitialProps = wrapper . getInitialPageProps ( store => ( { pathname , req , res } ) => {
console . log ( '2. Page.getInitialProps uses the store to dispatch things' ) ;
store . dispatch ( {
type : 'TICK' ,
payload : 'was set in error page ' + pathname ,
} ) ;
} ) ;
export default Page ; import React , { Component } from 'react' ;
import { wrapper } from '../store' ;
// you can also use `connect()` instead of hooks
const Page = ( ) => {
const { tick } = useSelector ( state => state ) ;
return < div > { tick } </ div > ;
} ;
Page . getInitialProps = wrapper . getInitialPageProps ( store => ( { pathname , req , res } ) => {
console . log ( '2. Page.getInitialProps uses the store to dispatch things' ) ;
store . dispatch ( {
type : 'TICK' ,
payload : 'was set in error page ' + pathname ,
} ) ;
} ) ;
export default Page ;請記住,如果getInitialProps在客戶端調用getInitialProps,則可能無法使用req和res 。
無狀態功能組件也可以用類替換:
class Page extends Component {
public static getInitialProps = wrapper . getInitialPageProps ( store => ( ) => ( { ... } ) ) ;
render ( ) {
// stuff
}
}
export default Page ;儘管您可以包裝單個頁面(而不是包裝pages/_app ),但不建議您使用使用部分中的最後一段。
pages/_app派遣操作。但是此模式與Next.js 9的自動部分靜態導出功能不兼容,請參見下面的說明。
包裝器也可以連接到您的_app組件(位於/pages )。所有其他組件都可以使用react-redux的connect函數。
// pages/_app.tsx
import React from 'react' ;
import App , { AppInitialProps } from 'next/app' ;
import { wrapper } from '../components/store' ;
import { State } from '../components/reducer' ;
// Since you'll be passing more stuff to Page
declare module 'next/dist/next-server/lib/utils' {
export interface NextPageContext {
store : Store < State > ;
}
}
class MyApp extends App < AppInitialProps > {
public static getInitialProps = wrapper . getInitialAppProps ( store => async context => {
store . dispatch ( { type : 'TOE' , payload : 'was set in _app' } ) ;
return {
pageProps : {
// https://nextjs.org/docs/advanced-features/custom-app#caveats
... ( await App . getInitialProps ( context ) ) . pageProps ,
// Some custom thing for all pages
pathname : ctx . pathname ,
} ,
} ;
} ) ;
public render ( ) {
const { Component , pageProps } = this . props ;
return < Component { ... pageProps } /> ;
}
}
export default wrapper . withRedux ( MyApp ) ; // pages/_app.tsx
import React from 'react' ;
import App from 'next/app' ;
import { wrapper } from '../components/store' ;
class MyApp extends App {
static getInitialProps = wrapper . getInitialAppProps ( store => async context => {
store . dispatch ( { type : 'TOE' , payload : 'was set in _app' } ) ;
return {
pageProps : {
// https://nextjs.org/docs/advanced-features/custom-app#caveats
... ( await App . getInitialProps ( context ) ) . pageProps ,
// Some custom thing for all pages
pathname : ctx . pathname ,
} ,
} ;
} ) ;
render ( ) {
const { Component , pageProps } = this . props ;
return < Component { ... pageProps } /> ;
}
}
export default wrapper . withRedux ( MyApp ) ;然後所有頁面都可以簡單地連接(示例考慮頁面組件):
// pages/xxx.tsx
import React from 'react' ;
import { NextPage } from 'next' ;
import { connect } from 'react-redux' ;
import { NextPageContext } from 'next' ;
import { State } from '../store' ;
const Page : NextPage < State > = ( { foo , custom } ) => (
< div >
< div > Prop from Redux { foo } </ div >
< div > Prop from getInitialProps { custom } </ div >
</ div >
) ;
// No need to wrap pages if App was wrapped
Page . getInitialProps = ( { store , pathname , query } : NextPageContext ) => {
store . dispatch ( { type : 'FOO' , payload : 'foo' } ) ; // The component can read from the store's state when rendered
return { custom : 'custom' } ; // You can pass some custom props to the component from here
} ;
export default connect ( ( state : State ) => state ) ( Page ) ; // pages/xxx.js
import React from 'react' ;
import { connect } from 'react-redux' ;
const Page = ( { foo , custom } ) => (
< div >
< div > Prop from Redux { foo } </ div >
< div > Prop from getInitialProps { custom } </ div >
</ div >
) ;
// No need to wrap pages if App was wrapped
Page . getInitialProps = ( { store , pathname , query } ) => {
store . dispatch ( { type : 'FOO' , payload : 'foo' } ) ; // The component can read from the store's state when rendered
return { custom : 'custom' } ; // You can pass some custom props to the component from here
} ;
export default connect ( state => state ) ( Page ) ; getServerSideProps或在頁面上getStaticProps您還可以在頁面級別使用getServerSideProps或getStaticProps ,在這種情況下, HYDRATE操作將兩次派遣:使用After App.getInitialProps進行狀態,然後在After getServerSideProps或GetStaticProps中使用State: getStaticProps :
getServerSideProps ,則將store在App.getInitialProps之後,並將其狀態存儲在getServerSideProps中,因此第二種HYDRATE將從兩者中具有完整狀態getStaticProps ,則將store在getStaticProps中,將在編譯時執行,並且不會從App.getInitialProps中執行狀態,因為它們是在不同的上下文中執行的,並且狀態無法共享。 App.getInitialProps和第二個在getStaticProps之後的狀態下,首先要HYDRATE動作狀態(即使它是在時間較早執行的)。確保正確合併的最簡單方法是從action.payload中刪除初始值:
const reducer = ( state : State = { app : 'init' , page : 'init' } , action : AnyAction ) => {
switch ( action . type ) {
case HYDRATE :
if ( action . payload . app === 'init' ) delete action . payload . app ;
if ( action . payload . page === 'init' ) delete action . payload . page ;
return { ... state , ... action . payload } ;
case 'APP' :
return { ... state , app : action . payload } ;
case 'PAGE' :
return { ... state , page : action . payload } ;
default :
return state ;
}
} ; const reducer = ( state = { app : 'init' , page : 'init' } , action ) => {
switch ( action . type ) {
case HYDRATE :
if ( action . payload . app === 'init' ) delete action . payload . app ;
if ( action . payload . page === 'init' ) delete action . payload . page ;
return { ... state , ... action . payload } ;
case 'APP' :
return { ... state , app : action . payload } ;
case 'PAGE' :
return { ... state , page : action . payload } ;
default :
return state ;
}
} ;假設頁面僅派遣PAGE操作和應用程序APP ,這使狀態合併安全。
有關服務器和客戶端狀態分離的更多信息。
使用next-redux-wrapper (“包裝器”),以下情況發生在請求中:
階段1: getInitialProps / getStaticProps / getServerSideProps
makeStore )。在此過程中,它還提供了Request和Response像作為makeStore的選項。_app的getInitialProps功能,並通過先前創建的商店。_app的getInitialProps方法中返回的道具。getXXXProps函數,並傳遞先前創建的商店。getXXXProps方法中返回。階段2:SSR
makeStore創建了一家新商店HYDRATE措施與上一個商店的狀態派遣為payload_app或page組件。階段3:客戶
HYDRATE合作用派遣為payload_app或page組件。注意:客戶端的狀態不會在請求中持續存在(即階段1總是以空狀態開始)。因此,它是重置頁面重新加載的重置。如果要在請求之間持久持續狀態,請考慮使用Redux持續存在。
由於@reduxjs/toolkit版本7.0版本的一流支持。
完整示例:https://github.com/kirill-konshin/next-redux-wrapper/blob/master/packages/demo-redux-toolkit。
import { configureStore , createSlice , ThunkAction } from '@reduxjs/toolkit' ;
import { Action } from 'redux' ;
import { createWrapper , HYDRATE } from 'next-redux-wrapper' ;
export const subjectSlice = createSlice ( {
name : 'subject' ,
initialState : { } as any ,
reducers : {
setEnt ( state , action ) {
return action . payload ;
} ,
} ,
extraReducers : {
[ HYDRATE ] : ( state , action ) => {
console . log ( 'HYDRATE' , state , action . payload ) ;
return {
... state ,
... action . payload . subject ,
} ;
} ,
} ,
} ) ;
const makeStore = ( ) =>
configureStore ( {
reducer : {
[ subjectSlice . name ] : subjectSlice . reducer ,
} ,
devTools : true ,
} ) ;
export type AppStore = ReturnType < typeof makeStore > ;
export type AppState = ReturnType < AppStore [ 'getState' ] > ;
export type AppDispatch = AppStore [ 'dispatch' ] ;
export type AppThunk < ReturnType = void > = ThunkAction < ReturnType , AppState , unknown , Action > ;
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = ( ) => useDispatch < AppDispatch > ( ) ;
export const useAppSelector : TypedUseSelectorHook < RootState > = useSelector ;
export const fetchSubject =
( id : any ) : AppThunk =>
async dispatch => {
const timeoutPromise = ( timeout : number ) => new Promise ( resolve => setTimeout ( resolve , timeout ) ) ;
await timeoutPromise ( 200 ) ;
dispatch (
subjectSlice . actions . setEnt ( {
[ id ] : {
id ,
name : `Subject ${ id } ` ,
} ,
} ) ,
) ;
} ;
export const wrapper = createWrapper < AppStore > ( makeStore ) ;
export const selectSubject = ( id : any ) => ( state : AppState ) => state ?. [ subjectSlice . name ] ?. [ id ] ;建議出口打字State和ThunkAction :
export type AppStore = ReturnType < typeof makeStore > ;
export type AppState = ReturnType < AppStore [ 'getState' ] > ;
export type AppThunk < ReturnType = void > = ThunkAction < ReturnType , AppState , unknown , Action > ;每次用戶打開具有getStaticProps或getServerSideProps的頁面時, HYDRATE操作將被派發。此操作的payload將在靜態生成或服務器端渲染時包含state ,因此您的還原器必須正確將其與現有客戶端狀態合併。
確保沒有意外覆蓋的最簡單,最穩定的方法是確保您的還原器將客戶端和服務器端操作應用於您的州的不同替代,而它們永遠不會發生衝突:
export interface State {
server : any ;
client : any ;
}
const reducer = ( state : State = { tick : 'init' } , action : AnyAction ) => {
switch ( action . type ) {
case HYDRATE :
return {
... state ,
server : {
... state . server ,
... action . payload . server ,
} ,
} ;
case 'SERVER_ACTION' :
return {
... state ,
server : {
... state . server ,
tick : action . payload ,
} ,
} ;
case 'CLIENT_ACTION' :
return {
... state ,
client : {
... state . client ,
tick : action . payload ,
} ,
} ;
default :
return state ;
}
} ; const reducer = ( state = { tick : 'init' } , action ) => {
switch ( action . type ) {
case HYDRATE :
return {
... state ,
server : {
... state . server ,
... action . payload . server ,
} ,
} ;
case 'SERVER_ACTION' :
return {
... state ,
server : {
... state . server ,
tick : action . payload ,
} ,
} ;
case 'CLIENT_ACTION' :
return {
... state ,
client : {
... state . client ,
tick : action . payload ,
} ,
} ;
default :
return state ;
}
} ;如果您更喜歡州的某些(最好是小)部分的同構方法,則可以使用Next-Redux-cookie-wrapper在服務器渲染的頁面上在客戶端和服務器之間共享它們,這是Next-redux-wrapper的擴展。在這種情況下,對於選定的替換,服務器會知道客戶端的狀態(除非在getStaticProps中),並且無需將服務器和客戶端狀態分開。
另外,您可以使用https://github.com/benjamine/jsondiffpatch之類的庫來分析diff並正確應用它。
我不建議在pages/_document.js中使用withRedux ,next.js不會提供可靠的方法來確定序列時何時渲染組件。因此,根據Next.js的建議,最好將僅在pages/_document中使用數據不可能的內容。
錯誤頁面也可以與其他任何頁面相同。
過渡到錯誤頁面( pages/_error.js模板)會導致pages/_app.js應用,但它始終是一個完整的頁面過渡(不是HTML5 PushState),因此客戶端將使用服務器中的狀態從SCRATCH創建商店。因此,除非您將商店持續到客戶端上,否則將忽略由此產生的先前客戶端狀態。
您可以使用https://github.com/reduxjs/redux-thunk來調度異步操作:
function someAsyncAction ( id ) {
return async function ( dispatch , getState ) {
return someApiCall ( id ) . then ( res => {
dispatch ( {
type : 'FOO' ,
payload : res ,
} ) ;
} ) ;
} ;
}
// usage
await store . dispatch ( someAsyncAction ( ) ) ;您也可以安裝https://github.com/pburtchaell/redux-promise-middleware,以便將承諾作為異步操作派遣。遵循庫的安裝指南,然後您可以這樣處理:
function someAsyncAction ( ) {
return {
type : 'FOO' ,
payload : new Promise ( resolve => resolve ( 'foo' ) ) ,
} ;
}
// usage
await store . dispatch ( someAsyncAction ( ) ) ;如果您在狀態中存儲複雜類型,例如immutable.js或json對象,則自定義序列化和挑選的處理程序可能會方便地將服務器上的redux狀態序列化並在客戶端上再次進行序列化。為此,請將serializeState和deserializeState作為withRedux配置選項提供。
原因是狀態快照是通過網絡從服務器轉移到客戶端作為普通對象的。
使用json-immutable的不變態的自定義序列化的示例:
const { serialize , deserialize } = require ( 'json-immutable' ) ;
createWrapper ( {
serializeState : state => serialize ( state ) ,
deserializeState : state => deserialize ( state ) ,
} ) ;使用不變的js同樣的事情:
const { fromJS } = require ( 'immutable' ) ;
createWrapper ( {
serializeState : state => state . toJS ( ) ,
deserializeState : state => fromJS ( state ) ,
} ) ;[請注意,此方法可能不安全 - 確保您正確處理異步傳奇。 makeStore您不小心,比賽條件很容易發生。具體而言,需要在此功能內部初始化redux-saga ,而不是在其外部進行初始化。 (我一開始就這樣做了,並且Before running a Saga, you must mount the Saga middleware on the Store using applyMiddleware )。這就是人們如何實現這一目標。這只是從文檔開頭的設置示例稍微修改的。請記住,此設置將您選擇自動靜態優化:https://err.sh/next.js/opt-ut-auto-static-optimization。
像往常一樣創建您的root傳奇,然後實現商店創建者:
import { createStore , applyMiddleware , Store } from 'redux' ;
import { createWrapper , Context } from 'next-redux-wrapper' ;
import createSagaMiddleware , { Task } from 'redux-saga' ;
import reducer , { State } from './reducer' ;
import rootSaga from './saga' ;
export interface SagaStore extends Store {
sagaTask ?: Task ;
}
export const makeStore = ( context : Context ) => {
// 1: Create the middleware
const sagaMiddleware = createSagaMiddleware ( ) ;
// 2: Add an extra parameter for applying middleware:
const store = createStore ( reducer , applyMiddleware ( sagaMiddleware ) ) ;
// 3: Run your sagas on server
( store as SagaStore ) . sagaTask = sagaMiddleware . run ( rootSaga ) ;
// 4: now return the store:
return store ;
} ;
export const wrapper = createWrapper < Store < State > > ( makeStore , { debug : true } ) ; import { createStore , applyMiddleware } from 'redux' ;
import { createWrapper } from 'next-redux-wrapper' ;
import createSagaMiddleware from 'redux-saga' ;
import reducer from './reducer' ;
import rootSaga from './saga' ;
export const makeStore = context => {
// 1: Create the middleware
const sagaMiddleware = createSagaMiddleware ( ) ;
// 2: Add an extra parameter for applying middleware:
const store = createStore ( reducer , applyMiddleware ( sagaMiddleware ) ) ;
// 3: Run your sagas on server
store . sagaTask = sagaMiddleware . run ( rootSaga ) ;
// 4: now return the store:
return store ;
} ;
export const wrapper = createWrapper ( makeStore , { debug : true } ) ; pages/_app然後在pages/_app等待中停止傳奇,並在執行服務器上執行時等待它完成:
import React from 'react' ;
import App , { AppInitialProps } from 'next/app' ;
import { END } from 'redux-saga' ;
import { SagaStore , wrapper } from '../components/store' ;
class WrappedApp extends App < AppInitialProps > {
public static getInitialProps = wrapper . getInitialAppProps ( store => async context => {
// 1. Wait for all page actions to dispatch
const pageProps = {
// https://nextjs.org/docs/advanced-features/custom-app#caveats
... ( await App . getInitialProps ( context ) ) . pageProps ,
} ;
// 2. Stop the saga if on server
if ( context . ctx . req ) {
store . dispatch ( END ) ;
await ( store as SagaStore ) . sagaTask . toPromise ( ) ;
}
// 3. Return props
return { pageProps } ;
} ) ;
public render ( ) {
const { Component , pageProps } = this . props ;
return < Component { ... pageProps } /> ;
}
}
export default wrapper . withRedux ( WrappedApp ) ; import React from 'react' ;
import App from 'next/app' ;
import { END } from 'redux-saga' ;
import { SagaStore , wrapper } from '../components/store' ;
class WrappedApp extends App {
static getInitialProps = wrapper . getInitialAppProps ( store => async context => {
// 1. Wait for all page actions to dispatch
const pageProps = {
// https://nextjs.org/docs/advanced-features/custom-app#caveats
... ( await App . getInitialProps ( context ) ) . pageProps ,
} ;
// 2. Stop the saga if on server
if ( context . ctx . req ) {
store . dispatch ( END ) ;
await store . sagaTask . toPromise ( ) ;
}
// 3. Return props
return { pageProps } ;
} ) ;
public render ( ) {
const { Component , pageProps } = this . props ;
return < Component { ... pageProps } /> ;
}
}
export default wrapper . withRedux ( WrappedApp ) ; getServerSideProps或getStaticProps為了與getServerSideProps或getStaticProps一起使用,您需要在每個頁面的處理程序中await Sagas:
export const getServerSideProps = ReduxWrapper . getServerSideProps ( async ( { store , req , res , ... etc } ) => {
// regular stuff
store . dispatch ( ApplicationSlice . actions . updateConfiguration ( ) ) ;
// end the saga
store . dispatch ( END ) ;
await store . sagaTask . toPromise ( ) ;
} ) ; _app內部使用無getInitialProps的使用情況如果您不想在下一個應用程序中選擇自動預先渲染,則可以像官方Next.js“ With Redux Saga”示例這樣的每個頁面管理服務器稱為Sagas。如果您確實使用此選項,請確保您在任何下一步中等待任何和所有sagas。如果您錯過了其中一個頁面之一,最終將寄給客戶的狀態不一致。因此,我們考慮在_app中等待自動更安全,但是顯然,主要缺點是從自動靜態導出中選擇。
如果您只需要堅持州的一小部分,那麼Next-redux-cookie-wrapper可能是支持SSR的Redux持續存在的簡單替代方法。
樣板:https://github.com/fazlulkarimweb/with-next-next-redux-wrapper-redux-persist
老實說,我認為不需要放置持久性門,因為服務器已經可以通過某種狀態發送一些HTML,因此最好立即展示它,然後等待REHYDRATE動作恰好顯示出來自Persistence Storage的其他Delta。這就是為什麼我們首先使用服務器端渲染的原因。
但是,對於那些實際上想在重新補充過程中阻止UI的人來說,這是解決方案(雖然仍然是黑客):
// lib/redux.js
import logger from 'redux-logger' ;
import { applyMiddleware , createStore } from 'redux' ;
const SET_CLIENT_STATE = 'SET_CLIENT_STATE' ;
export const reducer = ( state , { type , payload } ) => {
// Usual stuff with HYDRATE handler
if ( type === SET_CLIENT_STATE ) {
return {
... state ,
fromClient : payload ,
} ;
}
return state ;
} ;
const makeConfiguredStore = reducer => createStore ( reducer , undefined , applyMiddleware ( logger ) ) ;
const makeStore = ( ) => {
const isServer = typeof window === 'undefined' ;
if ( isServer ) {
return makeConfiguredStore ( reducer ) ;
} else {
// we need it only on client side
const { persistStore , persistReducer } = require ( 'redux-persist' ) ;
const storage = require ( 'redux-persist/lib/storage' ) . default ;
const persistConfig = {
key : 'nextjs' ,
whitelist : [ 'fromClient' ] , // make sure it does not clash with server keys
storage ,
} ;
const persistedReducer = persistReducer ( persistConfig , reducer ) ;
const store = makeConfiguredStore ( persistedReducer ) ;
store . __persistor = persistStore ( store ) ; // Nasty hack
return store ;
}
} ;
export const wrapper = createWrapper ( makeStore ) ;
export const setClientState = clientState => ( {
type : SET_CLIENT_STATE ,
payload : clientState ,
} ) ;然後在Next.js _app頁面中您可以使用裸上下文訪問來獲取商店(https://react-readux.js.s.org/api/provider#props):
// pages/_app.tsx
import React from 'react' ;
import App from 'next/app' ;
import { ReactReduxContext } from 'react-redux' ;
import { wrapper } from './lib/redux' ;
import { PersistGate } from 'redux-persist/integration/react' ;
export default wrapper . withRedux (
class MyApp extends App {
render ( ) {
const { Component , pageProps } = this . props ;
return (
< ReactReduxContext . Consumer >
{ ( { store } ) => (
< PersistGate persistor = { store . __persistor } loading = { < div > Loading </ div > } >
< Component { ... pageProps } />
</ PersistGate >
) }
</ ReactReduxContext . Consumer >
) ;
}
} ,
) ;或使用鉤子:
// pages/_app.tsx
import React from 'react' ;
import App from 'next/app' ;
import { useStore } from 'react-redux' ;
import { wrapper } from './lib/redux' ;
import { PersistGate } from 'redux-persist/integration/react' ;
export default wrapper . withRedux ( ( { Component , pageProps } ) => {
const store = useStore ( ) ;
return (
< PersistGate persistor = { store . __persistor } loading = { < div > Loading </ div > } >
< Component { ... pageProps } />
</ PersistGate >
) ;
} ) ;然後在Next.js頁面:
// pages/index.js
import React from 'react' ;
import { connect } from 'react-redux' ;
export default connect ( state => state , { setClientState } ) ( ( { fromServer , fromClient , setClientState } ) => (
< div >
< div > fromServer: { fromServer } </ div >
< div > fromClient: { fromClient } </ div >
< div >
< button onClick = { e => setClientState ( 'bar' ) } > Set Client State </ button >
</ div >
</ div >
) ) ; createWrapper的簽名已更改:而不是createWrapper<State>您應該使用createWrapper<Store<State>> ,所有類型都將自動從Store中推斷出來。
GetServerSidePropsContext和GetStaticPropsContext不再從next-redux-wrapper導出,您應該直接使用GetServerSideProps , GetServerSidePropsContext , GetStaticProps和GetStaticPropsContext直接從next 。
將所有簽名更改為({store, req, res, ...}) => { ... }所有簽名均已更改為store => ({req, res, ...}) => { ... } ,以保持Next.js內部功能免費修改和更好的鍵入支持。
在版本7.x中,您必須用適當的包裝器手動包裝所有getInitialProps : wrapper.getInitialPageProps和wrapper.getInitialAppProps 。
window.next_redux_wrapper_store已被刪除,因為它引起了熱重新加載的問題
版本6中事物包裹的方式的重大變化。
默認的用withRedux進行了標記,您應該創建一個包裝器const wrapper = createWrapper(makeStore, {debug: true}) ,然後使用wrapper.withRedux(MyApp) 。
您的makeStore功能不再獲得initialState ,它僅接收上下文: makeStore(context: Context) 。上下文可以是NextPageContext或AppContext或getStaticProps或getServerSideProps上下文,具體取決於您將包裝的生命週期函數。取而代之的是,您需要處理還原器中的HYDRATE合作用。此操作的payload將在靜態生成或服務器端渲染時包含state ,因此您的還原器必須正確將其與現有客戶端狀態合併。
App不應再將其孩子與Provider包裹,現在可以在內部進行。
isServer不會在context / props中傳遞,使用您自己的功能或簡單的檢查const isServer = typeof window === 'undefined'或!!context.req或!!context.ctx.req 。
store不會傳遞給包裝的組件道具。
WrappedAppProps已重命名為WrapperProps 。
如果您的項目正在使用Next.js 5和Next Redux包裝器1.x這些說明將幫助您升級到2.x。
升級Next.js和包裝器
$ npm install next@6 --save-dev
$ npm install next-redux-wrapper@latest --save import withRedux from "next-redux-wrapper";和withRedux(...)(WrappedComponent)在您的所有頁面中均使用純React React Redux connect Hoc:
import { connect } from "react-redux" ;
export default connect ( ... ) ( WrappedComponent ) ;您可能還必須將基於包裝器對象的配置重新格式化為簡單的React Redux配置。
使用以下最小代碼創建pages/_app.js文件:
// pages/_app.js
import React from 'react'
import { Provider } from 'react-redux' ;
import App from 'next/app' ;
import { wrapper } from '../store' ;
class MyApp extends App {
static async getInitialProps = ( context ) => ( {
pageProps : {
// https://nextjs.org/docs/advanced-features/custom-app#caveats
... ( await App . getInitialProps ( context ) ) . pageProps ,
}
} ) ;
render ( ) {
const { Component , pageProps } = this . props ;
return (
< Component { ... pageProps } />
) ;
}
}
export default wrapper . withRedux ( MyApp ) ;請遵循Next.js 6的所有組件升級說明( props.router而不是props.url等)
就是這樣。您的項目現在應該像以前一樣工作。