网络中介绍redux和react-redux的文章非常非常多我为什么要写这篇文章呢?因为他们都写得不好。好与不好的标准就是对于一个需要学习react redux的人能不能在读完文章后顺利理解和完成可运行案例。由于时间关系和演示项目对文件命名不是很合理,请谅解。
redux作为一个状态管理库其实是独立的。可以在angular,vue,react或者jQuery中使用。当然使用redux你需要遵循一定的规律或者标准,这会在项目和代码中体现。
在本案例中我要做一个用户管理的功能,用redux来管理用户的数据。规划的结构如下:
{
users:[
{ name: 'xxx', gender: 'm' },
{ name: 'yyy', gender: 'f' },
],
selectedUser: { name: '魏永强', gender: 'm' }
}
这一步非常重要,你需要先自己构思下你的应用的单一状态树。这个结构请大家记住了后边在构建action, reducer时你就会觉得非常清晰
;
ADD_USER
SELECT_USER
DELETE_USER
action编写
]redux官网对action的描述是:把数据从应用传入store的有效载荷。
其实这个解释很难理解,我觉得在我们开发的视角应该觉得action是对功能的描述。
action.js文件
export const ADD_USER = "ADD_USER";
export const SELECT_USER = "SELECT_USER";
export const DELETE_USER = "DELETE_USER";
export function addUser(user) {
return {
type: ADD_USER,
payload: user
};
};
export function selectUser(user) {
return {
type: SELECT_USER,
payload: user
};
};
export function deleteUser(id) {
return {
type: DELETE_USER,
payload: id
};
};
注意每个对象都有一个type就是我们的功能描述和载荷payload其实就是我们的功能需要的数据
reducer编写
]redux官方的解释是:指定了应用状态的变化如何响应 actions 并发送到 store 的
也就是实现action对功能的描述
这儿我们要思考下怎么样来划分reducer了。回到文章刚开始规划结构
我们知道本应用就涉及到两个数据一个是users也就是用户列表,另一个是selectedUser也就是用户详情。那我们就创建两个js:
reducers/
|----user_list.js
|----user_selected.js
|----index.js
先看代码:
user_list.js
/* @Author: weiyognqiang @Date: 2018/10/17 */
import { ADD_USER, DELETE_USER } from '../actions';
export default function (state = [], action) {
switch (action.type) {
case ADD_USER:
return [...state, action.payload];
case DELETE_USER:
if (action.payload >= state.length) {
return state;
}
state.splice(action.payload, 1);
return [...state];
default:
return state;
}
}
user_selected.js
/* @Author: weiyognqiang @Date: 2018/10/17 */
import { SELECT_USER } from '../actions';
export default function (state = null, action) {
switch (action.type) {
case SELECT_USER:
return action.payload;
default:
return state;
}
}
index.js
import { combineReducers } from "redux";
import Users from "./user_lists";
import SelectedUser from "./user_selected";
const rootReducer = combineReducers({
users: Users,
selectedUser: SelectedUser
});
export default rootReducer;
注意:
在reducer的处理函数中传入了state和action。需要注意的是reducer是纯函数
那什么事纯函数呢,就是你传入的参数相同了他返回的结果是确定的。不产生副作用
(其实我觉得这个形容很别扭)。举一个老生常谈的例子。比如在函数中有Math.Random(), Date.Now()之类的是不行的。因为随机和与时间相关让我们对函数的结果没法预知。
以下两点非常重要:
注意我在ADD_USER和DELETE_USER的处理,一个要想users数组对象增加元素,一个要删除数组对象的某个值。但是以上两点必须遵循所以我们用了...对象展开运算符。当然你也可以使用Object.assign({},{},{})深拷贝
以上代码对reducer进行了拆分但是在index.js中使用redux提供的combineReducers进行了合并。该函数返回的结果是和combineReducers传入对象结构完全一致的state,这里需要注意下。当然你可以自己处理combineReducers但是官方的combineReducers还进行了reducer检查,比如是否在action未匹配时返回了state等。
container和component以及connect
]在我们设计中可以把页面元素拆分成组件component
但是在真实处理中这也存在问题,就是组件中数据处理逻辑混在一起。所以有些人抽象了container
这个东西。把这个东西成为数据组件,顾名思义就是专门为组件提供数据
。给组件提供数据那么就是组件间通信就要使用props。
那么我们同样需要两个container一个用来显示列表
,一个用来显示详情
。
其实到了这一步很多人发现其实缺了什么。也就是我们就数据如何进入store的操作描述(action)及操作实现(reducer)都完成了。但是如何在界面使用这些数据呢?
显然,要让他们关联起来。这个就是react-redux提供的connect把store中的数据以及操作跟我们的视图组件连接起来
这个过程很抽象其实不难理解。
connect
]containers/user-list.js
/* @Author: weiyognqiang @Date: 2018/10/17 */
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Input, Button, List } from 'antd';
import {selectUser, addUser, deleteUser} from "../actions";
class UserList extends Component{
constructor(props) {
super(props);
}
showUserLists()
{
return this.props.users.map((user, index) => (
<li
onClick={() => this.props.selectUser(user)}
onDoubleClick={() => this.props.deleteUser(index)}
key={index}>{user.name}</li>
));
}
render() {
return (
<div>
<Button onClick={() => this.props.addUser({name: '魏永强', gender: 'f'})}>添加</Button>
<ul>
{this.showUserLists()}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
return {
users: state.users
};
};
function mapDispatchToProps(dispatch) {
return bindActionCreators({
addUser: addUser,
selectUser: selectUser,
deleteUser: deleteUser
}, dispatch)
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserList);
containers/user-detail.js
/* @Author: weiyognqiang @Date: 2018/10/18 */
import React, { Component } from 'react';
import { connect } from 'react-redux';
class UserDetail extends Component{
constructor(props) {
super(props);
}
render() {
if (!this.props.selectUser) {
return (<p>请单击用户姓名查看详情:</p>);
}
return (
<div>
<p>name: {this.props.selectUser.name}</p>
<p>gender: {this.props.selectUser.gender}</p>
</div>
);
}
}
function mapStateToProps(state) {
return {
selectUser: state.selectedUser
}
}
export default connect(
mapStateToProps
)(UserDetail);
注意
每个container前面的class组件大家都很容易理解。在组件中需要的数据我们都是使用this.props
属性来引用。那么我们必须让他和我们的store中的数据对应起来。这儿有两个函数需要注意。
mapStateToProps(state)
:从这个函数的结构我们就能看出来其实这个是将store的state和我们的props对应起来了而已,很好理解。
mapDispatchToProps(dispatch)
:这个就是把dispatch和props对应起来了而已。(注意我通篇没有讲解dispatch这个东西,这个就是把我们的操作描述传递给reducer的一个函数而已。当然具体的api请查阅官方手册
)
完了之后呢把props和组件连接起来:
export default connect()(UserDetail);
pages/UserList/index.js
import React, { Component } from 'react';
import UserLists from '../../models/containers/user-list';
import UserDetail from '../../models/containers/user-detail';
export default class UserList extends Component{
constructor(props) {
super(props);
}
render() {
return (
<div>
<UserLists />
<UserDetail />
</div>
);
}
}
让所有容器组件访问store
[非常重要
]以上都做完了还不算晚。我们为了让所有容器组件访问store我们要使用react-redux提供的Provider 组件来包裹APP组件。当然如果要结合react-router使用的话还要包裹BrowserRouter或其他。
index.js修改
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './models/reducers'
const store = createStore(rootReducer);
store.subscribe(() => {console.log(store.getState())});
ReactDOM.render(<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();