众所周知,React虽然在前端大放异彩,但是根本上来说,它只是一个View层的框架,一般初学者都会使用Facebook官方的Flux作为数据管理框架,然而在2015年,Redux作为另一个更专业的数据框架异军突起,甚至在GitHub上的star超过了Flux,我们先看官网的介绍:
Redux is a predictable state container for JavaScript apps.
从简介上可以看出,Redux已经不满足于简单做一个数据框架了,而是希望成为一个状态容器,在React中使用Redux,本质上只是用React作为View来展现Redux中的状态罢了。而且Redux的代码量并不大,并且是用ES6的语法编写的,还有着充分的注释,阅读起来非常舒服,大致浏览一遍后,我决定通读这份代码,现在我使用Redux也算是得心应手了,就当是在“知其然”的同时,也努力做到“知其所以然”吧。
src/index.js
import createStore from './createStore'
import combineReducers from './utils/combineReducers'
..
export {
createStore,
combineReducers,
...
}
这个是整个项目的基础文件,从这个文件我们可以看出,Redux一开始就没有向传统前端妥协,而是专为模块化准备的,整个项目的功能都需要通过require/import来使用,没有导出任何全局变量来供前端通过script加载来使用。
src/createStore.js
import isPlainObject from './utils/isPlainObject'
export var ActionTypes = {
INIT: '@@redux/INIT'
}
import了一个isPlainObject
方法,用来检测一个变量是否为简单对象,并且定义了一个保留的ActionType,当使用这个保留的ActionType时,将会返回初始的state。
export default function createStore(reducer, initialState) {
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
var currentReducer = reducer
var currentState = initialState
var listeners = []
var isDispatching = false
这个文件默认export一个名为createStore
的函数,这个函数接受reducer
和初始state为参数,reducer的类型必须是函数,否则会抛出异常。
然后定义了一系列变量,用来保存当前的reducer和state,一个listener数组用于观察者模式,并且初始store不处在dispatch action的状态。
function getState() {
return currentState
}
获得当前state。
function subscribe(listener) {
listeners.push(listener)
var isSubscribed = true
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
var index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
这个函数就是实现观察者模式,通过subscribe
函数给state添加一个listener,这个listener会被加入到listeners数组中。
添加listener的代码没有什么好说的,真正有趣的是在后面移除listener的函数,这里就是函数thunk化的思想,我设想了一下,如果由我来编写这个unsubscribe
函数,我可能会把这个函数像subscribe一样定义,然而在这段代码中,这个函数被作为了subscribe函数的返回值,也就是说,当我们初始化一个store的时候,这个store是没有unsubscribe函数的,只有当我们给一个store加上listener的时候,unsubscribe函数便会自动创建,并且不用传入listener,因为unsubscribe函数形成了一个闭包,listener已经在初始化unsubscribe函数的时候成为了它的内部变量。
示例代码:
// original, written by me
store.subscribe(handler);
store.unsubscribe(handler);
// current, in Redux
var listener = store.subscribe(handler);
listener.unsubscribe();
显然后者更优雅一点。
function dispatch(action) {
if (!isPlainObject(action)) {
...
}
if (typeof action.type === 'undefined') {
...
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
listeners.slice().forEach(listener => listener())
return action
}
这里实现了store里的dispatch函数,这个函数接受一个action对象,并且根据action里的内容,分配到相应的reducer来对state进行更改,一个action能否生效有三个前提:
- action是一个
PlainObject
,即朴素对象,简单的说就是通过对象字面量创建的对象; - action对象必须包含名为
type
的key; - store不能正在出于dispatch的状态,也就是同一个时刻只能有一个action在改变store内容,确保store稳定。
然后,把isDispatching
置为true,也就是说当前store进入dispatch状态,并且把当前state和action传入当前reducer做处理。
处理完成后,把listensers数组复制一遍,至于为什么要复制而不是直接使用listeners数组,我猜可能是为了在执行前后保证listeners数组内容不变吧,然后遍历数组把函数都执行一边。
最后,返回action对象。
function replaceReducer(nextReducer) {
currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
}
这个方法用来替换当前的reducer,当需要动态变更reducer的时候可以使用这个方法,当然一般是不用的,然后对state进行初始化
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer
}
}
初始化state,并且暴露几个关键方法。到这里,createStore.js这个文件就结束了。