redux 源码阅读
【目录结构】
![image-20190827201059192](/Users/shilongfei/Library/Application Support/typora-user-images/image-20190827201059192.png)
Redux 源码可以在任意项目中的 node_modules
文件夹下的 redux
中找到。我们阅读学习中主要关注 src 即可。
src 下主要分成两个部分, 一部分是 utils
工具库, 一部分是 redux
逻辑代码。
【utils】
Redux 自定义的工具库
下属对应三个文件
- actionTypes.js
- isPlainObject.js
- warning.js
actionTypes.js
源码如下:
const randomString = () =>
Math.random()
.toString(36)
.substring(7)
.split('')
.join('.')
const ActionTypes = {
INIT: `@@redux/INIT${randomString()}`,
REPLACE: `@@redux/REPLACE${randomString()}`,
PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}
export default ActionTypes
这个文件主要是用来对外暴露三个 action 类型,比较好理解。
其中的 randomString
方法用来获取指定长度的随机字符串, 这里有个很多同学都会忽略掉的知识点, Number.prototype.toString 方法 接受一个可选参数 radix
,该参数代表数字的基数, 也就是我们常数的二进制、八进制等, 默认为 10, 范围在 2~36之间。
isPlainObject.js
源码如下:
export default function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false
let proto = obj
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto
}
这个文件对外暴露一个用来判断是否为简单对象的方法。
简单对象
凡不是new Object()或者字面量的方式构建出来的对象都不是简单对象
就是该对象的 __proto__
等于 Object.prototype
举🌰, 像 正则, 日期,类, 函数, 都不是简单对象
具体的相关知识点,可以去看下原型链的相关知识。
warning.js
源码如下:
export default function warning(message) {
if (typeof console !== 'undefined' && typeof console.error === 'function') {
console.error(message)
}
try {
throw new Error(message)
} catch (e) {}
}
这个文件也是很简单只是用来打印一下错误信息, 这里对 console.error
加了层判断处理, 用于处理兼容性问题。原因是 ie8及其以下都是不支持 console
【逻辑代码】
下属文件如下:
- applyMiddleware.js
- bindActionCreators.js
- combineReducers.js
- compose.js
- createStore.js
- index.js
我们首先从入口文件开始阅读:
index.js
源码如下:
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
function isCrushed() {}
if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
) {
warning(
'You are currently using minified code outside of NODE_ENV === "production". ' +
'This means that you are running a slower development build of Redux. ' +
'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
'to ensure you have the correct code for your production build.'
)
}
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
这里的入口文件主要的工作是把其余的几个文件做一个统一导出, 需要关注的地方有两点
-
__DO_NOT_USE__ActionTypes
。 通过导入路径可以得知,引用自之前untils
中的actionTypes.js
中,平时工作中也用不到,官方文档也未做解释,暂且放下不管,以后再研究 。。。 -
函数
isCrushed
从字面理解,这是函数是用来判断是否是碎的, 但函数体中却什么都没有,很是奇怪,带着这个疑问往下看这个if
语句,在这里给出了答案,process.env.NODE_ENV !== 'production'
typeof isCrushed.name === 'string'
isCrushed.name !== 'isCrushed'
这三个条件从字面上也非常好理解。 当三个条件都不满足时 执行
warning
抛出异常信息。信息内容翻译如下:“您目前正在使用NODE_ENV =''production'之外的缩小代码。”+
'这意味着你正在运行一个较慢的Redux开发版本。 '+
'你可以使用loose-envify(https://github.com/zertosh/loose-envify)进行浏览器化'+
'或者为Webpack定义插件(http://stackoverflow.com/questions/30030031)'+
'以确保您拥有正确的生产构建代码。可能有同学有对于前两个条件还好理解, 对第三个条件会感到有些疑惑, 觉得没有必要。这里了解过些打包原理的同学应该对这个判断还是比较好理解的。
以
create-react-app
为例: 项目代码在生产环境下会对代码内容进行一个深度压缩,会将所有的变量名替换成a
,b
,c
之类的字母, 所以当进行生成环境编译后 函数isCrushed
可以就变成了 函数i
。这个函数的主要作用就是防止开发者在开发环境下对代码进行压缩, 影响调试
createStore.js
源码如下:
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore(reducer, preloadedState, enhancer) {
// 参数处理
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function'
)
}
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.')
}
// 定义变量
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
// 定义方法
function ensureCanMutateNextListeners() {
...
}
function getState() {
...
}
function subscribe(listener) {
...
}
function dispatch(action) {
...
}
function replaceReducer(nextReducer) {
...
}
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
函数 createStore
创建一个 Redux store 来以储存应用中所有的 state。应用用应有且仅有一个 store。
createStore(reducer, [preloadedState], enhancer)
返回的接口分别是 dispathc
subscribe
getState
replaceReducer
和 [$$observable]
参数
-
reducer: function
接收两个参数, 分别是当前的 state 树和要处理的 action, 返回新的 state 树。 -
[preloadedState]: any
初始时的 state, 如果只有两个参数,并且第二个参数是个函数时,将此参数赋值为第三个参数, 并忽略此参数所以我们在使用时可以不传第二个参数,直接将第三个参数书写到第二个参数的位置 同样可以正常使用
-
enhancer: func
enhaner 增强器 是一个组合 stroe creator 的高阶函数,返回一个新的强化后的 store creator。 通常可以理解为中间件,同时它也允许你通过复合函数改变 store 接口。注意: 当存在多个参数时,且从第三个参数开始类型同为 函数, createStroe 将默认理解你想要使用多个 增强器, 这里会抛出异常提示你需要将多个增强器合并成一个函数传入。
常见的
enhancer
有 redux-thunk 以及 redux-sage。一般都会配合 applyMiddleware 一起使用, 其作用就是将这些 enhancer 格式化成符合 redux 要求的 enhancer。举个🌰:
import {createStore, applyMiddleware} from 'redux' import thunk from 'redux-thunk' import logger from 'redux-logger' import reducer from './reducers' const store = createStore(reducer, applyMiddleware(thunk)) export default store
变量
let currentState = preloadedState //从函数createStore第二个参数preloadedState获得
let currentReducer = reducer //从函数createStore第一个参数reducer获得
let currentListeners = [] //当前订阅者列表
let nextListeners = currentListeners //新的订阅者列表
let isDispatching = false
其中的 isDispatching
是作为锁来用的, 我们都知道 redux 是一个统一的状态管理容器。所以同一时间里我们必须保证只要一个action在执行
ensureCanMutateNextListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
确保 nextListeners 与 currentListeners 不存在同一个引用关系, 主要服务于 dispatch 与订阅。
dispatch
function dispatch(action) {
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
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
这里我们可以看到 dispatch 方法中做了三重判断,
1. action 必须是个简单函数;
2. action.type 存在;
3. 未锁住状态。
只有当三重判断都通过时, 才往下执行 action 并进行上锁
getState
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState
}
getState 非常简单, 返回 currentState 即可。这个 currentState 在每次 dispatch 的时候都会进行更新, 同dipatch 一样在进行 reducer 操作的时候, 是不可以读取到当前 state 里的值的。
提问: 执行 createStore 函数生成 store, 可不可以直接修改它的 state?
答案是: 可以的, 但redux 不允许这么去做, 因为这样不会通知到订阅者重新更新数据。
subscribe
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
这个函数可以给 store 的状态添加订阅监听函数,一旦调用 dispatch
,所有的监听函数就会执行;
nextListeners
就是储存当前监听函数的列表,调用 subscribe
,传入一个函数作为参数,那么就会给 nextListeners
列表 push
这个函数;
同时调用 subscribe
函数会返回一个 unsubscribe
函数,用来解绑当前传入的函数,同时在 subscribe
函数定义了一个 isSubscribed
标志变量来判断当前的订阅是否已经被解绑,解绑的操作就是从 nextListeners
列表中删除当前的监听函数。
replaceReducer
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
这个函数可以替换 store 当前的 reducer 函数,首先直接把 currentReducer = nextReducer
,直接替换;
然后 dispatch({ type: ActionTypes.INIT })
,用来初始化替换后 reducer 生成的初始化状态并且赋予 store 的状态;平时很难用到。
observable
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
...
}
这个不是暴露给我们开发者的, 不需要掌握。因此贴给注释,就不贴详细代码, 关于 函数 observable
有兴趣的可以去作者的 gayhub(github)去学习一下: https://github.com/tc39/proposal-observable
compose.js
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)))
}
这个函数的意义就是将多个函数方法合并组成一个函数并返回。 注意这里的组合是从右像左进行组合。
applyMiddleware.js
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
applyMiddleware 需要结合到 createStore 中的第三个参数 enhancer
增强器。通过对比我们可以得出 applyMiddleware 通过组合多个中间件返回的一个 enhaner 。
可以看一下前面 createStore 中 enhaner
的相关代码
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
这里的 enhancer === applyMiddleware(...)
。。。( 读不动了 ,😳懵逼中,,, 理解不了 🤣🤣 , 跳过 跳过 😂😂)
bindActionCreators.js
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
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
}
这部分代码很好理解, 返回一个能够直接触发 action 的函数。
第一个参数类型应为一个函数或者对象。 当为对象时, 直接调用 bindActionCreator
方法进行返回。 当为对象时, 对对象进行遍历 将其中为 类型为 function 的 再次整合到一个对象中进行集中返回。
combineReducers.js
import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'
function getUndefinedStateErrorMessage(key, action) {
...
}
function getUnexpectedStateShapeWarningMessage(
inputState,
reducers,
action,
unexpectedKeyCache
) {
...
}
function assertReducerShape(reducers) {
...
}
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 (process.env.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 (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
if (process.env.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
}
}
这个js 主要用来将多个 reducer 合并成一个 进行统一返回, 工作中也比较常见。
工具方法:
assertReducerShape
function assertReducerShape(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.`
)
}
if (
typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === '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.`
)
}
})
}
函数 assertReducerShape 将对传入的 reducer 做了两个判断处理, 两个判断分别针对于 初始的 state 未定义, 与 传入一个未知的 type 做异常处理。 通过这里我们就不难理解 为什么 reducer 中为何必须返回一个初始的 state 与 在 switch 中的 default 返回 原有的 state。
getUnexpectedStateShapeWarningMessage
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 (action && action.type === ActionTypes.REPLACE) return
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.`
)
}
}
获取意外状态形状警告消息, 用来定义返回一下异常信息的描述内容, 供 warning 调用, 条件有: 传入的 reducer 不能为空对象、state 必须为一个简单对象、意外的 reducers 缓存。
getUndefinedStateErrorMessage
function getUndefinedStateErrorMessage(key, action) {
const actionType = action && action.type
const actionDescription =
(actionType && `action "${String(actionType)}"`) || 'an action'
return (
`Given ${actionDescription}, 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.`
)
}
获取未定义的状态错误消息
业务逻辑
- 第一步将传入的 reducer 对象 做一层浅拷贝,再赋值到 finalReducers
- 通过
assertReducerShape
检测 finalReducers 是否都有默认返回值 - 通过 变量
hasChanged
来表示 state 是否产生变化,遍历reducers集合,将每个reducer对应的原state传入其中,得出其对应的新的state。紧接着后面对新的state做了一层未定义的校验。 校验后会与原 state 进行比对,发生变化则返回 nextState, 反之返回 原 state。
结束:
到这整个源码算是粗略的读了一遍,虽然代码量不多, 只有区区数百行, 但是很绕的, 到现在有些地方依旧不能理解。不过对自身的帮助还是很大, 这期间也拜读了不少大佬的源码剖析。在这也很感谢 有那么多的大佬分享自己的学习心得体会。其中特别是 wuming 大佬的 redux源码剖析 写的十分精彩,有需要的同学可以前往拜读一下。 这份源码阅读还存在很多理解不足, 随着后续的理解进一步深入,也会再次更新的。