redux流程理解

image.png
所谓redux,就是将数据集中管理,依托于一个“数据中心”对数据进行增删改查的操作。组件自身并不保存数据。建立一个这样的“数据中心”需要包含如下几个关键点:

  • “数据中心”中需要有存放数据的“仓库”,所有数据都保存在这个仓库中。(store)
  • 组件(component)需要给数据中心发送操作数据的命令,告诉“数据中心”要处理什么数据、做何种操作。这个命令不能是组件想怎么写怎么写,需要“数据中心”预先写好,提供给组件使用。(action)
  • “数据中心”接到组件的操作数据命令,需要真正对数据进行操作,因此需要有一个模块根据组件给出的命令,来对数据进行“加工”(reducer)

以上三步分别对应redux中三个非常关键的三个模块。具体行为如图所示。

redux应用

实现如下功能:一个计数器,下拉框选择加几,加、减、奇数加、异步加四个功能。
redux.gif

action.js

根据上图可知有四个功能,但实际只有两个action,也就是说对数据只有两种操作模式:加、减。奇数加和异步加都是对加的二次封装。因此,定义action只需要两个。一个action就是一个对象该对象包含typedata两个字段,分别表示对操作类型和数据。

  1. export const createIncrementAction = data =>({type: 'INCREMENT', data})
  2. export const createDecrementAction = data =>({type: 'DECREMENT', data})

action.js中定义了两个方法,分别返回加、减的action对象。

reducer.js

reducer.js定义了对action的具体处理,该文件很简单,定义一个reducer,接受两个参数,第一个参数preState为更改前的值,第二个参数是actionaction.js中返回的action对象。

  1. const initState = 0
  2. export default function countReducer(preState=initState, action) {
  3. console.log(preState, action)
  4. const {type, data} = action
  5. switch (type) {
  6. case 'INCREMENT' :
  7. return preState + data
  8. case 'DECREMENT' :
  9. return preState - data
  10. default:
  11. return preState
  12. }
  13. }

store.js

该文件相当简单,通过createStore方法创建一个store对象。store对象内部维护着所有的state和所有的reducer,怎么理解所有的?想一想,如果有一个计数数据count、有两个操作数据方法(加、减)reducer,对应两个action。这个数据count只是state中的一个。state中会有许多数据、对应许多reducer。因此store可以理解为一个“数据中心”。

  1. import {createStore} from 'redux'
  2. import countReducer from './count_reducer'
  3. export default createStore(countReducer)

Count.jsx 组件

根据上面的动图,写出如下代码。有几个关键点:

  • 取数据使用store.getState()
  • 通过createDecrementAction, createIncrementAction方法创建的action对象需要调用store.dispatch方法将action传递至store“数据中心”等待接下来处理。
  • redux有个大问题,数据虽然改变了但不会引起视图的改变。因此,需要在componentDidMount生命周期钩子方法中通过store.subscribe(callback)监听store中的数据变化。一旦发生变化,调用this.setState({})从而引起视图rerender。 ```javascript // 引入store,用于获取redux中保存状态 import store from “../../redux/store”; // 引入actionCreator,专门用于创建action对象 import {createDecrementAction, createIncrementAction} from “../../redux/count_action”; class Index extends Component { componentDidMount() {

    1. // 监测redux中的状态变化,只要变化就调用render(通过调用setState手动rerender)
    2. // 好怪啊
    3. store.subscribe(() => {
    4. this.setState({})
    5. })

    }

    increment = () => {

    1. const {value} = this.selectNumber
    2. store.dispatch(createIncrementAction(value*1))

    } decrement = () => {

    1. const {value} = this.selectNumber
    2. store.dispatch(createDecrementAction(value*1))

    } incrementIfOdd = () => {

    1. const {value} = this.selectNumber
    2. const count = store.getState()
    3. if (count % 2 !== 0) {
    4. store.dispatch(createIncrementAction(value*1))
    5. }

    } incrementAsync = () => {

    1. const {value} = this.selectNumber
    2. const count = store.getState()
    3. setTimeout(() => {
    4. store.dispatch(createIncrementAction(value*1))
    5. }, 500)

    }

  1. render() {
  2. return (
  3. <div>
  4. <h1>当前求和为:{store.getState()}</h1>
  5. <select ref={c => this.selectNumber = c}>
  6. <option value="1">1</option>
  7. <option value="2">2</option>
  8. <option value="3">3</option>
  9. </select>
  10. <button onClick={this.increment}>+</button>
  11. <button onClick={this.decrement}>-</button>
  12. <button onClick={this.incrementIfOdd}>当前求和为奇数加</button>
  13. <button onClick={this.incrementAsync}>异步加</button>
  14. </div>
  15. );
  16. }

}

  1. <a name="F8Bdt"></a>
  2. ### 多数据管理
  3. 通常,redux中不可能只有一个数据需要维护。对于一个复杂的系统,例如图书管理系统,需要维护书籍信息`books` 注册用户信息`users`等等。不同的数据对应不同的reducer。再次同样做一个演示,例子仍旧是上面的计数器的例子,还加了一个person数据(虽然这个person数据和计数器没有任何关联,仅仅只是展示作用)
  4. <a name="ocxTY"></a>
  5. #### action文件
  6. ```javascript
  7. export const createIncrementAction = data =>({type: 'INCREMENT', data})
  8. export const createDecrementAction = data =>({type: 'DECREMENT', data})
  1. export const createAddPersonAction = data => ({type: 'ADD_PERSON', data})

reducer文件

  1. const initState = 0
  2. export default function countReducer(preState=initState, action) {
  3. console.log(preState, action)
  4. const {type, data} = action
  5. switch (type) {
  6. case 'INCREMENT' :
  7. return preState + data
  8. case 'DECREMENT' :
  9. return preState - data
  10. default:
  11. return preState
  12. }
  13. }
  1. const initState = [{name: "Tom", age: 18}]
  2. export default function personReducer(preState=initState, action) {
  3. const {type, data} = action
  4. switch (type){
  5. case ADD_PERSON:
  6. return [data, ...preState] // 返回一个新对象哦
  7. default:
  8. return preState
  9. }
  10. }

store.js

  1. import {createStore, combineReducers} from 'redux'
  2. import countReducer from './reducers/count'
  3. import personReducer from "./reducers/person";
  4. // 汇总所有reducer为一个总reducer
  5. const allReducer = combineReducers({
  6. count: countReducer,
  7. person: personReducer
  8. })
  9. export default createStore(allReducer)

此时的store数据,是key-value形式的。当使用state.getState()获取对象时,获取的是如下形式:

  1. {
  2. count: {...},
  3. person: {...}
  4. }

React-redux

前面所说的redux并不是react官方实现的,所以看得到视图更新需要用this.setState({})来触发,说实话这代码挺丑的。因此,react官方对redux进行了实现,对于redux的三步实现,react-redux并未做任何改动。action、store、reducer的代码都不曾改动。react-redux所作的是更改了获取数据的方式。
image.png
react-redux将组件分为了container组件和UI组件。UI组件就负责正常的页面逻辑,而container组件通过props的方式,专门负责给UI组件提供数据。显然所有的UI组件的父组件都会是对应的container组件。
例子依然是计数器的例子。

container组件

container组件有三个关键点,分别是mapStateToPropsmapDispatchToPropsconnect

  1. mapStateToProps方法的返回值作为状态传递给了UI组件。参数是state,是react-redux自己传进去的。相当于react-redux自己调用了**store.getState()**方法拿到了状态,并且把返回的数据作为参数传递给这个方法了。
  2. mapDispatchToProps方法的返回值作为操作状态的方法传递给了UI组件。参数是dispatch,相当于react-redux自己将**store.dispatch**方法作为参数传递给这个方法。
  3. connect()()可以创建并返回一个容器组件,connect()(CountUI)在第二个参数传入UI组件可建立联系。**connect()()**方法自带状态检测更新视图的能力。 ```jsx // 引入Count的UI组件 import CountUI from ‘../../components/Count’; // 引入connect用于连接CountUI组件和redux import {connect} from “react-redux”; import {createIncrementAction, createDecrementAction} from “../../redux/count_action”;

// mapStateToProps函数的返回值作为状态传递给了UI组件 function mapStateToProps(state) { return {count: state.count} }

// mapDispatchToProps函数的返回值作为操作状态的方法传递给了UI组件 function mapDispatchToProps(dispatch) { return { add: data => { dispatch(createIncrementAction(data)) }, jian: data => { dispatch(createDecrementAction(data)) } } }

// 使用connect()()创建一个Count的容器组件 const CountContainer = connect(mapStateToProps, mapDispatchToProps)(CountUI)

export default CountContainer

  1. <a name="GO5cz"></a>
  2. #### App.jsx
  3. 这里唯一需要注意的是在使用Count组件时不再使用UI组件,而是使用Count的container组件。
  4. ```jsx
  5. import React, {Component} from 'react';
  6. import Count from "./containers/Count";
  7. import store from "./redux/store";
  8. class App extends Component {
  9. render() {
  10. return (
  11. <div>
  12. <Count/>
  13. </div>
  14. );
  15. }
  16. }
  17. export default App;

index.js

index.js作为react项目的最初始入口,我们在这里做了两件事:

  • 引入store,作为props引入到组件中
  • 使用Provider标签包裹整个react app,表示react app中都可以使用redux。 ```javascript import React from ‘react’ import ReactDOM from ‘react-dom’ import App from ‘./App’ import store from “./redux/store”; import {Provider} from “react-redux”;

// app组件里面所有的容器组件都能收到store。 ReactDOM.render( , document.getElementById(‘root’) ) ```