浅谈react状态管理

407 阅读5分钟

浅谈react状态管理 -flux/redux/mobx/dva

react自身是有数据流管理的,通过state和props来更新和传递状态,但是跨组件通讯、状态同步以及状态共享的实现并不理想,需要一个真正的数据流管理工具来专门管理数据。

一、Flux

Flux 的最大特点,就是数据的"单向流动"

Flux的使用

  1. 用户访问 View

  2. View 发出用户的 Action

  3. Dispatcher 收到 Action,要求 Store 进行相应的更新

  4. Store 更新后,发出一个"change"事件

  5. View 收到"change"事件后,更新页面

    Flux将一个应用分成四个部分
  • View: 视图层
  • Action(动作):视图层发出的消息(比如Click)
  • Dispatcher(派发器):用来接收Actions、执行回调函数,Dispatcher 只能有一个,而且是全局的
  • Store(数据层):用来存放应用的状态,一旦发生变动,就提醒Views要更新页面

Flux的优缺点

缺点:

  • store之间需要建立依赖,需要token
  • store 逻辑的修改无法热加载

二、redux

redux是flux演变而来的,是flux的加强版

redux的工作流程

// 用户发出 Action
store.dispatch(action);

// Store 自动调用 Reducer,并且传入两个参数,当前 State 和收到的 Action,Reducer 会返回新的 State
let nextState = todoApp(previousState, action);

// State 一旦有变化,Store 就会调用监听函数
store.subscribe(listener);

// listener可以通过store.getState()得到当前状态,触发重新渲染 View
function listerner() {
  let newState = store.getState();
  component.setState(newState);   
}

redux的使用

组件结构

import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actions from '../redux';

class Main extends Component {
  render () {
    return (
      <div className="App">
        <header className="App-header">
          <p onClick={() => this.props.valueAdd()} > 点击 + 1 </p>
          <p onClick={() => this.props.valueReduce()} > 点击 - 1 </p>
          <div> i am {this.props.number} </div>
        </header>
      </div>
    )
  }
}

export default connect(
  ({number}) => ({number}),
  dispatch => bindActionCreators(action, dispatch)
)(Main)

redux-saga

解决异步等问题,根据相应中间件调整store的生成方式

import { applyMiddleware, createStore } from 'redux';
import createSagaMiddleware from "redux-saga";
import rootSaga from "./saga" 
import reducer from './reducer'
let sagaMiddleware = createSagaMiddleware()
 
export const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware),
);
sagaMiddleware.run(rootSaga); // 开始执行rootSaga

sage文件

function * add () {
  // 派发Action
  yield put({ type: ACTION_ADD })
}
 
function * reduce () {
  yield put({type: ACTION_REDUCER})
}
 
function * watchAdd() {
 // 监听派发给仓库的动作,如果动作类型匹配的话,会执行对应的监听生成器
  yield takeEvery(ACTION_ADD_SAGA, add)
  yield takeEvery(ACTION_REDUCER_SAGA, reduce)
}
 
export default function * rootSaga() {
  yield watchAdd()
}
put 派发Action, 可以理解成为redux中的dispatch函数
call 调用函数执行,阻塞
fork 调用函数执行,不会阻塞

redux图解

示例背景: TodoList = Todo list + Add todo button

React 只负责页面渲染, 而不负责页面逻辑, 页面逻辑可以从中单独抽取出来, 变成 store

img

  1. 状态及页面逻辑从里面抽取出来, 成为独立的 store, 页面逻辑就是 reducer
  2. 和都是 Pure Component, 通过 connect 方法可以很方便地给它俩加一层 wrapper 从而建立起与 store 的联系: 可以通过 dispatch 向 store 注入 action, 促使 store 的状态进行变化, 同时又订阅了 store 的状态变化, 一旦状态有变, 被 connect 的组件也随之刷新
  3. 使用 dispatch 往 store 发送 action 的这个过程是可以被拦截的, 自然而然地就可以在这里增加各种 Middleware, 实现各种自定义功能, eg: logging

这样一来, 各个部分各司其职, 耦合度更低, 复用度更高, 扩展性更好

加入 Saga

img

上面说了, 可以使用 Middleware 拦截 action, 这样一来异步的网络操作也就很方便了, 做成一个 Middleware 就行了, 这里使用 redux-saga 这个类库, 举个栗子:

  1. 点击创建 Todo 的按钮, 发起一个 type == addTodo 的 action
  2. saga 拦截这个 action, 发起 http 请求, 如果请求成功, 则继续向 reducer 发一个 type == addTodoSucc 的 action, 提示创建成功, 反之则发送 type == addTodoFail 的 action 即可

redux的优点

  • 状态持久化:globalstore可以保证组件就算销毁了也依然保留之前状态;
  • 状态可回溯:每个action都会被序列化,Reducer不会修改原有状态,总是返回新状态,方便做状态回溯;
  • Functional Programming:使用纯函数,输出完全依赖输入,没有任何副作用;
  • 中间件:针对异步数据流,提供了类express中间件的模式,社区也出现了一大批优秀的第三方插件,能够更精细地控制数据的流动,对复杂的业务场景起到了缓冲地作用;

redux的缺点

  • 繁重的代码模板:修改一个state可能要动四五个文件,可谓牵一发而动全身;
  • store里状态残留:多组件共用store里某个状态时要注意初始化清空问题;
  • 无脑的发布订阅:每次dispatch一个action都会遍历所有的reducer,重新计算connect,这无疑是一种损耗;
  • 交互频繁时会有卡顿:如果store较大时,且频繁地修改store,会明显看到页面卡顿;
  • 不支持typescript;

三、mobx

MobX的数据驱动解构: action–(update)–>state–(update)–>computed–(trigger)–>reaction

Mobx的核心原理是通过action触发state的变化,进而触发state的衍生对象(computed value & Reactions)

核心概念

Mobx的核心就是通过observable观察某一个变量,当该变量产生变化时,对应的autorun内的回调函数就会发生变化。

mobx异步处理

Mobx在异步处理上并不复杂,在严格模式下,对于异步action里的回调,若该回调也要修改observable的值,那么该回调也需要绑定action

mobx的优缺点

优点:

  • 响应式编程,学习成本低,代码量少,开发难度低,渲染性能好

四、Dva

Dva 是基于 React + Redux + Saga 的最佳实践沉淀, 做了 3 件很重要的事情, 大大提升了编码体验:

  1. 把 store 及 saga 统一为一个 model 的概念, 写在一个 js 文件里面
  2. 增加了一个 Subscriptions, 用于收集其他来源的 action, eg: 键盘操作
  3. model 写法很简约

model代码示例

app.model({
  namespace: 'count',
  state: {
    record: 0,
    current: 0,
  },
  reducers: {
    add(state) {
      const newCurrent = state.current + 1;
      return { ...state,
        record: newCurrent > state.record ? newCurrent : state.record,
        current: newCurrent,
      };
    },
    minus(state) {
      return { ...state, current: state.current - 1};
    },
  },
  effects: {
    *add(action, { call, put }) {
      yield call(delay, 1000);
      yield put({ type: 'minus' });
    },
  },
  subscriptions: {
    keyboardWatcher({ dispatch }) {
      key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
    },
  },
});

dva的图解

参考链接:

www.yuque.com/flying.ni/t…

www.ruanyifeng.com/blog/2016/0…

blog.csdn.net/nx2xjbur4jg…

blog.csdn.net/weixin_3416…

blog.csdn.net/weixin_4183…

blog.csdn.net/weixin_4183…