首页» 前端 » react » 四、Redux入门:Antd、store、reducer、actionTypes、actionCreator

四、Redux入门:Antd、store、reducer、actionTypes、actionCreator

日期:2019年05月29日 阅读次数:2609 分类:react

一、Redux概念简述

Facebook:react在2013年开源的时候,还开源了flux(已经过时),flux是官方推出最原始的辅助react的数据层框架,但实际使用时有缺点:公共数据存储区域store由很多的store所组成,这样会导致数据存储的时候会有数据依赖的问题。便在flux的基础上引入了reducer的概念–>redux。

redux = reducer+flux
redux基础设计理念: 数据放在Store,公用存储空间。组件改变Store 的数据,其他组件就能感知到Store里数据的变化,取到新的数据,从而间接的实现了组件之间实现数据传递的功能

三大设计原则:

  • 单一数据源(store中存储数据)
  • store是只读的(想要修改store只能使用action, 其他方法不可以修改)
  • 使用纯函数修改(即reducer)

Redux设计理念

二、Redux 的工作流程

用一个实际的例子来解释redux的工作流程

coponent是借阅人
store是用来存储数据的. 相当于图书管理员
action就是借阅人对图书管理员说的话
reducers就是借阅记录

当借阅人(component)要借书时, 对管理员(store)说”我要借xx书”(action), 管理员就在借阅记录(reducers)上找出来。然后,管理员将书(state)给借阅人(component)。

Redux的工作流程

三、使用 Antd 实现 TodoList 页面布局

使用 npm 或 yarn 安装

npm install antd --save
yarn add antd

具体代码

import React, { Component } from 'React';
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
import { Input, Button, List } from 'antd';

const data = [
  'AAAAA',
  'BBBBB',
  'CCCCC',
  'DDDDD',
  'EEEEE',
];

class TodlList extends Component {
  render() {
    return() {
      return (
        <div style={{marginTop: '10px', marginLeft: '10px'}}>
          <div>
            <Input placeholder='todo info' style={{width: '300px'}} />
            <Button type="primary">提交</Button>
          </div>
          <List
            style={{marginTop: '10px', width: '300px'}}
            bordered
            dataSource={data}
            renderItem={item => (<List.Item>{item}</List.Item>)}
          />
        </div>
      )
    }
  }
}

四、创建 redux 中的 store

  1. npm安装Redux
npm install --save redux
  1. 在src目录下新建目录store,新并建文件reducer.js。内容如下:
// 设置一个默认值,如果state没有值则使用这个默认值
const defaultState = {
    inputValue:'123',
    list: [1, 2]
}
// 导出一个函数(第一个参数是之前的state,第二个值数是一个action对象,包括类型和传值)
export default (state = defaultState, action) => {
    return state;
}
  1. 再在目录src/store下,新建文件index.js,创建stroe。内容如下:
// 引入createStore方法
import { createStore } from 'redux';
// 引入reducer
import reducer from './reducer';
// 创建store,并把reducer中导出的方法传进来
const store = createStore(reducer);

export default store;
  1. 在组件中引用store并使用
import React, { Component } from 'React';
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
import { Input, Button, List } from 'antd';
// 引用store
import store from './store';


class TodlList extends Component {
  constructor(props) {
    super(props);
    // 获取store,并赋值给state
    this.state = store.getState();
  }

  render() {
    return() {
      return (
        <div style={{marginTop: '10px', marginLeft: '10px'}}>
          <div>
            <Input value={this.state.inputValue} placeholder='todo info' style={{width: '300px'}} />
            <Button type="primary">提交</Button>
          </div>
          <List
            style={{marginTop: '10px', width: '300px'}}
            bordered
            dataSource={this.state.list}
            renderItem={item => (<List.Item>{item}</List.Item>)}
          />
        </div>
      )
    }
  }
}

五、Action 和 Reducer 的编写

  • 为了方便调试redux,可以先在chrome浏览器中安装插件 Redux DevTools
  • 为了能正常使用,还要在给createStore方法添加一个参数。(按插件提示添加即可)

redux的工作流程说明

  1. 要想更新state中的数据,首先派发(dispatch)一个action,action通过dispatch方法把类型(type)和新数据传给store。
  2. store把之前的数据(previousState)和传过来的action转发给reducers函数。
  3. reducers接收previousState和action后进行数据处理,重新生成一个newState(原state只读不改),把newState作为返回值返回给store。
  4. store接收newState,将新数据替换原来的数据。
  5. react组件中观测(store.subscribe)到数据发生改变,会从store里面重新取数据(state)设置(setState)state,更新组件的内容,页面发生变化。

Redux的工作流程

六、使用redux编写todoList代码

src/store/reducer.js中的内容

// 设置一个默认值,如果state没有值则使用这个默认值
const defaultState = {
    inputValue:'123',
    list: [1, 2]
}
// 导出一个函数(第一个参数是之前的state,第二个值数是一个action对象,包括类型和传值)
export default (state = defaultState, action) => {
  if (action.type === 'change_input_value') {
    const newState = JSON.parse(JSON.stringify(state));
    newState.inputValue = action.value;
    return newState;
  }
  if (action.type === 'add_todo_item') {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.push(newState.inputValue);
    newState.inputValue = '';
    return newState;
  }
  if (action.type === 'delete_todo_item') {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.splice(action.index, 1);
    return newState;
  }
  return state;
}

src/store/index.js中的内容

// 引入createStore方法
import { createStore } from 'redux';
// 引入reducer
import reducer from './reducer';
// 创建store,并把reducer中导出的方法传进来
const store = createStore(reducer);

export default store;

在组件中的内容

import React, {Component} from 'react';
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
import { Input, Button, List } from 'antd';
// 引用store
import store from './store';

class TodoList extends Component {
  constructor(props) {
    super(props);
    // 获取store,并赋值给state
    this.state = store.getState();

    // 统一在constructor中绑定this,提交性能
    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleStoreChange = this.handleStoreChange.bind(this);
    this.handleClick = this.handleClick.bind(this);

    // 在组件中订阅store,只要store改变就触发这个函数
    this.unsubscribe = store.subscribe(this.handleStoreChange);
  }

  // 当store状态改变时,更新state
  handleStoreChange() {
    // 用从store中获取的state,来设置state
    this.setState(store.getState());
  }

  render() {
    return (
      <div style={{margin: '10px'}}>
        <div className="input">
          <Input
            style={{width: '300px', marginRight: '10px'}}
            value={this.state.inputValue}
            onChange={this.handleInputChange}
          />
          <Button type="primary" onClick={this.handleClick}>提交</Button>
        </div>
        <List
          style={{marginTop: '10px', width: '300px'}}
          bordered
          dataSource={this.state.list}
          renderItem={(item, index) => (<List.Item onClick={this.handleDelete.bind(this, index)}>{item}</List.Item>)}
        />
      </div>
    )
  }

  // 组件注销前把store的订阅取消
  componentWillUnmount() {
    this.unsubscribe();
  }

  // 输入内容时(input框内容改变时)
  handleInputChange(e) {
    const action = {
      type: 'change_input_value',
      value: e.target.value
    }
    // 当input框中数据改变时,交给store去处理
    store.dispatch(action);
  }
  // 添加一项
  handleClick () {
    const action = {
      type: 'add_todo_item',
    }
    store.dispatch(action);
  }
  // 点击删除当前项
  handleDelete (index) {
    const action = {
      type: 'delete_todo_item',
      index
    }
    store.dispatch(action);
  }
}

export default TodoList;

七、ActionTypes 的拆分

使用常量代替action中的type字符串
因为如果action的type中字符串,如果拼错了,不会报错,很难找到问题。但是使用常量如果写错了控制台会报错,方便排错。

  1. 在store目录中新文件actionTypes.js,定义一些常量。内容如下
const types = {
  CHANGE_INPUT_VALUE: 'change_input_value',
  ADD_TODO_ITEM: 'add_todo_item',
  DELETE_TODO_ITEM: 'delete_todo_item'
}
export default types;
  1. 在组件中引入这些常量
import types from './store/actionTypes';

...

...

  // 输入内容时(input框内容改变时)
  handleInputChange(e) {
    const action = {
      type: types.CHANGE_INPUT_VALUE,
      value: e.target.value
    }
    // 当input框中数据改变时,交给store去处理
    store.dispatch(action);
  }
  // 添加一项
  handleClick () {
    const action = {
      type: types.ADD_TODO_ITEM,
    }
    store.dispatch(action);
  }
  // 点击删除当前项
  handleDelete (index) {
    const action = {
      type: types.DELETE_TODO_ITEM,
      value: index
    }
    store.dispatch(action);
  }
  1. src/store/reducer.js中的内容改成
import types from './actionTypes';

// 设置一个默认值,如果state没有值则使用这个默认值
const defaultState = {
  inputValue: '',
  list: ['学习英语', '学习React']
}
// 导出一个函数(第一个参数是之前的state,第二个值数是一个action对象,包括类型和传值)
export default (state = defaultState, action) => {

  const { type, value } = action;
  const newState = JSON.parse(JSON.stringify(state));

  switch(type) {
    case types.CHANGE_INPUT_VALUE:
      newState.inputValue = value;
      break;
    case types.ADD_TODO_ITEM:
      newState.list.push(newState.inputValue);
      newState.inputValue = '';
      break;
    case types.DELETE_TODO_ITEM:
      newState.list.splice(value, 1);
      break;
    default:
      return state
  }
  return newState;
}

八、使用 actionCreator 统一创建 action

在store文件夹下创建一个actionCreators.js,把action都集中写在这个文件中统一管理。这样子看上去多了一个步骤,但是会方便后期维护和自动化测试。

具体代码如下

  1. 在store目录中新文件actionCreators.js,把action都集中写在这个文件中。内容如下
import types from './actionTypes';

// 返回的是action对象
export const inputChangeAction = (value) => ({
  type: types.CHANGE_INPUT_VALUE,
  value: value
})

export const addItemAction = (value) => ({
  type: types.ADD_TODO_ITEM
})

export const deleteItemAction = (index) => ({
  type: types.DELETE_TODO_ITEM,
  value: index
})
  1. 在组件中引入这些action
import { inputChangeAction, addItemAction, deleteItemAction } from './store/actionCreators';

...

...

// 输入内容时(input框内容改变时)
handleInputChange(e) {
  const action = inputChangeAction(e.target.value);
  store.dispatch(action);
}

// 添加一项
handleClick () {
  const action = addItemAction();
  store.dispatch(action);
}

// 点击删除当前项
handleDelete (index) {
  const action = deleteItemAction(index);
  store.dispatch(action);
}

九、Redux 知识点总结及补充

Redux三个基本原则:

  1. store必须是唯一;
  2. 只有store能够改变自己的内容(reducer只是返回newState让store更新state,而不是改变state);
  3. reducer必须是纯函数。

纯函数:纯函数指的是,给定固定的输入,就一定会返回固定的输出(所以不能有ajax请求、new Date()和定时器)。而且不会有任何副作用(如不能修改store的内容,要先复制一份再去操作)。

Redux的API

createStore:创建store仓库
store.dispatch:派发action,并传递给store
store.getState:获取store中所有的内容
store.subscribe:订阅store的改变

文章标签: