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

网络中介绍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 @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()之类的是不行的。因为随机和与时间相关让我们对函数的结果没法预知。
以下两点非常重要:

  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 @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();

你可能感兴趣的:(前端)