在做了一段时间react 项目之后,发觉自己遇到了瓶颈,给定的项目能按照react , redux 固定的模式来套用实现效果,然而深究却不知其意,一直在做无谓的代码搬运工而已,为了改变自己当下这种状态,找了很多对 redux 源码解析的例子,大多写的不明所以,思路不清晰,直到看了这篇文章【redux源码解析】,心中豁然开朗,故作为笔记记录一下。
redux源码解析
1、首先让我们看看都有哪些内容
2、让我们看看redux的流程图
Store:一个库,保存数据的地方,整个项目只有一个
创建store
Redux提供 creatStore 函数来生成 Store
// 引入redux
import { createStore } from 'redux';
//创建Store 需要传递一个函数fn 这里的fn是之后会提及的reducers
const store = createStore(fn);
State:状态,某时刻的数据即是Store的状态
获取状态的方法是store.getState()
Action:行为,它有一个不可或缺的type属性
action还可以携带其他内容
我们可以使用action来改变State的值,
从而将我们需要的数据通过Action“运输”到 Store;
dispatch:发送action
dispatch(action)接受一个action对象为参数,并将它发送出去,
Store接受Action,接受之后需要返回一个新的State(状态)
Reducer:处理器
dispatch(action)接受一个action对象为参数,并将它发送出去,
Store接受Action,接受之后需要返回一个新的State(状态)
而创建这个新的状态的过程就是reducer
3、从 isPlainObject.js 开始理解
/**
* @param {any} obj The object to inspect.
* @returns {boolean} True if the argument appears to be a plain object.
*/
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
}
isPlainObject 这个函数的核心思想在于:判断一个值是否为普通对象。
此处的普通对象指的是直接通过字面量(let obj={})或者new Object()创建出来的对象
具体是怎么判断的呢?
if (typeof obj !== 'object' || obj === null) return false
一上来就排除肯定不是对象的值
注意:typeof null 的返回值为 "object" 。所以只使用 typeof obj !== 'object' 不能将 null 值排除掉. 因此应使用 typeof obj !== 'object' || obj === null 进行判断。
再往下就是通过原型链判断了。
通过 while 不断地判断 Object.getPrototypeOf(proto) !== null 并执行,
最终 proto 会指向 Object.prototype. 这时再判断 Object.getPrototypeOf(obj) === proto,
如果为 true 的话就代表 obj 是通过字面量或调用 new Object() 所创建的对象了。
Object.getPrototypeOf()
方法用于获取一个对象的原型属性指向的是哪个对象 。
举例:
假设有一个构造器:function Fun(){}
创建一个对象:var f = new Fun()
Object.getPrototypeOf(f) 得到的返回值
和访问 f.__proto__ 是一样的
这个值指向 Fun.prototype.
假如一个对象是普通对象
那么这个对象的 __proto__ 一定是指向 Object.prototype 的,
而非普通对象, 例如 f, 其 __proto__ 是指向其构造函数的 prototype 属性。
因此比较 Object.getPrototypeOf(obj) 与 proto 相等, 则判定 obj 是普通对象。
4、接下来是createStore.js
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.')
}
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
...
...
...
}
从上往下解读,先看第一部分内容,意思是:
如果 preloadedState 是一个function ,enhancer 是undefined ,那么把 preloadedState 的结果赋值给 enhancer , 并且将preloadedState 设置为 undefined
如果 enhancer 不是 undefined ,并且不是 function , 那么抛出异常 【Expected the enhancer to be a function】
如果 enhancer 不是 undefined , 是一个function , 那么返回 enhancer(createStore)(reducer, preloadedState)
如果 reducer 不是function ,直接抛异常
里面的几个函数:
getState:
function getState() {
return currentState
}
意思是:读取状态树的状态
subscribe
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
let isSubscribed = true
ensureCanMutateNextListeners() // 监听当前内容
nextListeners.push(listener) // 新增一个监听函数
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
ensureCanMutateNextListeners 监听当前内容
nextListeners.push(listener) 新增一个监听函数
返回 函数 unsubscribe 用于销毁监听
dispatch
function dispatch(action) {
//action必须是一个包含type的对象
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?'
)
}
//如果正处于isDispatching状态,报错
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
//这里就是调用我们reducer方法的地方,返回一个新的state作为currentState
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
}
它只是执行了当前的reducer方法,然后把当前的state和你在调用dispatch时传入的action作为参数,返回的值就是新的currentState。
从这里我们也可以看出,改变state的代码逻辑就在reducer方法中,在这些执行完之后,dispatch方法会遍历当前的监听列表,并执行所有的监听函数。
replaceReducer
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
替换reducer之后重新初始化状态树。
observable
function observable() {
const outerSubscribe = subscribe
return {
/**
* The minimal observable subscription method.
* @param {Object} observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns {subscription} An object with an `unsubscribe` method that can
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.')
}
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
最小可观测订阅方法。
*@param{Object}观测器可以用作观测器的任何对象。
*观察者对象应该有一个`下一个`方法。
*@返回{订阅}具有`取消订阅`方法的对象
*用于从商店取消订阅可观察的,并进一步防止
*可观测值的发射。
5、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)))
}
reduce方法接受2个参数,第一个参数是一个callback函数,第二个是一个初始值initValue
第一个函数有四个参数
- previousValue: 上一次调用callback时返回的值
- currentValue: 正在处理的数组元素
- index: 正在处理的数组元素下标
- array: 被处理的数组
如果有initValue,initValue将作为第一次的previousValue,若没有,则数组第一个元素将作为previousValue,
后面一个元素将作为currentValue,然后执行callback的函数体,将返回的值作为previousValue,
将下一个元素作为currentValue,一直到最后一个数组最后一个元素执行完位置,再返回最终的结果。
比如有一个数组arr=[1,2,3,4,5],我们使用reduce来求和:
let sum = [1,2,3,4,5].reduce((a,b)=>a+b);
它巧妙的地方在于数组的每个元素都是函数,
callback返回一个复合函数作为previousValue,在reduce方法执行完之后,
也就返回了一个将整个数组中所有函数串式调用的一个函数。
applyMiddleware.js
export default function applyMiddleware(...middlewares) {
//return一个函数,可以接收createStore方法作为参数
//给返回的store的dispatch方法再进行一次包装
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)
}
//传入middlewareAPI参数并执行每一个外部函数,返回结果汇聚成数组
const chain = middlewares.map(middleware => middleware(middlewareAPI))
//用到了上面的compose方法
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
还记得刚才createStore方法中的enhancer参数吗?
applyMiddleware就是用来创建enhancer函数的。
官方的注释中提到了redux-thunk,就是使用applyMiddleware的一个很好的例子,
我们结合它的代码来看可以更好的理解,下面是它的代码:
redux-thunk 文件的代码
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
最终export了一个接受{ dispatch, getState }作为参数的function thunk,
这个thunk方法也就是传给applyMiddleware方法的参数,
此时的middlewares只有thunk一个方法,
那么applyMiddleware中的chain也就很显然的是执行了thunk方法后返回的结果,
我们再看redux-thunk的代码,返回了一个接受next作为参数的方法!
applyMiddleware的下一行,
dispatch = compose(...chain)(store.dispatch),
chain只有一个function,所以这里可以忽略compose,
那么这一句就是将store.dispatch 作为next参数传给了刚才的方法A,
终于,方法A返回了我们熟悉的dispatch方法。
但是注意,此时的dispatch方法还是原来的dispatch方法吗?
它已经不是原来的它了。经过thunk方法的包装,早已物是人非。
我们来看一下redux-thunk的代码,第三行之后的4行,
如果dispatch方法接受的参数不是一个function,
那么这个dispatch就和普通的dispatch没什么不同,
但如果此时的action是一个方法,那么就会执行此方法,且第一个参数是store.dispatch。
这意味着我们的action创建函数不再只能创建一个包含type的Object,而可以是一个方法。
你可能会问有什么用呢?当你在action中需要一个异步操作,并需要在回调中改变state的状态的时候,这就是一个绝佳的解决方案。
所以说,applyMiddleware实际上做了一件事,就是根据外部函数(中间件函数)包装原来的dispatch函数,然后将新的dispatch函数暴露出去。
再回头去看createStore.jsx中的 return enhancer(createStore)(reducer, preloadedState)这句代码,是不是明白了很多事情?
7、combineReducers.js
//根据key和action生成错误信息
function getUndefinedStateErrorMessage(key, action) {
//...
}
//一些警告级别的错误
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'
//判断reducers是否为空数组
//判断state是否是对象
//给state中存在而reducer中不存在的属性添加缓存标识并警告
//...
}
//这个方法用于检测用于组合的reducer是否是符合redux规定的reducer
function assertReducerSanity(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
//调用reducer方法,undefined为第一个参数
//使用前面说到过的ActionTypes.INIT和一个随机type生成action作为第二个参数
//若返回的初始state为undefined,则这是一个不符合规定的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 (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
//finalReducers是过滤后的reducers,它的每一个属性都是一个function
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let sanityError
//检测每个reducer是否是符合标准的reducer
try {
assertReducerSanity(finalReducers)
} catch (e) {
sanityError = e
}
return function combination(state = {}, action) {
if (sanityError) {
throw sanityError
}
//如果不是成产环境,做一些警告判断
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
if (warningMessage) {
warning(warningMessage)
}
}
let hasChanged = false
const nextState = {} //下一个state树
//遍历所有reducers,然后将每个reducer返回的state组合起来生成一个大的状态树,所以任何action,redux都会遍历所有的reducer
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)
//如果此reducer返回的新的state是undefined,抛出异常
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
//如果当前action对应的reducer方法执行完后,该处数据没有变化,则返回原来的流程树
return hasChanged ? nextState : state
}
}
8、bindActionCreators.js
//很简单却很关键,我就不解释了~
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
/**
* 将action与dispatch函数绑定,生成直接可以触发action的函数,
* 可以将第一个参数对象中所有的action都直接生成可以直接触发dispatch的函数
* 而不需要一个一个的dispatch,生成后的方法对应原来action生成器的函数名
* */
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
//actionCreators必须为object类型
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]
//给actionCreators的每一个成员都绑定dispatch方法生成新的方法,
//然后注入新的对象中,新方法对应的key即为原来在actionCreators的名字
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
} else {
warning(`bindActionCreators expected a function actionCreator for key '${key}', instead received type '${typeof actionCreator}'.`)
}
}
return boundActionCreators
这个方法主要的作用就是将action与dispatch函数绑定,生成直接可以触发action的函数。代码比较简单注释也比较明白,就过去了~
至此 redux 源码基本学习结束,即将以一个小demo,进一步理解redux。