Hoc yang membawa selanjutnya.js dan redux bersama -sama
Isi:
Menyiapkan Redux untuk aplikasi statis agak sederhana: Toko redux tunggal harus dibuat yang disediakan untuk semua halaman.
Namun, ketika generator situs statis atau rendering sisi server terlibat, namun, hal-hal mulai menjadi rumit karena instance toko lain diperlukan di server untuk membuat komponen yang terhubung dengan Redux.
Selain itu, akses ke Store redux juga mungkin diperlukan selama getInitialProps halaman.
Di sinilah next-redux-wrapper menjadi berguna: secara otomatis membuat instance toko untuk Anda dan memastikan mereka semua memiliki keadaan yang sama.
Selain itu memungkinkan untuk menangani kasus -kasus kompleks seperti App.getInitialProps (saat menggunakan pages/_app ) bersama -sama dengan getStaticProps atau getServerSideProps di tingkat halaman individual.
Perpustakaan menyediakan antarmuka yang seragam tidak peduli di mana metode siklus hidup selanjutnya.js Anda ingin menggunakan Store .
Dalam contoh next.js https://github.com/vercel/next.js/blob/canary/examples/with-redux-thunk/store.js#l23 toko sedang diganti pada navigasi. Redux akan membuat ulang komponen bahkan dengan selektor yang dimoized ( createSelector dari recompose ) jika store diganti: https://codesandbox.io/s/redux-store-change-kzs8q, yang dapat mempengaruhi kinerja aplikasi dengan menyebabkan rene ulang yang besar, bahkan apa yang tidak berubah. Perpustakaan ini memastikan store tetap sama.
npm install next-redux-wrapper react-redux --save Perhatikan bahwa next-redux-wrapper membutuhkan react-redux sebagai ketergantungan sebaya.
Contoh Langsung: https://codesandbox.io/s/next-redux-wrapper-demo-7n2t5.
Semua contoh ditulis dalam naskah. Jika Anda menggunakan JavaScript biasa, hilangkan deklarasi jenis. Contoh -contoh ini menggunakan vanilla redux, jika Anda menggunakan redux toolkit, silakan merujuk ke contoh khusus.
Next.js memiliki beberapa mekanisme pengambilan data, perpustakaan ini dapat melampirkannya. Tetapi pertama -tama Anda harus menulis beberapa kode umum.
Harap dicatat bahwa peredam Anda harus memiliki penangan aksi HYDRATE . HYDRATE Action Handler harus merekonsiliasi dengan benar keadaan terhidrasi di atas keadaan yang ada (jika ada). Perilaku ini ditambahkan dalam versi 6 perpustakaan ini. Kami akan membicarakan tindakan khusus ini nanti.
Buat file bernama 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 Sangat disarankan untuk menggunakan pages/_app untuk membungkus semua halaman sekaligus, jika tidak karena kondisi balapan potensial yang mungkin Anda dapatkan 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 >
) ;
} ; Alih-alih wrapper.useWrappedStore Anda juga dapat menggunakan Legacy Hoc, yang dapat bekerja dengan komponen berbasis kelas.
getInitialProps generik saat menggunakan class MyApp extends App yang akan diambil dengan pembungkus, jadi Anda tidak boleh memperpanjang App karena Anda akan memilih keluar dari optimasi statis otomatis: https://err.sh/next.js/opt-out-auto-statis-optimisasi. Cukup ekspor komponen fungsional reguler seperti pada contoh di atas.
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 ) ; Setiap kali ketika halaman yang memiliki getStaticProps atau getServerSideProps dibuka oleh pengguna tindakan HYDRATE akan dikirim. Ini mungkin terjadi selama pemuatan halaman awal dan selama navigasi halaman biasa. payload tindakan ini akan berisi state pada saat pembuatan statis atau rendering sisi server, sehingga peredam Anda harus menggabungkannya dengan status klien yang ada dengan benar.
Cara paling sederhana adalah dengan menggunakan pemisahan server dan status klien.
Cara lain adalah menggunakan https://github.com/benjamine/jsondiffpatch untuk menganalisis diff dan menerapkannya dengan benar:
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 ;
}
} ;Atau seperti ini (dari dengan contoh-redux-wrapper di Next.js repo):
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 ) ;
}
} ; Fungsi createWrapper menerima makeStore sebagai argumen pertamanya. Fungsi makeStore harus mengembalikan instance Redux Store baru setiap kali dipanggil. Tidak diperlukan memoisasi di sini, secara otomatis dilakukan di dalam bungkus.
createWrapper juga secara opsional menerima objek konfigurasi sebagai parameter kedua:
debug (Opsional, Boolean): Aktifkan Debug LoggingserializeState dan deserializeState : Fungsi Kustom untuk Serializing dan Deserializing The Redux State, lihat serialisasi dan deserialisasi khusus. Ketika makeStore dipanggil, itu dilengkapi dengan konteks Next.js, yang bisa berupa NextPageContext atau AppContext atau getStaticProps atau getServerSideProps konteks tergantung pada fungsi siklus hidup mana yang akan Anda bungkus.
Beberapa konteks itu ( getServerSideProps selalu, dan NextPageContext , AppContext kadang -kadang jika halaman diberikan di server) dapat memiliki properti yang terkait permintaan dan respons:
req ( IncomingMessage )res ( ServerResponse ) Meskipun dimungkinkan untuk membuat logika spesifik server atau klien di kedua makeStore , saya sangat menyarankan agar mereka tidak memiliki perilaku yang berbeda. Hal ini dapat menyebabkan kesalahan dan ketidakcocokan checksum yang pada gilirannya akan merusak seluruh tujuan rendering server.
Bagian ini menjelaskan cara melampirkan fungsi siklus hidup GetStaticProps.
Mari kita buat halaman di 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 dibuka oleh pengguna, tindakan HYDRATE akan dikirim. payload tindakan ini akan berisi state pada saat generasi statis, tidak akan memiliki keadaan klien, sehingga peredam Anda harus menggabungkannya dengan keadaan klien yang ada dengan benar. Lebih lanjut tentang ini di server dan pemisahan keadaan klien.
Meskipun Anda dapat membungkus setiap halaman (dan tidak membungkus pages/_app ), tidak disarankan, lihat paragraf terakhir di bagian penggunaan.
Bagian ini menjelaskan cara melampirkan fungsi siklus hidup GetServerSideprops.
Mari kita buat halaman di 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 dibuka oleh pengguna, tindakan HYDRATE akan dikirim. payload tindakan ini akan berisi state pada saat rendering sisi server, tidak akan memiliki status klien, sehingga peredam Anda harus menggabungkannya dengan status klien yang ada dengan benar. Lebih lanjut tentang ini di server dan pemisahan keadaan klien.
Meskipun Anda dapat membungkus setiap halaman (dan tidak membungkus pages/_app ), tidak disarankan, lihat paragraf terakhir di bagian penggunaan.
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 ; Perlu diingat bahwa req dan res mungkin tidak tersedia jika getInitialProps dipanggil di sisi klien.
Komponen fungsi stateless juga dapat diganti dengan kelas:
class Page extends Component {
public static getInitialProps = wrapper . getInitialPageProps ( store => ( ) => ( { ... } ) ) ;
render ( ) {
// stuff
}
}
export default Page ; Meskipun Anda dapat membungkus setiap halaman (dan tidak membungkus pages/_app ), tidak disarankan, lihat paragraf terakhir di bagian penggunaan.
pages/_app juga. Tetapi mode ini tidak kompatibel dengan fitur ekspor statis parsial otomatis Next.js 9, lihat penjelasan di bawah ini.
Wrapper juga dapat dilampirkan ke komponen _app Anda (terletak di /pages ). Semua komponen lain dapat menggunakan fungsi connect react-redux .
// 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 ) ;Maka semua halaman dapat dengan mudah dihubungkan (contoh mempertimbangkan komponen halaman):
// 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 atau getStaticProps di level halaman Anda juga dapat menggunakan getServerSideProps atau getStaticProps di level halaman, dalam hal ini tindakan HYDRATE akan dikirim dua kali: dengan status setelah App.getInitialProps dan kemudian dengan status setelah getServerSideProps atau getStaticProps :
getServerSideProps di level halaman maka store di getServerSideProps akan dieksekusi setelah App.getInitialProps dan akan memiliki keadaan darinya, jadi HYDRATE kedua akan memiliki keadaan penuh dari keduanyagetStaticProps di level halaman maka store di getStaticProps akan dieksekusi pada waktu kompilasi dan tidak akan memiliki status dari App.getInitialProps karena mereka dieksekusi dalam konteks yang berbeda dan negara tidak dapat dibagikan. Tindakan HYDRATE pertama menyatakan setelah App.getInitialProps dan kedua akan memiliki status setelah getStaticProps (meskipun dieksekusi sebelumnya dalam waktu). Cara paling sederhana untuk memastikan penggabungan yang tepat adalah dengan menjatuhkan nilai awal dari 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 ;
}
} ; Asumsikan halaman hanya pengiriman PAGE dan aplikasi hanya APP , ini membuat penggabungan negara aman.
Lebih lanjut tentang itu di server dan pemisahan keadaan klien.
Menggunakan next-redux-wrapper ("The Wrapper"), hal-hal berikut terjadi berdasarkan permintaan:
Fase 1: getInitialProps / getStaticProps / getServerSideProps
makeStore ) dengan keadaan awal yang kosong. Dengan melakukan itu juga memberikan objek Request dan Response sebagai opsi ke makeStore ._app getInitialProps dan melewati toko yang dibuat sebelumnya._app getInitialProps , bersama dengan keadaan toko.getXXXProps halaman dan melewati toko yang dibuat sebelumnya.getXXXProps halaman, bersama dengan keadaan toko.Fase 2: SSR
makeStoreHYDRATE dengan keadaan toko sebelumnya sebagai payload_app atau page .Fase 3: Klien
HYDRATE dengan keadaan dari fase 1 sebagai payload_app atau page .Catatan: Keadaan klien tidak bertahan di seluruh permintaan (yaitu Fase 1 selalu dimulai dengan keadaan kosong). Oleh karena itu, diatur ulang di halaman ulang halaman. Pertimbangkan untuk menggunakan Redux Syvant jika Anda ingin bertahan di antara permintaan.
Sejak versi 7.0 dukungan kelas satu dari @reduxjs/toolkit telah ditambahkan.
Contoh Lengkap: https://github.com/kirill-konshin/next-redux-vrapper/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 ] ; Disarankan untuk mengekspor State yang diketik dan ThunkAction :
export type AppStore = ReturnType < typeof makeStore > ;
export type AppState = ReturnType < AppStore [ 'getState' ] > ;
export type AppThunk < ReturnType = void > = ThunkAction < ReturnType , AppState , unknown , Action > ; Setiap kali ketika halaman yang memiliki getStaticProps atau getServerSideProps dibuka oleh pengguna tindakan HYDRATE akan dikirim. payload tindakan ini akan berisi state pada saat pembuatan statis atau rendering sisi server, sehingga peredam Anda harus menggabungkannya dengan status klien yang ada dengan benar.
Cara termudah dan paling stabil untuk memastikan tidak ada yang secara tidak sengaja ditimpa adalah dengan memastikan bahwa peredam Anda menerapkan tindakan sisi klien dan server ke substat yang berbeda dari negara bagian Anda dan mereka tidak pernah berbenturan:
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 ;
}
} ; Jika Anda lebih suka pendekatan isomorfik untuk beberapa bagian (lebih disukai kecil) dari negara bagian Anda, Anda dapat membaginya antara klien dan server pada halaman yang diseret server menggunakan-redux-cookie-wrapper, ekstensi ke wrapper redux berikutnya. Dalam hal ini, untuk penggantian yang dipilih, server mengetahui keadaan klien (kecuali di getStaticProps ) dan tidak perlu memisahkan status server dan klien.
Juga, Anda dapat menggunakan pustaka seperti https://github.com/benjamine/jsondiffpatch untuk menganalisis diff dan menerapkannya dengan benar.
Saya tidak merekomendasikan menggunakan withRedux di pages/_document.js , Next.js tidak memberikan cara yang dapat diandalkan untuk menentukan urutan kapan komponen akan diterjemahkan. Jadi per Rekomendasi Next.js lebih baik memiliki hanya hal-hal agnostik data di pages/_document .
Halaman kesalahan juga dapat dibungkus dengan cara yang sama seperti halaman lainnya.
Transisi ke halaman kesalahan ( pages/_error.js template) akan menyebabkan pages/_app.js diterapkan tetapi selalu merupakan transisi halaman penuh (bukan html5 pushstate), sehingga klien akan membuat toko yang dibuat dari awal menggunakan status dari server. Jadi, kecuali jika Anda tetap menggunakan toko pada klien entah bagaimana keadaan klien yang dihasilkan sebelumnya akan diabaikan.
Anda dapat menggunakan https://github.com/reduxjs/redux-hunk untuk mengirimkan tindakan async:
function someAsyncAction ( id ) {
return async function ( dispatch , getState ) {
return someApiCall ( id ) . then ( res => {
dispatch ( {
type : 'FOO' ,
payload : res ,
} ) ;
} ) ;
} ;
}
// usage
await store . dispatch ( someAsyncAction ( ) ) ;Anda juga dapat menginstal https://github.com/pburtchaell/redux-promise-middleware untuk mengirimkan janji sebagai tindakan async. Ikuti panduan instalasi perpustakaan, maka Anda akan dapat menanganinya seperti ini:
function someAsyncAction ( ) {
return {
type : 'FOO' ,
payload : new Promise ( resolve => resolve ( 'foo' ) ) ,
} ;
}
// usage
await store . dispatch ( someAsyncAction ( ) ) ; Jika Anda menyimpan jenis yang kompleks seperti Immutable.js atau JSON objek di negara bagian Anda, penangan serial khusus dan deserialize mungkin berguna untuk membuat serial redux status di server dan deserialisasi lagi pada klien. Untuk melakukannya, berikan serializeState dan deserializeState sebagai opsi konfigurasi untuk withRedux .
Alasannya adalah bahwa snapshot status ditransfer melalui jaringan dari server ke klien sebagai objek biasa.
Contoh Serialisasi Kustom dari Keadaan Immutable.js Menggunakan json-immutable :
const { serialize , deserialize } = require ( 'json-immutable' ) ;
createWrapper ( {
serializeState : state => serialize ( state ) ,
deserializeState : state => deserialize ( state ) ,
} ) ;Hal yang sama menggunakan immutable.js:
const { fromJS } = require ( 'immutable' ) ;
createWrapper ( {
serializeState : state => state . toJS ( ) ,
deserializeState : state => fromJS ( state ) ,
} ) ; [Catatan, metode ini mungkin tidak aman - pastikan Anda menaruh banyak pemikiran untuk menangani sagas async dengan benar. Kondisi ras terjadi dengan sangat mudah jika Anda tidak hati -hati.] Untuk memanfaatkan Redux Saga, orang hanya harus membuat beberapa perubahan pada fungsi makeStore mereka. Secara khusus, redux-saga perlu diinisialisasi di dalam fungsi ini, bukan di luarnya. (Saya melakukan ini pada awalnya, dan mendapat kesalahan jahat yang memberi tahu saya Before running a Saga, you must mount the Saga middleware on the Store using applyMiddleware ). Inilah cara seseorang mencapai hal itu. Ini hanya sedikit dimodifikasi dari contoh pengaturan di awal dokumen. Perlu diingat bahwa pengaturan ini akan memilih Anda dari optimasi statis otomatis: https://err.sh/next.js/opt-ut-uto-tatic-optimization.
Buat saga root Anda seperti biasa, lalu terapkan pembuat toko:
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 Kemudian di pages/_app tunggu saga berhenti dan tunggu sampai selesai saat eksekusi ada di server:
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 atau getStaticProps Untuk menggunakannya dengan getServerSideProps atau getStaticProps Anda perlu await sagas di setiap penangan halaman:
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 ( ) ;
} ) ; getInitialProps di dalam _app Jika Anda tidak ingin memilih keluar dari pra-rendering otomatis di aplikasi Next.js Anda, Anda dapat mengelola sagas yang disebut server berdasarkan per halaman seperti contoh resmi. Jika Anda menggunakan opsi ini, pastikan Anda menunggu semua dan semua kisah dalam metode halaman berikutnya.js. Jika Anda melewatkannya di salah satu halaman, Anda akan berakhir dengan keadaan yang tidak konsisten dikirim ke klien. Jadi, kami menganggap menunggu di _app lebih aman secara otomatis, tetapi jelas kelemahan utama adalah memilih keluar dari ekspor statis otomatis.
Jika Anda hanya perlu bertahan dalam bagian kecil dari negara bagian Anda, wrapper-redux-cookie next-redux mungkin merupakan alternatif yang mudah untuk redux bertahan yang mendukung SSR.
Boilerplate: https://github.com/fazlulkarimweb/with-next-redux-wrapper-redux-persist
Sejujurnya, saya pikir menempatkan gerbang kegigihan tidak diperlukan karena server sudah dapat mengirim beberapa HTML dengan beberapa keadaan, jadi lebih baik menunjukkannya segera dan kemudian menunggu tindakan REHYDRATE terjadi untuk menunjukkan delta tambahan yang datang dari penyimpanan kegigihan. Itu sebabnya kami menggunakan rendering sisi server di tempat pertama.
Tapi, bagi mereka yang benar -benar ingin memblokir UI saat rehidrasi sedang terjadi, berikut adalah solusinya (tetap hacky):
// 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 ,
} ) ; Dan kemudian di halaman Next.js _app Anda dapat menggunakan akses konteks telanjang untuk mendapatkan toko (https://react-redax.js.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 >
) ;
}
} ,
) ;Atau menggunakan kait:
// 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 >
) ;
} ) ;Dan kemudian di halaman 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 >
) ) ; Signature of createWrapper telah berubah: Alih -alih createWrapper<State> Anda harus menggunakan createWrapper<Store<State>> , semua jenis akan secara otomatis disimpulkan dari Store .
GetServerSidePropsContext dan GetStaticPropsContext tidak lagi diekspor dari next-redux-wrapper , Anda harus menggunakan GetServerSideProps , GetServerSidePropsContext , GetStaticProps dan GetStaticPropsContext langsung dari next .
Semua tanda tangan seperti ({store, req, res, ...}) => { ... } diubah menjadi store => ({req, res, ...}) => { ... } untuk menjaga internal selanjutnya.
Dalam versi 7.x Anda harus membungkus secara manual semua getInitialProps dengan pembungkus yang tepat: wrapper.getInitialPageProps dan wrapper.getInitialAppProps .
window.next_redux_wrapper_store telah dihapus karena menyebabkan masalah dengan memuat ulang panas
Perubahan besar dalam cara bagaimana hal -hal dibungkus dalam versi 6.
Ekspor default withRedux ditandai sudah usang, Anda harus membuat wrapper const wrapper = createWrapper(makeStore, {debug: true}) dan kemudian menggunakan wrapper.withRedux(MyApp) .
Fungsi makeStore Anda tidak lagi mendapat initialState , hanya menerima konteks: makeStore(context: Context) . Konteks dapat berupa NextPageContext atau AppContext atau getStaticProps atau getServerSideProps konteks tergantung pada fungsi siklus hidup mana yang akan Anda bungkus. Sebaliknya, Anda perlu menangani aksi HYDRATE dalam peredam. payload tindakan ini akan berisi state pada saat pembuatan statis atau rendering sisi server, sehingga peredam Anda harus menggabungkannya dengan status klien yang ada dengan benar.
App seharusnya tidak lagi membungkus anak -anaknya dengan Provider , sekarang dilakukan secara internal.
isServer tidak dilewati dalam context / props , gunakan fungsi Anda sendiri atau periksa sederhana const isServer = typeof window === 'undefined' atau !!context.req atau !!context.ctx.req .
store tidak diteruskan ke alat peraga komponen yang dibungkus.
WrappedAppProps diganti namanya menjadi WrapperProps .
Jika proyek Anda menggunakan Next.js 5 dan redux wrapper berikutnya 1.x instruksi ini akan membantu Anda untuk meningkatkan ke 2.x.
Tingkatkan Next.js dan Wrapper
$ npm install next@6 --save-dev
$ npm install next-redux-wrapper@latest --save Ganti semua penggunaan import withRedux from "next-redux-wrapper"; dan withRedux(...)(WrappedComponent) di semua halaman Anda dengan Hoc Redux React connect Plain:
import { connect } from "react-redux" ;
export default connect ( ... ) ( WrappedComponent ) ;Anda juga mungkin harus memformat ulang konfigurasi berbasis objek pembungkus Anda ke konfigurasi Redux React Sederhana.
Buat file pages/_app.js dengan kode minimal berikut:
// 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 ) ; Ikuti Next.js 6 Instruksi Peningkatan untuk semua komponen Anda ( props.router , bukan props.url dan sebagainya)
Itu saja. Proyek Anda sekarang harus bekerja sama seperti sebelumnya.