深入理解Redux之源码解读
去年在几个react-native项目中使用了redux,当时由于理解有限,仅限于实现了功能,代码很糟糕。最近补了下课,试图深入理解Redux。
1.Redux简介
简单来说,Redux是一个应用状态管理工具。本文不涉及Redux基础使用,为更好的理解Redux,引用了一张图来描述Redux数据流程。

上图中可以看出,Redux中几个主要内容包括Action、Reducer、Store、State、Middleware。用户在应用视图层触发的Action,经由层层Middleware,到达Reducer中,经过Reducer的处理,产生新的State。这一过程在Store中发生,最终新的State被用来刷新应用视图,完成一次交互。
2.Redux源码结构
本文代码解读基于redux版本3.6.0版本,源码结构如下

- index.js 输出redux对外暴露的全部接口API
- createStore.js 保存应用状态,定义内部方法,输出对外暴露的接口
- compose.js 为工具方法,将middleware中间件方法组合成一个调用链
- combineReducers.js 将多个reducer合并为一个包裹对象
- bindActionCreators.js 将dispatch(action)进行反转,根据传入action creator,返回一个包裹对象
- applyMiddleware.js 将middleware组合成调用链,并将改造后的dispatch方法应用到store上
3.createStore.js
3.1 代码讲解
createStore代码结构清晰,代码主要内容为
- 参数合法性检查
- 数据初始化
- 内部方法定义
- 调用dispatch({ type: ActionTypes.INIT }) 初始化store的状态
- 返回接口API
import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'
/**
* ActionTypes里定义的是Redux保留的私有action。
* 对于任何未知的action,你必须返回store的当前状态。
* 如果传入的当前状态是undefined,你必须返回store的初始状态。
* 不要在应用代码中直接引用这些action。
*/
export var ActionTypes = {
INIT: '@@redux/INIT'
}
/**
* createStore方法用于创建一个保存程序状态的store。
* 改变store中数据的唯一方法是调用store的`dispatch()`方法。
*
* 你的应用中应该只有一个store。为了将程序状态中不同部分的变更逻辑
* 组合在一起,你可以通过`combineReducers`方法将多个reducer组合成一个reducer。
*
* @param {Function} reducer 一个返回应用下一状态的函数,入参是程序的当前状态以及
* 要发送的action。
*
* @param {any} [preloadedState] store的初始状态。你可以选择性的为store指定一个
* 初始状态。
* 如果你使用了`combineReducers`方法来生成最终的reducer。那么这个初始状态对象的
* 结构必须与调用`combineReducers`方法时传入的参数的结构保持对应关系。
*
* @param {Function} enhancer store增强器。你可以选择性的传入一个增强函数来扩展
* store的功能,例如中间件,时间旅行,持久化等。Redux自带的唯一一个增强器是
* `applyMiddleware()`方法。
*
* @returns {Store} 返回一个可以读取状态,发送action以及订阅变更通知的Redux store。
*/
export default function createStore(reducer, preloadedState, enhancer) {
// 如果只传入reducer和enhancer,则store的初始状态为undefined
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
// enhancer必须是一个函数
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 返回使用enhancer增强后的store
return enhancer(createStore)(reducer, preloadedState)
}
// reducer必须是一个函数
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
// 在每次修改监听函数数组之前复制一份,实际的修改发生在这个新
// 复制出来的数组上。确保在某次dispatch发生前就存在的监听器,
// 在该次dispatch之后都能被触发一次。
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
/**
* 读取store状态
*
* @returns {any} 返回store的当前状态
*/
function getState() {
return currentState
}
/**
*
* 新增一个变更监听函数。每当dispatch了一个action之后监听函数都会被触发一次。
* 你可以在监听函数中通过`getState()`方法获取store的最新状态。
*
* 你可以在一个回调函数中再次调用`dispatch()`。但需要注意以下两点:
*
* 1. 在每一次dispatch()调用执行之前,监听函数数组都会被复制一份(通过前文提到
* 的ensureCanMutateNextListeners方法)。如果你在监听函数中增加或删除其他监听函
* 数,那么这些操作并不会影响到当前进行中的这一轮dispatch。而下一次dispatch,
* 不论其是否是嵌套调用,都会使用最新的,修改后的监听函数列表。
*
* 2. 由于在一个监听函数执行前可能已经进行了多次嵌套的dispatch调用,因此不能保证
* 每个监听函数都可以获取到所有的状态变更。然而,可以确定的是,在某次dispatch
* 触发之前已经注册的监听函数都可以读取到这次diapatch之后store的最新状态。
*
* @param {Function} listener 每次dispatch之后执行的回调函数
* @returns {Function} 返回一个用于取消这次订阅的函数
*/
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)
}
}
/**
* 发送一个action。这是触发状态变更的唯一方法。
*
* 每次发送action的时候,用于创建store的`reducer`函数都会被调用一次。调用时
* 传入的参数是当前的状态以及被发送的`action`。调用的返回值会被当做更新后的
* 状态。调用完成后,所有的状态监听函数都会被触发。
*
* 基础实现中仅支持发送形式为简单对象的action。如果你希望可以发送Promise,Observable,
* thunk或是其它形式的action,你需要使用相应的中间件把store创建函数封装起来。
* 你可以在`redux-thunk`模块的文档中找到这方面的示例。不过即使在这些中间件内部
* 还是通过dispatch方法发送了简单对象形式的action。
*
* @param {Object} action 一个表示变更内容的对象。保证你的action是可被序列化的
* 是一种很好的实践,这样你就可以记录并回放用户的操作,或是使用可以穿梭时间
* 的`redux-devtools`插件。一个action必须有一个值不为`undefined`的`type`属性。
* 推荐你使用字符串常量来表示action类型。
*
* @returns {Object} 为了方便起见,返回你传入的action对象。
*
* 要注意的是,如果你使用了一个自定义的中间件,它可能会把`dispatch()`的返回值
* 封装成其它内容(比如,一个你可以await的Promise)
*/
function dispatch(action) {
// 如果action不是简单对象,抛出异常
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// 如果action的类型是undefined,抛出异常
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
// reducer内部不允许再次调用dispatch,否则抛出异常
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
// 执行reducer,传入当前状态和action
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 触发所有的状态监听回调函数
var listeners = currentListeners = nextListeners
for (var i = 0; i < listeners.length; i++) {
listeners[i]()
}
return action
}
/**
* 替换store当前使用的reducer函数
*
* 如果你的应用程序实现了代码拆分并且你希望动态加载某些reducer的时候你
* 可能会用到这个方法。或者当你要为Redux实现一个热加载机制的时候,你也
* 会用到它。
*
* @param {Function} nextReducer 要使用的新reducer
*
* @returns {void}
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
}
/**
* 为observable/reactive库预留的交互接口。
* @returns {observable} 表示状态变更的最简单的observable对象
* 想要获取更多信息,可以查看observable提案:
* https://github.com/zenparsing/es-observable
*/
function observable() {
// 首先保留对Redux中subscribe方法的引用,在observable的世界里
// 有一个同名方法。
var outerSubscribe = subscribe
return {
/**
* 一个极简的observable订阅方法。
* @param {Object} observer 任何可以作为observer使用的对象
* observer对象应该包含一个`next`方法。
* @returns {subscription} 返回一个带有`unsbscribe`方法的对象。该
* 方法将用于停止接收来自store的状态变更信息。
*/
subscribe(observer) {
// observer参数必须是一个对象,否则抛出异常。但并未检测是否有next方法。。。
if (typeof observer !== 'object') {
throw new TypeError('Expected the observer to be an object.')
}
// 创建一个状态变更回调函数。逻辑很简单,把store最新的状态传给observer
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
// 立即执行一次回调函数,把当前状态传给observer
observeState()
var unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
// 根据observable提案,[Symbol.observable]()返回observable对象自身
[$$observable]() {
return this
}
}
}
// store创建好以后,立即发送一个初始化action。这样做是为了让reducer
// 返回store的初始状态(当给reducer传入的当前state为undefined时,reducer
// 会返回store的初始状态)。
dispatch({ type: ActionTypes.INIT })
// 返回创建好的store对象
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
3.2 使用示例
createStore创建一个store实例,实例保存了应用的状态,提供了对外API,以一个简单的例子进行说明
//action creator
var addTodo = function(text){
return {
type: 'add_todo',
text: text
};
};
// reducer方法, 传入的参数有两个
// state: 当前的state
// action: 当前触发的行为, {type: 'xx'}
// 返回值: 新的state
function TodoReducer(state = [], action){
switch (action.type) {
case 'add_todo':
return state.concat(action.text);
default:
return state;
}
};
// 创建store, 传入两个参数
// 参数1: reducer 用来修改state
// 参数2(可选): [], 默认的state值,如果不传, 则为undefined
var store = redux.createStore(TodoReducer,[]);
// 通过 store.getState() 可以获取当前store的状态(state)
// 默认的值是 createStore 传入的第二个参数
store.getState(); // state is []
// 通过 store.dispatch(action) 来达到修改 state 的目的
// 注意: 在redux里,唯一能够修改state的方法,就是通过 store.dispatch(action)
store.dispatch({type: 'add_todo', text: '读书'});
// 修改后的state
store.getState(); // state is ['读书']
4.combineReducers.js
上面使用示例中的,仅有一个reducer。实际使用中,一般会按业务拆分出多个reducer,combineReducers的作用在于合并多个reducer函数,并返回一个新的reducer函数。
4.1 代码讲解
import { ActionTypes } from './createStore'
import isPlainObject from 'lodash/isPlainObject'
import warning from './utils/warning'
const NODE_ENV = typeof process !== 'undefined' ? process.env.NODE_ENV : 'development'
function getUndefinedStateErrorMessage(key, action) {
const actionType = action && action.type
const actionName = (actionType && `"${actionType.toString()}"`) || 'an action'
return (
`Given action ${actionName}, reducer "${key}" returned undefined. ` +
`To ignore an action, you must explicitly return the previous state. ` +
`If you want this reducer to hold no value, you can return null instead of undefined.`
)
}
function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
const reducerKeys = Object.keys(reducers)
const argumentName = action && action.type === ActionTypes.INIT ?
'preloadedState argument passed to createStore' :
'previous state received by the reducer'
if (reducerKeys.length === 0) {
return (
'Store does not have a valid reducer. Make sure the argument passed ' +
'to combineReducers is an object whose values are reducers.'
)
}
if (!isPlainObject(inputState)) {
return (
`The ${argumentName} has unexpected type of "` +
({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
`". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`
)
}
const unexpectedKeys = Object.keys(inputState).filter(key =>
!reducers.hasOwnProperty(key) &&
!unexpectedKeyCache[key]
)
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
if (unexpectedKeys.length > 0) {
return (
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
`Expected to find one of the known reducer keys instead: ` +
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
)
}
}
//reducer合法性检查
function assertReducerSanity(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const initialState = reducer(undefined, { type: ActionTypes.INIT })
if (typeof initialState === 'undefined') {
throw new Error(
`Reducer "${key}" returned undefined during initialization. ` +
`If the state passed to the reducer is undefined, you must ` +
`explicitly return the initial state. The initial state may ` +
`not be undefined. If you don't want to set a value for this reducer, ` +
`you can use null instead of undefined.`
)
}
const type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
if (typeof reducer(undefined, { type }) === 'undefined') {
throw new Error(
`Reducer "${key}" returned undefined when probed with a random type. ` +
`Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
`namespace. They are considered private. Instead, you must return the ` +
`current state for any unknown actions, unless it is undefined, ` +
`in which case you must return the initial state, regardless of the ` +
`action type. The initial state may not be undefined, but can be null.`
)
}
})
}
/**
* 将值为各个不同reducer方法的对象,转化为一个单一reducer方法。
* 它将调用所有子reducer方法,并将结果合并到一个state对象。
* 该state对象的key和reducer方法名一一对应
*
* @param {Object} reducers 值为各个不同reducer方法的对象
*
* @returns {Function} 单reducer方法.
*/
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
if (NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let sanityError
try {
// 对所有的子reducer 做一些合法性断言,如果没有出错再继续下面的处理
assertReducerSanity(finalReducers)
} catch (e) {
sanityError = e
}
//返回单reducer方法,参数和普通reducer方法一致,分别为state对象和action对象
return function combination(state = {}, action) {
if (sanityError) {
throw sanityError
}
if (NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
if (warningMessage) {
warning(warningMessage)
}
}
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
4.2 使用示例
假设有两个reducer
function TodoReducer(state, action) {}
function FilterReducer(state, action) {}
通过调用combineReducers(),返回了一个新的wrapReducers
var wrapReducers = redux.combineReducers({
todos: TodoReducer,
filter: FilterReducer
});
将wrapReducers传入createStore()
var store = redux.createStore(wrapReducers,{});
注意,这里传入初始state对象时,需要和wrapReducers对象结构对应。
store中的currentReducer为wrapReducers,在store.dispatch(action)时,执行wrapReducers(state,action),进而依次执行所有子Reducer方法。根据action.type匹配Reducer中的switch case。
5.bindActionCreator.js
5.1 代码讲解
//使用dispatch包裹actionCreator
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
/**
* 将值为action creator的对象,转换为相同的key,但是方法被包裹到dispatch调用中,使得方法可以被直接调用的对象。
*
* @param {Function|Object} actionCreators 值为action creator的对象
*
* @param {Function} Redux store的dispatch方法
*
* @returns {Function|Object} 相同的key,方法被包裹到dispatch调用中的对象
*/
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
5.2 使用示例
假设有如下action creator
var addTodo = function(text){
return {
type: 'add_todo',
text: text
};
};
使用bindActionCreators前,这样调用
store.dispatch({type: 'add_todo', text: '读书'});
使用bindActionCreators包裹addTodo
var actions = redux.bindActionCreators({
addTodo: addTodo
}, store.dispatch);
现在可以这样调用
actions.addTodo('看电影');
bindActionCreators是一个语法糖,非必须。他的作用在于,将redux相关api--dispatch,封装在调用内部。这样,在实际应用中,从顶层容器,向子组件以props传递这些action creator时,子组件可以以普通函数的形式直接调用这些action creator,对redux无感知。
6.applyMiddleware.js
6.1 代码讲解
6.1.1 applyMiddleware
import compose from './compose'
/**
* 创建一个增强的store,可以应用middleware来dispatch store的方法
*
* 每一个middleware会议命名参数形式传入`dispatch` 和 `getState`方法
*
* @param {...Function} middlewares 被调用的中间件链.
* @returns {Function} 应用中间件的增强store.
*/
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
//创建store
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
//初始化所有中间件,传入middlewareAPI,`dispatch` 和 `getState`方法
//这里的dispatch方法是一个封装的方法,
//注意,此dispatch 不等于 store.dispatch,compose之后,dispatch已变更
chain = middlewares.map(middleware => middleware(middlewareAPI))
//使用compose将中间件组合成调用链,传入原始store.dispatch作为钩子,执行最终的store.dispatch
dispatch = compose(...chain)(store.dispatch)
//注意,返回的store,dispatch不再是原始store.dispatch
return {
...store,
dispatch
}
}
}
6.1.2 compose
/**
* 将方法从右到左组合起来
* compose(f, g, h)被转换为(...args) => f(g(h(...args)))
*
* @param {...Function} funcs 需要被组合的方法.
* @returns {Function} 将方法从右到左组合起来.
*/
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
6.2 使用示例
理解applyMiddleware需要结合middleware
假设有两个middleware,分别为thunkMiddleware、logger
function thunkMiddleware({ dispatch, getState }) {
return next => action =>
typeof action === 'function' ?
action(dispatch, getState) :
next(action);
}
function logger({ dispatch, getState }) {
return next => action => {
console.log('logger: dispatching ' + action.type);
var result = next(action);
console.log('logger: next state ' + result);
return result;
}
}
调用applyMiddleware(thunkMiddleware,logger)
,执行middlewares.map(middleware => middleware(middlewareAPI))
后,完成了中间件的初始化,chain为一个数组,包含两个函数

继续执行compose(...chain)(store.dispatch);
,dispatch方法为

其中,next为

而var result = next(action)
中的next为store.dispatch。
中间件和store.dispatch组成了一个调用链,thunkMiddleware(logger(store.dispatch(action)))
当有如下异步action creator
function asyncAddTodo(text) {
return dispatch => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(addTodo(text));
}, 1000);
};
}
以applyMiddleware()
返回的增强型store,执行store.dispatch(asyncAddTodo('test'))
,因为返回的是一个func : dispatch => {...}
,所以先执行该func。当异步返回时,执行dispatch(addTodo(text));
,此处的dispatch为前述调用链thunkMiddleware(logger(store.dispatch(action))),这时代码依次往下走,最终通过原始的store.dispatch,触发redux自身disptach(action),进而进入Reducer继续执行。
参考文章
如果觉得有帮助,可以扫描二维码对我打赏,谢谢
