前期考虑不周的坑,后期总要来填的。— ginny Guo
在这个项目的时候技术选型的时候,因为“时间周期较短”和“产品交流不充分”,没能完全get到prd中的一些细节问题,当时感觉传统的状态管理已经完全hold住这个项目了,结果在后期出现了一个state对应多个view改变,多个action触发一个state改变等问题。多个组件保留状态需要多个copy,简直是灾难啊啊!!!更加难过的是,后期出现了渲染太慢的问题,严重影响用户体验,所以不得不重构引入Redux状态管理了。
本篇记录了react-redux的使用,以及项目前后设计的对比,为以后技术选型做一个铺垫。
本篇真的写了好几天啊,好难讲清楚Orz,以后还要填坑几次才行。
Redux
Redux
是 JavaScript
状态容器,提供可预测化的状态管理。
其数据流大致如下:
Store
Redux 中只有一个单一的 Store
,存储了所有共享状态(以一个对象树的形式储存)。
合并后的reducer(之后讨论)作为参数传入store。
根目录下入口文件index.js中添加store:
| import React from 'react'; import ReactDOM from 'react-dom'; import {Provider} from 'react-redux'; import {createStore} from 'redux'; import App from './App' import myreducer from './reducers';
const store = createStore(myreducers);
ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
|
Action
Redux中把动作和状态独立,通过动作来改变状态。Action
是改变 Store
数据的唯一来源,包含 View
中数据变化、用户操作、服务器响应等等。
Redux中通过Action创建函数的结果(返回值是一个action对象),传给 dispatch
方法即可发起一次dispatch过程。
Store里能直接通过 store.dispatch()
调用dispatch方法。
Action本质上是一个对象,type是一个字符串常量,表示要执行的动作。
Action只有指定动作,不包含更新状态的方法,方法在下面的reducer中会提到。
触发AReducer的actions,存放于actions/index.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| export const AddSth = (data) => ({ type: 'sthAdd', data })
export const DelSth = (data) => ({ type: 'sthDel', data })
export const UpdateSth = (data) => ({ type: 'sthUpdate', data })
export const ClearList = (data) => ({ type: 'listClear', data });
|
Reducer
Reducer
是一个用于处理事件的纯函数,决定每个action如何改变应用的state。
在本项目中为了方便区分各个业务逻辑(互相独立),为每个业务逻辑编写一个reducer,存放于reducers文件夹中间,reduce文件夹中的index.js合并所有reduce,作为一个根级的reducer。
| import {combineReducers} from 'redux'; import AReducer from 'reducers'; import BReducer from 'reducers'; import CReducer from 'reducers';
const myreducer = combineReducers({ AReducer, BReducer, CReducer, }); export default myreducers;
|
单个reduce是形式为 (state, action) => state
的纯函数,state的形式可以是基本类型、数组、对象等等。在本项目中状态是存储的数据,数据用数组list来表示。
举例AReducer,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import _ from 'lodash';
export function AReducer(list:[], action) { const {data = {}} = action; switch (action.type) { case 'sthAdd': { const arrIndex = _.findIndex( list, (item) => parseInt(item.id, 10) === parseInt(data.id, 10) ); if (arrIndex > -1) { list.splice(arrIndex, 1); } else { list.push(data); } return list; }
case 'sthDel': { const arrIndex = _.findIndex( list, (item) => parseInt(item.id, 10) === parseInt(data.id, 10) ); if (arrIndex > -1) { list.splice(arrIndex, 1); } return list; }
case 'sthUpdate': { list.data.map(item => { if (parseInt(item.id, 10) === parseInt(data.id, 10)) { item.xxx = data.xxx; } return item; }); return list; }
case 'listClear': { return []; }
default: return list; } }
|
react-redux容器组件
react-redux
使用容器组件来把展示组件连接到Redux ,容器组件向Redux派发actions,同时监听state改变。
对于容器组件和展示组件的划分附录中有一篇官网推荐的阅读。目前来说,我在本项目中把需要处理共享数据的页面作为了容器组件,后期可能还要修改下。
可以使用 mapStateToProps
来订阅 Store
,其原理相当于在Store上安装了一个监听器,当Store中state改变了,子组件重新渲染。
可以定义 mapDispatchToProps
方法接收dispatch()方法并返回期望注入到展示组件的props中的回调方法。
在本项目中把action作为props整合数据,相当于包了一层dispatch的执行。
假设AList为容器组件,AList中代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import React from 'react'; import {connect} from 'react-redux';
import AItem from '../../components/AItem'; import {AddSth} from './actions';
class AList extends React.Component { this.state = { aList: [] };
getBetList = () => { const {list} = this.state; return list || []; }
listAddHandle = (obj) => { const {AddSth} = this.props; AddSth(obj); }
render() { const aList = this.getBetList(); return (<div className={'components-alist'}> {aList.map((item) => <div key={item.id}> <AItem data={item} onAdd = {this.listAddHandle.bind(this)}/> </div>)} </div>); } }
export const mapStateToProps = state => ({ aList: state.list });
export default connect(mapItemToProps, {AddSth})(AList);
|
附录
:
Redux
Presentational and Container Components