这一节主要是讲述Redux如何被引入到同构项目中以及其中需要注意的问题。
重新回顾一下redux的运作流程:
一、创建全局store
现在开始创建store。 在项目根目录的store文件夹(总的store)下:
import {createStore, applyMiddleware, combineReducers} from 'redux';
import thunk from 'redux-thunk';
import { reducer as homeReducer } from '../containers/Home/store';
//合并项目组件中store的reducer
const reducer = combineReducers({
home: homeReducer
})
//创建store,并引入中间件thunk进行异步操作的管理
const store = createStore(reducer, applyMiddleware(thunk));
//导出创建的store
export default store
复制代码
二、组件内action和reducer的构建
Home文件夹下的工程文件结构如下:
//constants.js
export const CHANGE_LIST = 'HOME/CHANGE_LIST';
复制代码
//actions.js
import axios from 'axios';
import { CHANGE_LIST } from "./constants";
//普通action
const 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.js
import { CHANGE_LIST } from "./constants";
const defaultState = {
name: 'sanyuan',
list: []
}
export default (state = defaultState, action) => {
switch(action.type) {
default:
return state;
}
}
复制代码
//index.js
import reducer from "./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.props
return list.map(item => <div key={item.id}>{item.title}</div>)
}
}
const mapStateToProps = state => ({
list: state.home.newsList,
})
const mapDispatchToProps = dispatch => ({
getHomeList() {
dispatch(getHomeList());
}
})
//连接store
export default connect(mapStateToProps, mapDispatchToProps)(Home);
复制代码
对于store的连接操作,在同构项目中分两个部分,一个是与客户端store的连接,另一部分是与服务端store的连接。都是通过react-redux中的Provider来传递store的。
客户端:
//src/client/index.js
import 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.js
import 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="/index.js"></script>
</body>
</html>
`
}
复制代码
四、潜在的坑
其实上面这样的store创建方式是存在问题的,什么原因呢?
上面的store是一个单例,当这个单例导出去后,所有的用户用的是同一份store,这是不应该的。那么这么解这个问题呢?
在全局的store/index.js下修改如下:
//导出部分修改
export default () => {
return createStore(reducer, applyMiddleware(thunk))
}
复制代码
这样在客户端和服务端的js文件引入时其实引入了一个函数,把这个函数执行就会拿到一个新的store,这样就能保证每个用户访问时都是用的一份新的store。