您的位置:首页 > Web前端 > React

react-redux的使用从action规划到reducer实现及完整案例

2018-10-18 01:47 746 查看
版权声明:本文为博主魏永强(http://blog.csdn.net/marswill)原创文章,未经博主允许不得转载。 https://blog.csdn.net/hayixia606/article/details/83120438

网络中介绍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时你就会觉得非常清晰
;

功能定位

  1. 我们需要可以添加用户
    ADD_USER
  2. 我们需要在列表中点击查看用户详情
    SELECT_USER
  3. 我们需要双击列表中数据删除该用户
    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<hayixia606@163.com>
@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<hayixia606@163.com>
@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()之类的是不行的。因为随机和与时间相关让我们对函数的结果没法预知。
以下两点非常重要:

  1. 不要去改变state的值
  2. 如果有未知的action请一定返回旧的state

注意我在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中的数据和组件沟通

其实到了这一步很多人发现其实缺了什么。也就是我们就数据如何进入store的操作描述(action)及操作实现(reducer)都完成了。但是如何在界面使用这些数据呢?
显然,要让他们关联起来。这个就是

react-redux提供的connect把store中的数据以及操作跟我们的视图组件连接起来
这个过程很抽象其实不难理解

创建组件及连接[
connect
]

containers/user-list.js

/*
@Author: weiyognqiang<hayixia606@163.com>
@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<hayixia606@163.com>
@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();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: