内容简介
本内容是基于实实在在的项目开发提炼出来的解决方案,手把手教你如何在React中使用Redux来进行数据管理,该案例可以直接在项目中使用;本文中不会介绍原理什么之类的,网上一大堆。本内容适合有react开发基础的人学习,希望对大家有帮助。
目录结构
- 1 按类型分
这里的类型指的是一个文件在项目中充当的角色类型,即这个文件是一个component,还是一个container,或者是一个reducer等,充当component、container、action、reducer等不同角色的文件,分别放在不同的文件夹下,这也是Redux官网示例所采用的项目结构。这种结构如下所示:当项目比较小时按类型来分是完全没问题的,当项目越来越庞大你会发现要修改某数据结构时很麻烦。actions/ a.js b.js components/ a1.jsx a2.jsx b1.jsx constainers/ a.jsx b.jsx reducers/ a.js b.js index.js复制代码
- 2 按功能分
我们在项目开发中总结了一种比较合适的目录结构,components里存放所有的view组件和组件对应的样式文件,constainers里存放所有的容器组件,为了开发效率和减少文件数量,我们把容器组件的action和reducer放在同一个文件(XXXRedux.js)里面。这种结构如下所示::显而易见采用这样的目录结构要修改某一个组件时很方便,每一个容器组件对应的action、reducer、component、css都同一个文件目录下,操作很方便。components/ componentA/ a.jsx a.less componentB/ b.jsx b.less constainers/ constainer1/ X.jsx X.less XRedux.js constainer2/ XX.jsx XX.less XXRedux.js复制代码
开发步骤
因为涉及到公司数据安全问题,真实项目是没法给各位看官look look了;以下都用经典的todolist代码做展示。
1 设计reducer
设计store有很多讲究,不能把服务给的数据结构直接作为store,也不能按照view结构来设计store;对于初学者来说是一件很烦恼的事,社区很多都推荐按照设计数据库表结构来设计store,但是很多以项目交付为目标的不可能做得这么标准,所以大家都尽量向标准看齐-_-。
曾经在文章看到设计store的标准,给大家参考参考:- 1) 把整个应用的状态按照领域(Domain)分成若干子State,子State之间不能保存重复的数据。
- 2) State以键值对的结构存储数据,以记录的key/ID作为记录的索引,记录中的其他字段都依赖于索引。
- 3)State中不能保存可以通过已有数据计算而来的数据,即State中的字段不互相依赖。
在业务比较复杂的情况下强力建议大家使用immutable,在项目初期还没有感觉,但是项目越来越大,业务越来越复杂时就能完全感受到它的好处了。
以下代码是一个reducer文件,我们把该reducer需要的所有type定义在一个常量types对象中,initialState是最初state没有数据时显示的默认数据。
const types = {
SELECT_ENTRY: 'select_entry',
CREATE_NEW_ENTRY: 'create_new_entry',
EDIT_ENTRY: 'edit_entry',
CANCEL_EDIT: 'cancel_edit',
UPDATE_ENTRY_LIST: 'update_entry_list',
UPDATE_SAVED_ENTRY: 'updadte_saved_entry'
}
const initialState = {
selectedId: null,
isEditing: false,
big: '测试',
};
export default function editor(state = initialState, action) {
switch (action.type) {
case types.SELECT_ENTRY:
return Object.assign({}, state, { selectedId: action.id });
case types.CREATE_NEW_ENTRY:
return Object.assign({}, state, { selectedId: null, isEditing: true });
case types.EDIT_ENTRY:
return Object.assign({}, state, { selectedId: action.id, isEditing: true });
case types.CANCEL_EDIT:
return Object.assign({}, state, { isEditing: false });
default:
return state;
}
}复制代码
- 2 action
为了快速开发,方便修改和减少文件数量,把action也写在reducer里面;一般每reducer里面代码最多也不会超过400行,如果代码量太多你应该考虑你组件是否写得有问题了;大家把storage操作就当作是与后台服务的交互吧。
可能已有人注意到使用dispatch分发action时已经使用异步数据流了,是的,但是使用异步操作之前还是需要在Redux里添加一些异步操作中间价,不用捉鸡,这个后面代码会讲到。(注意⚠️)在真正的项目开发时候因为业务需要,所以store没有这么简单;很多时候交互都是在props里获取的对象(变量非引用无所谓),若要对从props里获取的对象进行某些业务操作时一定要深拷贝一份进行操作,否则你会遇到意想不到的结果。
export function selectEntry(id) {
return { type: types.SELECT_ENTRY, id };
}
export function createNewEntry() {
return { type: types.CREATE_NEW_ENTRY };
}
export function editEntry(id) {
return { type: types. EDIT_ENTRY, id };
}
export function cancelEdit() {
return { type: types.CANCEL_EDIT };
}
function updateEntryList(items) {
return { type: types.UPDATE_ENTRY_LIST, items };
}
export function deleteEntry(id) {
return dispatch => {
storage.deleteEntry(id)
.then(() => storage.getAll())
.then((items) => dispatch(updateEntryList(items)));
};
}
export function fetchEntryList() {
return dispatch => {
storage.getAll()
.then(items => dispatch(updateEntryList(items)));
};
}
function updateSavedEntry(id) {
return { type: types.UPDATE_SAVED_ENTRY, id };
}
export function saveEntry(item) {
const { title, content, id } = item;
return dispatch => {
if (id) {
// 更新流程
storage.updateEntry(id, title, content)
.then(() => dispatch(updateSavedEntry(id)))
.then(() => storage.getAll())
.then(items => dispatch(updateEntryList(items)));
} else {
// 创建流程
storage.insertEntry(title, content)
.then(inserted => dispatch(updateSavedEntry(inserted.id)))
.then(() => storage.getAll())
.then(items => dispatch(updateEntryList(items)));
}
};
}复制代码
3 Redux 与组件
在项目中使用Redux做数据管理时,我们把组件分为容器组件和展示组件,一般容器组件就负责数据是这么更新的,不会包含任何Virtual DOM的修改和组合以及组件样式;而展示组件是view相关的,一般会写成无状态组件,但是在项目中很多时候展示组件需要有生命周期,但是建议尽量使用无状态组件。import React, { PropTypes } from 'react'; import { connect } from 'react-redux'; import CreateBar from '../CreateBar'; import List from '../List'; import ItemShowLayer from '../ItemShowLayer'; import ItemEditor from '../ItemEditor'; import './style.scss'; /** * Deskmark是一个容器组件 */ class Deskmark extends React.Component { constructor(props) { super(props); } static defaultProps = {} static propTypes = { state: PropTypes.object.isRequired, actions: PropTypes.object.isRequired, } componentDidMount() { this.props.actions.fetchEntryList(); } render() { const { state, actions } = this.props; const { isEditing, selectedId } = state.editor; const items = state.items; const item = items.find( ({ id }) => id === selectedId ); const mainPart = isEditing ? ( <ItemEditor item={item} onSave={actions.saveEntry} onCancel={actions.cancelEdit} /> ) : ( <ItemShowLayer item={item} onEdit={actions.editEntry} onDelete={actions.deleteEntry} /> ); return ( <section className="deskmark-component"> <nav className="navbar navbar-fixed-top navbar-dark bg-inverse"> <a className="navbar-brand" href="#">Deskmark App</a> </nav> <div className="container"> <div className="row"> <div className="col-md-4 list-group"> <CreateBar onClick={actions.createNewEntry} /> <List items={items} onSelect={actions.selectEntry} /> </div> {mainPart} </div> </div> </section> ); } } /** * mapStateToProps可以过滤该组件需要的数据 * @param {*} state Redux管理的store */ function mapStateToProps(state) { const { state, actions } = state; return { state, actions }; } export default connect(mapStateToProps)(Deskmark);复制代码
4 入口文件
app.js文件是项目的入口文件,也是在React中使用Redux的最关键的一步,该文件只需要配置一次,以后都可以拿来直接用。插件说明
- 1) redux
- 2) react-redux 该插件让Redux在React中的使用爽得不要不要的,在React中只需要使用一个组件(Provider)和一个高价函数(connect)就可以用Redux来管理React的数据了。
- 3) react-router 把router的数据给Redux管理
- 4) redux-thunk 可以在Redux中使用异步操作,网上也有很多类似比较牛X异步中间价
大家如果有兴趣可以看看redux-arena,项目中没有使用过,也就不做过多介绍了。
1) app.js
import React from 'react'; import ReactDom from 'react-dom'; import {Provider} from 'react-redux'; import Deskmark from '../containers/Main/index'; import mainStore from '../containers/Main/store'; import { Router, Route, hashHistory, browserHistory } from 'react-router'; //获取store const store = mainStore(); ReactDom.render( <Provider store={store}> <Router history={hashHistory}> <Route path="/" component={Deskmark} /> </Router> </Provider>, document.getElementById('react-content') );复制代码
2) store.js
import { createStore, applyMiddleware, compose } from 'redux'; import thunkMiddleware from 'redux-thunk'; import rootReducer from './mainReducer'; //thunkMiddleware异步操作中间件 const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore); //创建一个store export default function mainStore() { const store = createStoreWithMiddleware(rootReducer, compose(applyMiddleware(thunkMiddleware)) ); return store; }复制代码
3) mainReducer.js
import { combineReducers } from 'redux'; //combineReducers把多个子reducer合并成一个reducer const rootReducer = combineReducers({ editor, index, items }); export default rootReducer;复制代码
好了,到此为止在React中使用Redux就告一段落,谢谢阅读,如果有问题可以一起讨论,若文中有错误欢迎批评指出或者给出建议。