Redux

Redux数据管理

Posted by SkioFox on December 3, 2018

Redux流程图

avatar

redux中的角色

  • Store:存储状态,访问状态,提交状态更新,监听状态变更
  • Reducer:状态更新的执行者,是纯函数
  • Action:存放数据的对象,消息的载体,只能被别人操作,自己不能进行任何操作
  1. redux的基本使用

创建store和reducer

  // store.js
  // 引入createStore函数
  import {createStore} from 'redux'
  // reducer具体状态更新的执行者, reducer是纯函数:传入的值和返回值是固定的可预测的。
  const counterReducer=(state=0,action)=>{
    switch (action.type) {
      case 'add':
        return state+1
      case 'minus':
        return state-1
      default:
        return state
    }
  }
  // 创建store=>将reduce传给store
  const store = createStore(counterReducer)

  export default store

在组件中使用redux


  // reduxTest组件中使用store
  import React, { Component } from "react"
  import store from '../store'
  export default class ReduxTest extends Component {
    render() {
      return (
        <div>
          <!--获取状态-->
          <p>{store.getState()}</p>
          <div>
            <button onClick={() => store.dispatch({type:'add'})}>+</button>
            <button onClick={() => store.dispatch({type:'minus'})}>-</button>
          </div>
        </div>
      );
    }
  }

最原始的使用方法,监听store变化更新render函数


  // 连接store和component
  // index.js
  import store from './store'

  function render(){
    ReactDOM.render(<ReduxTest/>, document.querySelector('#root'))
  }
  render()
  // 监听:store的订阅方法,当store有变化时执行回调函数
  store.subscribe(render)
  

总结步骤:

  • 创建reducer
  • 创建store
  • 获取getState
  • 修改状态dispatch
  • 订阅状态变更subscribe
  1. react-redux整合
  • 提供了两个api

    • Provider顶级组件,提供数据
    • connect高阶组件, 提供数据和方法

使用Provider组件封装组件,通过属性传递store

react-redux积层总结

  • 创建Action模块
  • 创建Reducer模块
  • 创建Store模块
  • 通过connect方法将React组件和Redux连接起来
  • 添加Provide作为项目的根组件,用于数据的存储
  // index.js
  import {Provider} from 'react-redux'
    // 通过属性的方式将store传递
  ReactDOM.render((
      <Provider store={store}>
        <ReduxTest/>
      </Provider>
      ), document.querySelector('#root'))

  import React, { Component } from "react"
  // connect方法是个工厂函数,类似高阶组件
  import {connect} from 'react-redux'

  // 装饰器写法 connect返回的才是高阶组件,用于去包装ReduxTest
  @connect(
    // 状态映射
    state => ({num: state}),
    // type映射
    {
      add: ()=>({type:'add'}),
      minus: ()=>({type:'minus'})
    }
  )

  class ReduxTest extends Component {
    render() {
      return (
        <div>
          {/*用属性获取*/}
          <p>{this.props.num}</p>
          <div>
            <button onClick={()=>this.props.add()}>+</button>
            <button onClick={()=>this.props.minus()}>-</button>
          </div>
        </div>
      );
    }
  }
  // 配置函数的作用:将组件中需要的state和dispatch方法映射到属性中
  // const mapStateToProps = state => ({num: state})
  // const mapDispatchToProps = dispatch => ({
  //   add: ()=>dispatch({type:'add'}),
  //   minus: ()=>dispatch({type:'minus'})
  // })
  // connect本身并不是高阶组件,需要传递两个特别的配置函数,生成的函数才是高阶组件
  // 导出高阶组件封装的组件
  // export default connect(
  //   mapStateToProps,
  //   mapDispatchToProps
  // )(ReduxTest)

  // 装饰器写法
  export default ReduxTest
  1. Mobx与redux对比
  • 难度:redux > mobx
  • 工作量:redux > mobx
  • 内存开销:redux >mobx
  • 状态管理的集中性:redux > mobx
  • 样板代码的必要性:redux > mobx

redux中间件机制

利用redux中间件机制可以在实际action响应前执行其他额外的业务逻辑 avatar

  1. 使用redux-thunk和redux-logger实现异步任务和日志记录
  //store.js
  import {createStore, applyMiddleware} from 'redux'
  import logger from 'react-logger'
  import thunk from 'redux-thunk'
  const counterReducer=(state=0,action)=>{
    switch (action.type) {
      case 'add':
        return state+1
      case 'minus':
        return state-1
      default:
        return state
    }
  }

  export default createStore(counterReducer,applyMiddleware(logger,thunk))
  // ReduxTest.js
  import React, { Component } from "react"
  import {connect} from 'react-redux'

  @connect(
    // 状态映射
    state => ({num: state}),
    // type映射
    {
      add: ()=>({type:'add'}),
      minus: ()=>({type:'minus'}),
      // 本身是个函数,函数执行后又返回一个函数。返回的函数传递一个dispatch,在异步操作后可以dispatch一个修改state的action
      // 正常的action返回的是一个对象如上add,minus=>这里使用trunk可以处理返回函数类型,只要是函数就当作异步处理
      asyncAdd: ()=> (dispatch) => {
        // 模拟异步操作
        setTimeout(()=>{
          // 异步结束后手动执行dispatch
          dispatch({type:'add'})
        },1000)
      }
    }
  )

  class ReduxTest extends Component {
    render() {
      return (
        <div>
          <p>{this.props.num}</p>
          <div>
            <button onClick={()=>this.props.add()}>+</button>
            <button onClick={()=>this.props.minus()}>-</button>
            <button onClick={()=>this.props.asyncAdd()}>asyncAdd</button>
          </div>
        </div>
      );
    }
  }
  export default ReduxTest

  // index.js
  import {Provider} from 'react-redux'
    // 通过属性的方式将store传递
  ReactDOM.render((
      <Provider store={store}>
        <ReduxTest/>
      </Provider>
      ), document.querySelector('#root'))
  1. 整合redux,将counter相关的reducer和逻辑放到一起, 简化ReduxTest和index.js。所有与counter相关的业务逻辑都放到counter.redux.js中。
  // store/counter.redux.js
  export default (state = 0, action) => {
    switch (action.type) {
      case "add":
        return state + 1;
      case "minus":
        return state - 1;
      default:
        return state;
    }
  };

  function add() {
    return { type: "add" };
  }

  function minus() {
    return { type: "minus" };
  }

  function asyncAdd() {
    return (dispatch, getState) => {
      console.log(getState());
      // 模拟异步操作
      setTimeout(() => {
        dispatch({ type: "add" });
      }, 1000);
    };
  }

  export {add, minus, asyncAdd}

  // store/index.js
  import {createStore, applyMiddleware} from 'redux'
  import logger from 'react-logger'
  import thunk from 'redux-thunk'
  import counterReducer from './counter.redux.js'

  export default createStore(counterReducer,applyMiddleware(logger,thunk))
  // ReduxTest.js
  import React, { Component } from "react";

  // import store from '../store'
  import { add, minus, asyncAdd } from "../store/counter.redux";
  import { connect } from "react-redux";

  @connect(
    state => ({ num: state.counterReducer }), // 状态映射
    { add, minus, asyncAdd } // action 映射
  )
  class ReduxTest extends Component {
    render() {
      return (
        <div>
          <p>{this.props.num}</p>
          <div>
            <button onClick={() => this.props.minus()}>-</button>
            <button onClick={() => this.props.add()}>+</button>
            <button onClick={() => this.props.asyncAdd()}>asyncAdd</button>
          </div>
        </div>
      );
    }
  }

  export default ReduxTest;

redux 模块化和redux-saga

  // store/index.js

  import { createStore, applyMiddleware, combineReducers } from "redux";
  import logger from "redux-logger";
  import thunk from "redux-thunk";
  import counter from "./counter.redux";
  import user from "./user.redux";
  import createSagaMiddleware from 'redux-saga';
  import saga from './sagas';

  // 1.创建中间件
  const mid = createSagaMiddleware();
  // 2.应用中间件
  const store = createStore(
    // reducer模块化
    combineReducers({ counter, user }),
    applyMiddleware(logger, mid)
  )
  mid.run(saga);
  export default store;

  // store/sagas.js 异步处理中间件redux-saga
  import { call, put, takeEvery } from "redux-saga/effects";

  const api = {
    login() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (Math.random() > 0.5) {
            resolve({ id: 1, name: "jerry" });
          } else {
            reject("用户名或密码错误");
          }
        }, 1000);
      });
    }
  };

  // work saga 实现请求逻辑的地方
  // 这是使用了generator生成器
  function* login(action) {
    try {
      // 用call调用异步方法api.login,也可以传参call(api.login,{})
      const result = yield call(api.login);
      yield put({ type: "login", result });
    } catch (error) {
      yield put({ type: "login_failure", message: error.message});
    }
  }
  // 监听器
  function* mySaga(){
      yield takeEvery('login_request', login);
  }

  export default mySaga;
  // store/user.redux.js
  const initialState = {
    isLogin: false
  };

  export default (state = initialState, { type, payload }) => {
    switch (type) {
      case 'login':
        return {isLogin: true};

      default:
        return state;
    }
  };
  // for redux-thunk
  // export function login(){
  //     return (dispatch)=>{
  //         // mock一个异步登录
  //         setTimeout(()=>{
  //             dispatch({type:'login'})
  //         }, 1000)
  //     }
  // }

  // for saga
  export function login(){
    return {type:'login_request'}
  }

redux调试工具

avatar

redux-saga和redux-trunk的区别

redux-trunk可以接收function类型的action,saga则是纯对象action解决方案 saga使用generator解决异步问题,非常方便用同步方法编写异步代码