Redux源码解读
早就想找个时间看看react全家桶的源码了 这次先从redux看起,后续还会有react-redux和react-router-redux
下面看代码 目录结构如下:
- applyMiddleware.js
- bindActionCreator.js
- combineReducers.js
- compose.js
- createStore.js
- index.js
先看入口文件
index.js
入口文件主要是暴露1-5的核心方法 代码就不贴了
createStore.js
// reducer是一个函数, 接收一个单独的reducer或者经过combineRedeucers组合的多个reducer
// preloadedState 是指需要提前加载的state 会和各store的state一起整合到state树里
// enhancer 是store的增强器 下面会重点讲 会涉及到compose和applyMiddleware这两个核心函数
export default function createStore(reducer, preloadedState, enhancer) {
// 如果第二个参数是个函数 并且没有第三个参数 就默认没有穿preloadedState 直接传了增强器函数
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.')
}
// 存在函数类型的增强器就返回执行增强器执行后的结果
// 这里可以推测出增强器的函数结构应该是这样的
// const enhancer = createStore => (reducer, preloadedState, enhancer) => {
// const store = createStore(reducer, preloadedState, enhancer)
// return {
// ...store,
// }
// }
// 所以可以猜测enhancer是为了通过覆盖达到增强store的某一个属性 下面再细说
return enhancer(createStore)(reducer, preloadedState)
}
// 如果reducer不是函数 抛异常
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
let currentReducer = reducer
// 当前的state树
let currentState = preloadedState
// 这里为什么药声明两个监听队列呢?
// 作者是为了在dispatch的时候能够完整的执行所有的事件监听
// 有人试验过 在一个监听里面取消另一个监听 这个一个无效操作 feature or bug???
let currentListeners = []
let nextListeners = currentListeners
// 是否正在dispatch过程
let isDispatching = false
// 确保nextListeners 和 currentListeners没有引用关系
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
// 这个是获取到最新state内容
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
}
// 这个subscribe是为了给currentListener增加监听事件 下面的dispatch函数中 在执行完dispatch操作后 会遍历这个监听队列依次订阅的监听事件
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
// 保证不能再dispatch的过程中触发监听事件
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)
// 这种写法还是比较常见且妙的 添加监听事件后 通过闭包返回一个移除当前监听事件的方法
// 用法如下
// const store = createStore(...)
// const subscribe = store.subscribe(() => {})
// 移除只需要执行subscribe()就行了
return function unsubscribe() {
if (!isSubscribed) {
return
}
// 无法再dispatch过程中移除监听事件
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)
}
}
// 每个redux middleware都是一个对dispatch的一个增强
// 是redux的dispatch方法 也是页面修改树状态的唯一途径
function dispatch(action) {
// 首先判断action是不是一个纯对象 即通过{}或者new Object()创建
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// 保证每个action必须有type属性
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
// 一个action结束才能调用下一个
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
// 这是执行reducer的函数 下面讲到combineReducers的时候再重点讲
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 遍历触发subscribe添加的监听
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
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.REPLACE })
}
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
}
}
}
// 初始化store
// 收集每个独立store上的state 因为每个store都default返回自身的state
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[?observable]: observable
}
}
下面我们看看combineReducers做了什么
combineReducers.js
// 看这个文件名字就知道是一个组合多个reducer的方法
// reducers的结构如下
/*
* {
* demo1: function(state, action) {
* switch(action.type) {}
* },
* demo2: function(state, action) {
* switch(action.type) {}
* },
* }
* */
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}"`)
}
}
// 保证每个reducer对应的value都是函数
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 {
// 这个方法是为了保证每个自定义reducer至少有一个初始的state和兜底返回值
assertReducerShape(finalReducers)
} catch (e) {s
shapeAssertionError = e
}
// 这一步才是重点
// 返回createStore/dispatch方法中有一行: currentState = currentReducer(currentState, action)
// 所以这个方法才是真正修改状态树的方法
// combination方法会遍历传进来的多个reducers组成的对象 依次执行每个reducer 得到每个reducer相应的state
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)
}
}
// 每次dispatch的都会遍历reducer树让每个小reducer执行这个action
// 保证每次dispatch 都是根据上次的state进行操作的
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)
// 该type没有定义 抛异常
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
看完以上代码我们先举个小例子介绍一下enhancer到底是何方神圣
- touch index.js
- 开始写demo
const { createStore, combineReducers, applyMiddleware, compose } = require('redux')
const initialState = {
count: 1,
}
function count1(state = initialState, action) {
switch (action.type) {
case 'ADD':
return {
...state,
count: state.count + 1,
}
default:
return state
}
}
const store = createStore(count1)
const action1 = { type: 'ADD' }
store.dispatch(action1)
// 我们打印store.getStore()
// 结果是 { count1: { count: 2 } }
/*
* 上面是redux的基本使用 我们主要讲的是store增强器
* 以上也有提到增强器的主要结构
* 我们再做个小demo
* */
// 这是一个每次dispatch前后打印日志的增强器
const loggerEnhancer = createStore => (reducer, proloadedState) => {
const store = createStore(reducer, proloadedState)
// 重写dispatch方法 或者说是在原有的dispatch上包了一层
function dispatch(action) {
console.log(`dispatch action type: ${action.type}`)
const _action = store.dispatch(action)
const nextState = store.getState()
console.log('new state is:', nextState)
// 原来的dispatch就是个纯函数 所以这里的_action就是入参action
return _action
}
return {
...store,
dispatch,
}
}
const storeWithLogger = createStore(count1, loggerEnhancer)
storeWithLogger.dispatch(action1)
// 这样在dispatch的时候就会出现我们的logger内容
// 写到这儿的时候我在想 咦 这玩意儿怎么和redux中间件这么像 肯定compose和applyMiddleware有关系
// 好奇心驱使下 去看了compose和applyMiddleware
接着我们看看compose和applyMiddleware做了什么吧~
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)))
}
compose的代码很少 通过reduce方法将各中间件形成一个执行链 前一个中间件套在后一个的外层 并接受后一个函数的执行结果作为自己的入参 举个栗子吧
const a = function () {}
const b = function () {}
const c = function () {}
compose([a, b, c])
// 先处理a和b 返回
const ab = function (...args) {
return a(b(...args))
}
// 再处理ab和c 返回
const abc = function (...args) {
return ab(c(...args))
// 这个c(...args)执行结果就是ab的入参
// 所以我们可以换个写法
return a(b(c(...args)))
}
// 换而言之compose的返回值就是 (...args) => a(b(c(...args)))
applyMiddleware.js
我们看看applyMiddleware是怎么实现的 找找他是怎么通过compose组合成enhancer的
// 看到这段代码 我觉得猜测是对的 是不是有点像上面的loggerEnhancer的结构 重写dispatch方法
// 所以得出结论 与其说增强器是对store的增强不如说是对dispatch的增强
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
// 这里的...args是 reducers、preloadedState、enhancer
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))
// 这里的compose的作用是包裹所有dispatch操作
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
我们再写个使用applyMiddleware小demo 通过demo我们再进行设想
const { createStore, applyMiddleware, compose } = require('redux')
const initialState = {
count: 1,
}
function count1(state = initialState, action) {
switch (action.type) {
case 'ADD':
return {
...state,
count: state.count + 1,
}
default:
return state
}
}
const store = createStore(count1, applyMiddleware(fn1, fn2))
// 上面两个fn1和fn2我们都没定义 现在我们根据applyMiddleware猜测一下fn1和fn2是啥样子的
// 根据loggerEnhancer的设想
const enhancer = createStore => (reducers, preloadedState, enhancer) => {}
// applyMiddleware也应该是这样的函数体
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 这行代码可以看出 fn1的雏形 应该是这样的
const fn1 = ({ dispatch, getState }) => {}
// 但是通过compose方法还能接受store.dispatch作为入参 说明fn1应该是这样的
const fn1 = ({ dispatch, getState }) => dispatch => {}
// 通过返回的是一个dispatch 我可以知道fn1原来是这样的
const fn1 = ({ dispatch, getState }) => dispatch => action => {}
// 举个栗子
const a = dispatch => action => {}
const b = dispatch => action => {}
// compose([a, b])的结果为 (...args) => a(b(...args))
// compose([a, b])(store.dispatch) 结果为 a(b(dispatch)) 再做简化就是a(action => {})
// 从这里可以看出 a执行的dispatch其实就是 b(dispatch)的结果
// 如果还有c c执行dispatch就是a(dispatch) 从而达到中间件的作用
// a执行的dispatch就是dispatch
// 举个更确切的栗子
const fn1 = store => next => {
console.log('next1')
return action => {
console.log('fn1')
next(action)
console.log('fn1')
}
}
const fn2 = store => next => {
console.log('next2')
return action => {
console.log('fn2')
next(action)
console.log('fn2')
}
}
/*
* 在执行applyMiddleware时 执行了compose(...chain)(store.sidpatch) 执行顺序是 next2 、 next1 这是中间件入栈的顺序
* 但是调用的顺序不一样 有兴趣可以了解一下洋葱模型
* 执行store.dispatch(action1) 实际打印顺序是 fn1、fn2、fn2、fn1
* fn2在传入store.dispatch的时候 会将
*/
const dispatchFromStore = action => {
console.log('fn2')
next(action)
console.log('fn2')
}
/*
* 当做返回值传给fn1的next
* 那fn1的执行过程就是
* */
const dispatchFromFn2 = action => {
console.log('fn1')
dispatchFromStore(action)
console.log('fn1')
}
// 所以打印顺序是fn1、fn2、fn2、fn1就不奇怪了