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