一个将接下来的事件带到下来的。
内容:
为静态应用程序设置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等)
就是这样。您的项目现在应该像以前一样工作。