深入理解Redux

1,468 阅读7分钟


                              

Redux的前世今生

在理解Redux之前,我们应该要知道Redux的开发目标和实现理念。

Redux的作者是Dan Abramov

Dan 在他准备 React Europe 演讲《热加载和时间旅行》时开始开发 Redux。

当时的目标是创建一个状态管理库,来提供最简化 API,但同时做到行为的完全可预测,以实现日志打印,热加载,时间旅行,同构应用,录制和重放。

我们知道这时候React已经很火了。

同时还有一个配套React的状态管理库,他叫 Flux……


React和Flux都是Facebook内部孵化的项目,至少当时Flux是为了配合React而开发的。

Flux有以下几个特点:

1、单向数据流。

2、Store 可以有多个。

3、Store 不仅存放数据,还封装了处理数据的方法。

4、视图事件发出 Action ,经由 Dispatcher 派发给 Store。

下面一段代码很清晰地体现了Flux的特点:


多Store思想,是Flux现在不够流行原因之一,但是它作为先驱开创先河的精神是值得肯定的。

Flux是一个库,逐渐演变成一种架构思想。

Flux的核心思想就是共享状态和数据和逻辑永远单向流动。

从写一段Redux开始

Redux站在巨人的肩膀上,有效借鉴了Flux的思想并做了优化。

Redux的特点:

1、单向数据流。

2、单一数据源,唯一Store。

3、Store 不仅存放数据,还封装了处理数据的方法。

4、没有 Dispatcher ,而是在 Store 中集成了 dispatch 方法,store.dispatch() 是 View 发出 Action 的唯一途径。

写一段Redux代码:

import { createStore } from 'redux';

const initState = {
  count: 0,
};
function reducer(state = initState, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    default:
      return state;
  }
}
const store = createStore(reducer);

store.subscribe(() => {
  console.log('redux state:', store.getState());
});
store.dispatch({
  type: 'increment',
});
export default store;

一个简单的redux实现就是这样。

store.dispatch发出一个action,action必须是对象类型并且要附有type属性。

action会被发送到reducer中作为第二参数,reducer函数返回值会被用来更新store。

reducer的第一参数是上一次的state对象,当首次调用reducer时,state为undefined,这时候initState会被赋值为state,这是也就是初始化state的推荐方式。

Redux的三驾马车:

Store: 存储应用 state 以及用于触发 state 更新的 dispatch 方法等,整个应用仅有单一的 Store 。

Action: Action 是用于更新 state 的消息对象,必须由dispatch发出。

Reducer: 是一个用于改变 state 的纯函数

reducer必须是一个纯函数,它是修改state的唯一途径。这保证了单向数据流,同时每次返回一个完整的state对象,Redux可以记录这些变化以实现时间旅行。

Redux vs. Vuex


在对比Vuex之前我们也先来写一段Vuex代码:

import Vuex from 'vuex';

const store = new Vuex.Store({
  state: {
    count: 0,
  },
  mutations: {
    increment(state) {
      state.count += 1;
    },
  },
  actions: {
    increment({ commit }) {
      commit('increment');
    },
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment');
      }, 1000);
    },
  },
  modules: {
  },
});

store.subscribe((mutation, state) => {
  console.log('vuex state:', state);
});

store.dispatch('increment');
store.dispatch('incrementAsync');

export default store;

但是这时候会报错:


这说明Vuex与Vue必须绑定使用,它不是一个独立的状态管理方案。

在补充上 Vue.use(Vuex) 之后,代码可以正常运行。

Vuex同样是通过dispatch发出一个action通知,这个action通知可以是String也可以使用action type对象的方式发出。

actions通知会触到到对应的action方法,通过action方法的commit触发对应的mutation。与Redux action不同的是Vuex action可以支持异步方式。

mutation是修改state的唯一途径。但是mutation不需要返回完整的state,而是可以直接在state上修改属性。这是因为Vuex的观察者模式是使用defineProperty的setter实现的,在修改数据的时候setter会收集到修改内容,Vuex会记录这些修改,同样可以实现时间旅行功能。

Vuex出自Redux之后,Vuex的定制化使得我们在Vue中感到特别顺滑,但是这也限制了Vuex的使用场景。

到这里我们已经可以很明显地对比Vuex和Redux了。

Redux vs. Vuex:

1、Vuex和Redux都是Flux思想的一种实现,由于目的不一样,所以实现的方案不一样。

2、Redux目的是低版本兼容以及通用;Vuex是为Vue量身打造。

3、Redux实现时光旅行是使用Reducer方法返回存快照;Vuex使用setter,用户无感。

4、 Redux本身不支持异步数据流(副作用),是理论的标准实现;Vuex支持异步数据,更贴合业务。

5、Vuex(in Vue) ≈ Redux + Redux-thunk(或其他任意异步方案)+ mina-redux (in React )

Redux和Vuex是兄弟关系。


在小程序中使用Redux

我是在2017年开始使用React + Redux,后面一直延续了两年,项目结构一直都大同小异。

一个经典的react项目依赖:

React + React-router + React-redux + Redux + Redux-thunk + Immutablejs

这里必须鼓吹一下Immutablejs,Immutablejs是个持久化数据结构的实现,持久化数据就是一旦创建,就不能再被更改的数据,这个思想在JS中特别棒。可惜被复杂的API拖累,Immutablejs并不是特别普及,经常用来做React的性能优化补充。有兴趣的可以自己去了解一下,这里就不细说了。

后来工作中有一次需要搭建一个微信小程序框架,在对比了 taro、mpvue、wepy 等许多方案后,最终还是决定使用原生小程序框架开发。

当然现在 taro 它们都是一个很优秀的且完善的框架了,但是在当时这些框架的open issue和文档真的有点劝退,直到今天我还是一直坚持使用原生小程序框架做开发。

选定了使用原生小程序框架开发也是遇到了许多问题,其中之一就是状态管理,也是找了许多方案,但是都不是很满意,最后自己写了一个简单的开源库—miniprogram-sync-state


miniprogram-sync-state借鉴了React-redux的API,目的是降低学习成本和无侵入的代码风格。这个库在小型项目中使用是没问题的,但是在大型项目中可能就显得捉襟见肘(无模块化是最主要的问题)。

后来在我行开发项目的时候就想使用这个模式连接Redux在小程序使用,所以就开发出了minapp-redux。掘金地址

在开发minapp-redux的时候也考虑了几个问题:

1、为什么是redux?

redux作为一个最好的flux最好的实现之一,他拥有庞大的社区支持与完善的插件支持。并且相对于vuex的定制化,redux作为一个极简的状态管理系统(通过第三方库实现定制化),天生有易于移植、适用于任何框架的基因。 事实上,minapp-redux也可以归属于redux的定制化连接库之一。

2、运行时如何map?

由于小程序规范,data中的数据必须在初始化page对象时存在,minapp-redux提供的connect语法会在小程序初始化时调用,使用Page(connect(stateMapFun, methodMapFun)(pageObject))语法,pageObject保持了你原本page对象的完整性,便于项目移植或移除minapp-redux。在后续redux状态改变的时候,使用redux API subscribe监听状态变化,并通过保存store实例使用setData更新到视图。

3、如何优化性能?

minapp-redux使用了专属于小程序的diff更新机制,在有属性变化的时候会比较新store返回的stateMap与page对象中当前data的属性,并且抽取出变化属性的集合(丢弃相同值的属性),保证最小粒度的更新。 值得注意的一点是,有时候我们会使用特别复杂的store对象作为map属性,而对这些复杂对象的diff对比会带来比较大的开销,所以minapp-redux使用浅比较,当遇到map数据类型为Object时,会将整个对象直接赋值更新。


最终形成了一套完整的小程序状态管理方案:

minapp-redux + redux + redux-thunk + redux-logger

这套方案已经我行两个线上项目中稳定运行。


以上就是我对Redux的理解以及使用Redux的经验,希望能对大家有所帮助。

谢谢!

—— The End