在Next.js中引入Redux|第一篇

前言

春节在家里做了一点关于Next.js的工作,由于知识点比较多,空余写作时间比较零碎,我决定采用系列文的形式对所有知识点进行回顾总结,争取一篇文章讲明一个点,本篇要讲的是如何通过React高阶组件在Next.js项目中引入Redux

背景

背景没什么好说的,Next.js作为React的一个前后端同构框架,在应用中引入Redux是一个常见的需求(阅读本文前应对Next.js前后端渲染的流程以及Redux基本用法有一定了解),使用常规方法引入Redux会产生一个问题:当服务端接收到请求后,Next会先后在服务端、前端各生成一个store,由于两个store没有进行数据同步,前端渲染时会报错。

解决思路

在服务端渲染的过程中完成store的初始化,并将初始化的store返回前端,供前端使用,即可保证前后端store数据一致性。 工作流程如下:

前端 服务器 请求 完成store初始化 及页面渲染 初始化后的store与HTML 此处渲染的作用是构 建JS层面的内容, 比如说:虚拟DOM 前端 服务器

由于store不能被序列化,HTML不能直接携带store返回前端,只能先取出初始化后的store的state,随HTML回传到前端后,前端再利用此state创建store,以此保证前后端store一致性。具体的实现会在代码中进行讲解。

实现

首先安装两个包,redux和react-redux
redux就是redux本体
react-redux是redux的官方react连接库

代码

store.js

创建一个createMyStore模块,此模块的作用是创建store,用户传入一个InitialState,它返回一个store

import {
      createStore } from 'redux'

const ADD = 'add';

const defaultState = {
     
    counter: 28
}

function reducer(preState, action) {
     
    switch (action.type) {
     
        case ADD:
            return {
     
                ...preState,
                counter: preState.counter+1
            }
        default:
            return preState
    }
}

function createMyStore(initialState = defaultState) {
     
    const MyStore = createStore(reducer, initialState);
    return MyStore
}

export default createMyStore

withRedux.js(集成store)

使用React的高阶组件把store集成到自定义App中,高阶组件可以“增强”组件的功能,同时又避免了组件中嵌入业务逻辑过多产生难以维护的问题。
高阶组件中获取store的流程示意图:

Created with Raphaël 2.2.0 开始 是否在服务端? 创建store并返回 结束 前端是否存在store? 返回store yes no yes no
import {
      Component } from "react";
import createMyStore from "../store/store";

const isServer = typeof window === "undefined";
const _REDUX_STORE_ = "_REDUX_STORE_";

/* 
获取或创建一个store。每次进行后端渲染,都创建一个新的store,
请求返回后的首次前端渲染,也会创建一个新的store,与流程示意图一致。
 */
function getOrCreateStore(initialState) {
     
	if (isServer) {
     
		return createMyStore(initialState);
	}
	if (!window[_REDUX_STORE_]) {
     
		window[_REDUX_STORE_] = createMyStore(initialState);
	}
	return window[_REDUX_STORE_];
}


function WithRedux(Comp) {
     
	return class HOCComp extends Component {
     
		constructor(props) {
     
			super(props);
			/* 利用初始化后的state创建store */
			this.store = getOrCreateStore(props.initialState);
		}
		static async getInitialProps(ctx) {
     
			
            const MyStore = getOrCreateStore({
      counter: 0 });
            /* 利用ctx把store传入App,在App中进行store的初始化 */
			ctx.ReduxStore = MyStore;
            
            let appProps = {
     };
            if(typeof Comp.getInitialProps === 'function'){
     
                appProps = await Comp.getInitialProps(ctx);
            }
            /* 
            store初始化后,使用store的getState()方法获取初始化后的state,放入到return的对象中,
            return的对象会被序列化,存放在id为"__NEXT_DATA__"的script标签中,随服务端渲染出的HTML返回,
            前端渲染时从props中取出此state,生成与后端一致的store。
            */
			return {
     
				...appProps,
				initialState: MyStore.getState()
			};
		}
		render() {
     
			/* 把constructor中生成的store通过props传到App中 */
			return <Comp {
     ...this.props} ReduxStore={
     this.store} />;
		}
	};
}

export default WithRedux;

_app.js

在自定义App中的getInitialProps函数中完成store的初始化,在渲染时通过react-redux的Provider把store提供给应用使用。

import App from "next/app";
import WithRedux from "../components/hoc";
import {
      Provider } from "react-redux";

function MyApp({
      Component, pageProps, ReduxStore }) {
     
	return (
		<Provider store={
     ReduxStore}>
			<Component {
     ...pageProps} />
		</Provider>
	);
}

MyApp.getInitialProps = async appContext => {
     
	const appProps = await App.getInitialProps(appContext);
	
	/* 获取store并初始化 */
	const store = appContext.ReduxStore;
	store.subscribe(() => {
     
		console.log("store change");
	});
	store.dispatch({
      type: "add" });
	
	return {
      ...appProps };
};
/* 使用WithRedux */
export default WithRedux(MyApp);

如文章有错漏之处,欢迎各位指正和讨论~~

你可能感兴趣的:(Next.js,reactjs,javascript,前端)