之前5.1假期的时候,有去读了下redux的源码,当时感触很深,今天重新梳理遍redux的源码:
从源码中的src文件夹看起:
从这里可以看出源码主要分为两大块,一块为自定义的工具库,另一块则是redux的逻辑代码。
上一次我是从createStore.js
也就是redux的逻辑代码开始看的,那么这一次我从工具库开始看起:
utils
actionTypes.js
const randomString = () =>
Math.random()
// 转化成36进制的字符串
.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的类型,两个类型的值都是通过一个随机数字转化成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
}
这个方法判断传入参数的原型是否等于Object.prototype
换句话说,判断参数的父类是不是Object
。
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) {}
}
就是打印错误信息,然后为了兼容ie8,做了下判断
逻辑代码
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() {}
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
去除警告代码,我们可以看到index.js就是将我们熟悉的一些api导出了,这些api后面我会挨个重新看的哈~
但是__DO_NOT_USE__ActionTypes
这个是什么?好像我们日常和文档里都没有见过这个东西呀!回到引用的地方可以看的就是我们之前分析过的actionTypes
方法,里面定义了redux自带的action的类型,从这个变量的命名来看,这是帮助开发者检查不要使用redux自带的action的类型,以防出现错误。
createStore.js
接着往下看就是我们最长用的createStore.js
文件啦,我们的源码分析也终于进入正餐了~
export default function createStore (reducer,preloadedState, enhancer) {}
函数createStore
接受了三个参数(reducer、preloadedState、enhancer)
reducer就是我们常用的处理数据的纯函数,reducer会根据传入的state和action,返回新的state。
preloadedState就是初始状态
enhancer的意思是增强器,其实就是增强redux功能的函数。
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 当enhancer不为空且为函数时,就执行该函数,并return回去,作为creatStore的返回值。
return enhancer(createStore)(reducer, preloadedState)
}
这段代码重点在于enhancer(createStore)(reducer, preloadedState)
可以推出enhancer
的结构
function enhancer(createStore) {
return (reducer,preloadedState) => {
//逻辑代码
.......
}
}
我们举个常见的传入中间件的例子:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
首先看下声明的变量:
// 初始化数据
let currentReducer = reducer //把 reducer 赋值给 currentReducer
let currentState = preloadedState //把 preloadedState 赋值给 currentState
let currentListeners = [] //当前订阅者列表
let nextListeners = currentListeners //新的订阅事件列表
let isDispatching = false //标记正在进行dispatch
我们了解了具体的参数和变量,接着往下看createStore的对外接口:
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
从dispatch开始看,dispatch接受一个action作为参数
function dispatch (action) {
// action要求是一个简单对象,
// 而一个简单对象就是指通过对象字面量和new Object()创建的对象,如果不是就报错。
if (!isPlainObject(action)) {
throw new Error(
....
)
}
// reducer内部是根据action的type属性来switch-case,
// 决定用什么逻辑来计算state的,所以type属性是必须的。不传会报错
if (typeof action.type === 'undefined') {
throw new Error(
'......'
)
}
// 如果是已经在dispatch的,就报错,避免不一致
if (isDispatching) {
throw new Error('.....')
}
// 计算新的state,并赋值给currentState
try {
isDispatching = true
// 根据reducer计算出新的state
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// state更新后,将subscribe中注册的监听器,回调触发一遍
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
// 返回当前的action
return action
}
分析下dispach的操作:
- 将isDispatching置为true
- 根据reducer计算出新的state
- 得到新的state值,isDispatching置回false
- 一一通知订阅者state数据已经更新了
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.'
)
}
//返回当前的state tree
return currentState
}
这里就可以看出来getState是直接返回currentState,如果我们直接修改state的话,就不会通知订阅者做更新了。
subscribe
function subscribe (listener) {
// listener是state变化时的回调,必须是个函数
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
// 如果是正在dispatch中,就报错。因为要确保state变化时,
// 监听器的队列也必须是最新的。所以监听器的注册要在计算新的state之前。
if (isDispatching) {
if (isDispatching) {
throw new Error(
......
)
}
//标记有订阅的 listener
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)
}
}
- 首先标记正在订阅
- 保存了一份快照
function ensureCanMutateNextListeners() {
// 如果nextListeners和currentListeners是否为同一个引用,那么就复制一份,做一个浅拷贝
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
其实我们曾经在dispatch中做了const listeners = (currentListeners = nextListeners)
的操作,那为什么这里又要去给他做浅拷贝呢?
因为有这么一种的情况存在。当redux在通知所有订阅者的时候,此时又有一个新的订阅者加进来了。如果只用currentListeners的话,当新的订阅者插进来的时候,就会打乱原有的顺序,从而引发一些严重的问题。
- 将新的订阅者加入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 })
}
这个方法将reducer替换成传入的参数,并且触发一次state更新操作。
combineReducers.js
因为createStore只接受一个reducer,这个方法主要用来整合reducer。
第一步:检验用户传入的reducers的准确性
// 把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]
}
}
//把 finalReducers 对象中可枚举的属性转换成一个数组
const finalReducerKeys = Object.keys(finalReducers)
对传入的reducer做了过滤操作,过滤掉不是函数的部分,然后用定义的finalReducers和finalReducerKeys分别存放拷贝后的reducers和其属性。
第二步:调用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(
`....`
)
}
if (
typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined'
) {
throw new Error(
`......`
)
}
})
}
主要用来判断每个reducer是否都有默认返回值
第三步:返回了一个函数,这个函数接收 state 和action
也就是之前creteStore中调用的reducer
currentReducer(currentState, action)
return function combination(state = {}, action) {
......
// 定义了一个hasChanged变量用来表示state是否发生变化
let hasChanged = false
const nextState = {}
//循环遍历 finalReducerKeys ,执行所有的 reducer, 所以一定不要有相同的 action.type ,否则状态一定会混乱的
for (let i = 0; i < finalReducerKeys.length; i++) {
//获取当前的 key
const key = finalReducerKeys[i]
//获取当前 reducer
const reducer = finalReducers[key]
//获取当前 key 的 state
const previousStateForKey = state[key]
//执行 reducer ,获取 state
const nextStateForKey = reducer(previousStateForKey, action)
//判断执行完Reducer后, 返回回来的 nextStateForKey 是 undefined
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
//赋值给 nextState
nextState[key] = nextStateForKey
//判断 state 是否经过 Reducer 改变了(判断地址是否改变)
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 改变了的话,返回新的state否则返回之前的state
return hasChanged ? nextState : state
}
从这里
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
我们可以看出来,reducer为什么一定要是个纯函数。
compose
当需要使用多个redux中间件 依次执行的时候,需要用到它。
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,reduce接收一个回调函数,那我们把这段代码中的箭头函数先去掉:
function compose(...funcs) {
return funcs.reduce(function (total, currentVal) {
return function () {
return a(b.apply(undefined, arguments));
}
})
}
也就是说如果传入a,b两个参数
function compose(a, b) {
return a(b())
}
所以compose的执行顺序是从右向左。
applyMiddleware(...middlewares)
Middleware
主要是包装store
的dispatch
方法。可以同时传入多个middleWare
组合到一起使用,形成middleware
链。每个middleware
接受Store
的dispatch
和getState
函数作为命名参数,并返回一个函数。
这是文档中的Middleware的解释,之前在讲createStore的时候,讲到了enhancer
的结构
function enhancer(createStore) {
return (reducer,preloadedState) => {
//逻辑代码
.......
}
}
enhancer
实际上就是applyMiddleware(...middlewares)
的返回值。
我们继续看applyMiddleware
的源码:
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args) // 原始的store 此时的dispatch 就是原始的dispatch
// 声明一个dispatch
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的时候, 其实就是调用了这个dispatch, 而不是一开始声明的逻辑
// 这个dispatch是已经经过compose的包装的函数
// const composeRes = compose(...chain);
// 第二行通过传入store.dispatch,
// 这个store.dispatch就是最后一个 next => action => {}的next参数
// 所以中间件其实就是改装dispatch
// dispatch作为有中间件的store的dispatch属性输出
// 当用户调用dispatch时, 中间件就会一个一个
// 执行完逻辑后, 将执行权给下一个, 直到原始的store.dispacth, 最后计算出新的state
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
第一步:通过传入的createStore创建了一个store
第二步:声明了一个原始的dispatch,如果在中间件的调用过程中出现了错误,则抛出错误
第三步:定义middlewareAPI,有两个方法,一个是getState,另一个是dispatch,将其作为每个中间件调用的store的参数,整合出一个chain
第四步:通过compose的包装chain,并赋值给dispatch
第五步:将新的dispatch替换原先的store.dispatch