手把手从头开始教你,彻底理解服务端渲染原理( 三 )


part3: 同构项目中引入Redux这一节主要是讲述Redux如何被引入到同构项目中以及其中需要注意的问题 。
重新回顾一下redux的运作流程:
 

手把手从头开始教你,彻底理解服务端渲染原理

文章插图
 
再回顾一下同构的概念,即在React代码客户端和服务器端各自运行一遍 。
 
一、创建全局store现在开始创建store 。在项目根目录的store文件夹(总的store)下:
import {createStore, applyMiddleware, combineReducers} from 'redux';import thunk from 'redux-thunk';import { reducer as homeReducer } from '../containers/Home/store';//合并项目组件中store的reducerconst reducer = combineReducers({home: homeReducer})//创建store,并引入中间件thunk进行异步操作的管理const store = createStore(reducer, applyMiddleware(thunk));//导出创建的storeexport default store复制代码二、组件内action和reducer的构建Home文件夹下的工程文件结构如下:
 
手把手从头开始教你,彻底理解服务端渲染原理

文章插图
 
在Home的store目录下的各个文件代码示例:
 
//constants.jsexport const CHANGE_LIST = 'HOME/CHANGE_LIST';复制代码//actions.jsimport axIOS from 'axios';import { CHANGE_LIST } from "./constants";//普通actionconst changeList = list => ({type: CHANGE_LIST,list});//异步操作的action(采用thunk中间件)export const getHomeList = () => {return (dispatch) => {return axios.get('xxx').then((res) => {const list = res.data.data;console.log(list)dispatch(changeList(list))});};}复制代码//reducer.jsimport { CHANGE_LIST } from "./constants";const defaultState = {name: 'sanyuan',list: []}export default (state = defaultState, action) => {switch(action.type) {default:return state;}}复制代码//index.jsimportreducerfrom "./reducer";//这么做是为了导出reducer让全局的store来进行合并//那么在全局的store下的index.js中只需引入Home/store而不需要Home/store/reducer.js//因为脚手架会自动识别文件夹下的index文件export {reducer}复制代码三、组件连接全局store下面是Home组件的编写示例 。
import React, { Component } from 'react';import { connect } from 'react-redux';import { getHomeList } from './store/actions'class Home extends Component {render() {const { list } = this.propsreturn list.map(item => <div key={item.id}>{item.title}</div>)}}const mapStateToProps = state => ({list: state.home.newsList,})const mapDispatchToProps = dispatch => ({getHomeList() {dispatch(getHomeList());}})//连接storeexport default connect(mapStateToProps, mapDispatchToProps)(Home);复制代码对于store的连接操作,在同构项目中分两个部分,一个是与客户端store的连接,另一部分是与服务端store的连接 。都是通过react-redux中的Provider来传递store的 。
客户端:
//src/client/index.jsimport React from 'react';import ReactDom from 'react-dom';import {BrowserRouter, Route} from 'react-router-dom';import { Provider } from 'react-redux';import store from '../store'import routes from '../routes.js'const App = () => {return (<Provider store={store}><BrowserRouter>{routes}</BrowserRouter></Provider>)}ReactDom.hydrate(<App />, document.getElementById('root'))复制代码服务端:
//src/server/index.js的内容保持不变//下面是src/server/utils.jsimport Routes from '../Routes'import { renderToString } from 'react-dom/server';import { StaticRouter } from 'react-router-dom'; import { Provider } from 'react-redux';import React from 'react'export const render = (req) => {const content = renderToString(<Provider store={store}><StaticRouter location={req.path} >{Routes}</StaticRouter></Provider>);return `<html><head><title>ssr</title></head><body><div id="root">${content}</div><script src=https://www.isolves.com/it/cxkf/bk/2020-05-12/"/index.js">`}复制代码四、潜在的坑其实上面这样的store创建方式是存在问题的,什么原因呢?
上面的store是一个单例,当这个单例导出去后,所有的用户用的是同一份store,这是不应该的 。那么这么解这个问题呢?
在全局的store/index.js下修改如下:
//导出部分修改export default() => {return createStore(reducer, applyMiddleware(thunk))}复制代码这样在客户端和服务端的js文件引入时其实引入了一个函数,把这个函数执行就会拿到一个新的store,这样就能保证每个用户访问时都是用的一份新的store 。


推荐阅读