Redux 解析
August 9, 2016 • ☕️☕️ 9 min read
Redux 为 JavaScript 应用提供可预测化的状态管理。
原理
使用 Redux
之后,数据的流向可以使用下图来表示:
(ps:其实图中的 Dispatcer
并不太准确,Redux
没有专门的 Dispatcher
, 数据的更新是通过 store
中的 dispatch
方法。)
从上图我们可以看到清晰的数据流向: View
触发数据更新 ---> Actions
将数据传递到 Store
---> Store
更新 state
---> 更新 View
。
Redux
中整个应用的状态存储在一棵 object tree
中,对应一个唯一的 Store
,并且 state
是只读的,使用纯函数来更新 state
会生成一个新的 state
而不是直接修改原来的。
Redux
通过以上约束试图让状态的变化可预测。
源码分析
Redux
的源码非常少,但是却实现了这样一个数据流的管理,非常值得阅读学习。
Redux
的源码并不是非常容易理解的,包含很多闭包和高阶函数的使用导致理解起来有点绕。
了解了原理之后,对 Redux
源码的分析,就简单许多。
源码结构:
.
├── applyMiddleware.js
├── bindActionCreators.js
├── combineReducers.js
├── compose.js
├── createStore.js
├── index.js
└── utils
└── warning.js
index.js
从入口文件 index.js
开始,删除了部分 warning
代码
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose
}
从上面可以看出,Redux
暴露的顶层 API
就只有 4 个。
接下来分析每个 API
。
createStore.js
用来创建 store
,其中暴露 dispatch
, subscribe
, getState
, replaceReducer
方法。
/**
* 初始化时,默认传递的 action,默认也应该返回初始化的 state
*/
export var ActionTypes = {
INIT: '@@redux/INIT'
}
/**
* 创建 store, 参数根 reducer, state 以及中间件
*/
export default function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
var currentReducer = reducer
var currentState = preloadedState
var currentListeners = []
var nextListeners = currentListeners
var isDispatching = false
// 去除引用
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
/**
* Reads the state tree managed by the store.
* @returns {any} The current state tree of your application.
*/
function getState() {
return currentState
}
// 订阅事件,返回移除订阅函数,巧妙的利用了闭包
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
var isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
var index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
// 执行 reducer,并触发订阅事件
function dispatch(action) {
// https://lodash.com/docs#isPlainObject
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
// 产生新的 state
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 触发订阅的事件
var listeners = currentListeners = nextListeners
for (var i = 0; i < listeners.length; i++) {
listeners[i]()
}
return action
}
/**
* 动态替换 reducer
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
}
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer
}
}
combineReducers.js
combineReducers
用于拆分 reducer
,拆分后的每一块独立负责管理 state
的一部分,方便管理复杂的应用。
combineReducers
返回一个函数可以将传入的 reducers
都调用一遍合成一个大的 state
。
比如有 reducer
: r1, r2, r3;
将 { r1, r2, r3 }
传入 combineReducers
将返回一个可以产生这样的 state
: { r1: {}, r2: {}, r3: {} }
的函数。
其中很长一段代码都是对 reducers
合法性的检测,这里只需要分析下 combineReducers
函数的实现,一些代码已经删除掉了。
export default function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers)
// 将 reducers 存储在一个对象中
var finalReducers = {}
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i]
... 省略 `reducer` 的合理性检测
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
var finalReducerKeys = Object.keys(finalReducers)
...省略 `reducer` 的不合理的处理
return function combination(state = {}, action) {
var hasChanged = false
var nextState = {}
// 遍历调用 reducers,产生一个大的 state,key 和 reducer 名对应
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
var reducer = finalReducers[key]
var previousStateForKey = state[key]
var nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
bindActionCreators.js
类似 combineReducers
, bindActionCreators
函数把 action creators
转成拥有同名 keys
的对象,
并使用 dispatch
把每个 action creator
包装起来,这样在调用 action
时可以直接调用 dispatch
。
也就是说不需要将 dispatch
手动传入到子组件中了。
// 在 action 外部包装一层 dispatch
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
// 如果是函数,这种情况一般就只有一个 action
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
...
// 遍历 actions ,包装一层 dispatch
var keys = Object.keys(actionCreators)
var boundActionCreators = {}
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
var actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
compose.js
compose
的作用是从右到左来组合多个函数,不使用深度右括号的情况下来写深度嵌套的函数,内部实现其实就是一个 reduceRight
。
关于 reduceRight
, 查看 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/ReduceRight
比如 a(b(c()))
可以这样写 compose(a,b,c)
。
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}
applyMiddleware.js
applyMiddleware
最终返回的是一个作用了 middlewares
的 store
。
之后 store.dispatch
的调用都会经历各个 middleware
,所以你就可以在中间件中做一些额外的事情,比如打印日志。
import compose from './compose'
export default function applyMiddleware(...middlewares) {
// 返回一个接收 createStore 的函数
return (createStore) =>
// 返回一个接收 reducer, preloadedState, enhancer 的函数
(reducer, preloadedState, enhancer) => {
// 创建 store
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch
var chain = []
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// 作用每个 middleware
chain = middlewares.map(middleware => middleware(middlewareAPI))
// 将 dispatch 传给最后一个 middleware
dispatch = compose(...chain)(store.dispatch)
// 返回作用 middleware 之后的 store
return {
...store,
dispatch
}
}
}
每个 middleware
接受 Store
的 dispatch
和 getState
函数作为命名参数,并返回一个函数。
该函数会被传入 被称为 next
的下一个 middleware
的 dispatch
方法,并返回一个接收 action
的新函数,
这个函数可以直接调用 next(action)
,或者在其他需要的时刻调用,甚至根本不去调用它。
调用链中最后一个 middleware
会接受真实的 store
的 dispatch
方法作为 next
参数,并借此结束调用链。
所以,middleware
的函数签名是 ({ getState, dispatch }) => next => action
。
一个简单 middleware
可以这样写:
function logger({ getState }) {
return (next) => (action) => {
console.log('will dispatch', action)
// 调用 middleware 链中下一个 middleware 的 dispatch。
let returnValue = next(action)
console.log('state after dispatch', getState())
// 一般会是 action 本身,除非
// 后面的 middleware 修改了它。
return returnValue
}
}
当调用 store.dispatch
之后,会打印出 will dispatch xxx
, state after dispatch xxx
信息。
结论
Redux
的代码里使用高阶函数来简化代码,但是阅读起来并不容易理解,会明显感觉有些绕。