前言
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。主要解决了组件间通信问题,redux通过对store的管理和控制,可以很方便的实现页面状态的管理和切片。
阅读本片文章你能学到createStore、compose、applyMiddleware、combineReduces的源码解析,包括洋葱模型,高阶组件,科里化等高级知识点,废话不多说,下面直接步入主题。
createStore
createStore是redux的核心,用于生成一个store实例。
// reducer必须是一个函数,preloadedState是一个对象,enhancer是一个函数
// enhancer是加强store.dispatch的函数
export function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
// 如果使用了中间件会返回一个包含被加强的dispatch的store实例。
if (typeof enhancer !== 'undefined') {
// 这一步主要是对dispatch的加强
return enhancer(createStore)(reducer, preloadedState)
}
// 如果没有使用中间件会向下执行返回一个普通store实例
let currentReducer = reducer
// 当前state
let currentState = preloadedState
// 当前监听函数数组
let currentListeners = []
// 下一个监听函数数组
let nextListeners = currentListeners
// 判断是否在执行dispatch里面的reducer函数
let isDispatching = false
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice() // 浅拷贝
}
}
function getState() { // 典型的闭包
if (isDispatching) {
throw new Error('你不能在reducer执行的时候调用store.getState()')
}
return currentState
}
function subscribe(listener) { // 添加订阅的方法
// 防止你在reducer函数里调用dispatch,dispatch一个action,action又会执行reducer,进入死循环
if (isDispatching) {
throw new Error('你不能在reducer执行的时候调用store.subscribe()')
}
let isSubscribed = true // 表示正在监听
ensureCanMutateNextListeners()
nextListeners.push(listener)
// 发布订阅有2种取消订阅方式:
// 一种是将函数push进去,如果有2个相同的就将当前的删除
// 一种是返回一个取消订阅的函数,此处使用了第二种
return function unsubscribe() {
if (!isSubscribed) { // 未监听直接返回
return
}
if (isDispatching) {
throw new Error('你不能在reducer执行的时候取消订阅')
}
isSubscribed = false
ensureCanMutateNextListeners()
// 通过index找到然后删除
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
function dispatch(action) { // action必须是包含type的对象
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true // 标识将要执行reducer函数
// 执行reducer函数会返回一个新的state,赋值给currentState
currentState = currentReducer(currentState, action)
} finally {
// 执行完reducer函数后,标识isDispatching为false
isDispatching = false
}
// state变化之后,通知订阅者状态已经更新
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
// 循环执行每个订阅者(本质是函数)
listener()
}
return action
}
function replaceReducer(nextReducer) { // nextReducer必须是函数,否则报错
currentReducer = nextReducer
//此操作的效果与ActionTypes.INIT类似。
dispatch({ type: ActionTypes.REPLACE })
}
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer) { // observer必须是对象
function observeState() {
if (observer.next) { // observer应该有next方法
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
},
}
}
// 创建store时 会dispatch一个“INIT”的action,
// reducer里面匹配不到对应的type就会返回默认值state
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable,
}
}
export const legacy_createStore = createStore
compose(...functions)
compose的作用是从右到左来组合多个函数。当需要把多个 store 增强器 依次执行的时候,需要用到它。 参数functions是需要合成的多个函数,预计每个函数接收一个参数,它的返回值将作为一个参数提供给它左边的函数,以此类推。compose(funA,funB,funC) => compose(funA(funB(funC())))
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)))
// 如果这个不好理解可以写成下面这样
// return funcs.reduce((a,b) => {
// return (...args) => {
// return a(b(...args))
// }
//})
}
const add = x => x + 2
const mutiply = y => y * 2
compose(add, multiply) // 会先执行multiply,得到的结果作为参数传到add函数执行
applyMiddleware
applyMiddleware是一个增强器,组合多个中间件,最终增强store.dispatch函数,dispatch时,可以串联执行所有中间件。applyMiddleware的函数签名是:(...middleware) => (createStore) => (...agrs) => {}
- 我们通过createStore(reducers, [], applyMiddleware(中间件名称))时 会先执行applyMiddleware得到函数(createStore) => (...args) => {}
- 之后会真正执行createStore函数,由上面的createStore源码可以看出,使用了中间件的时候createStore会 执行return enhancer(createStore)(reducer,preloadedState)
- 执行enhancer(createStore)就是执行(createStore) => (...args) => {} 得到签名为
(...args) => {}的函数。 - 之后执行(...args) => {}函数将reducer和preloadedState作为参数传进去。
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
// 执行到这的时候,我们指定...args其实就是调用enhancer(createStore)(reducer,preloadedState)
// 传进来的参数reducer和preloadedState
const store = createStore(...args) // 将参数传进去得到一个store实例对象,以便跟增强的dispatch一起返回
let dispatch = () => {
throw new Error('不允许在构建中间件时进行dispatch。其他中间件将不会应用于该dispatch')
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
}
// 遍历中间件,将middlewareAPI传进去,得到的chain是一个数组
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
利用compose函数依次执行中间件函数得到加强后的dispatch
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch,
}
}
}
注意上面middlewareAPI中的dispatch必须写成一个匿名函数,如果直接将store.dispatch返回,中间件中拿到的是原始的dispatch,而写成匿名函数利用闭包原理,拿到的是经过compose(...chain)(store.dispatch)加强后的dispatch。
洋葱模型
洋葱模型执行的过程就是从外面一层一层的进去再一层一层的出来。
下面我们定义三个中间件,依次执行
function M1 ({getState}) {
return (next) => {
return (action) => {
console.log('M1 开始')
// 调用middleware链中下一个middleware的dispatch
next(action)
console.log('M1 结束')
}
}
}
function M2 ({getState}) {
return (next) => {
return (action) => {
console.log('M2 开始')
// 调用middleware链中下一个middleware的dispatch
next(action)
console.log('M2 结束')
}
}
}
function M3 ({getState}) {
return (next) => {
return (action) => {
console.log('M3 开始')
// 调用middleware链中下一个middleware的dispatch
next(action)
console.log('M3 结束')
}
}
}
function reducers (state,action) {
if(action.type === 'ADD') {
console.log('======')
}
return {}
}
const store = createStore(reducers, {}, applyMiddleware(M1, M2, M3))
store.dispatch({type: 'ADD'})
- 调用dispatch({type: 'ADD'})后会先执行M1里的(action)=>{},打印'M1 开始'
- M1里遇到next(action)会执行下一个中间件M2
- 执行M2的副作用函数(action)=>{},打印'M2 开始'
- M2里调用next(action)会执行下一个中间件M3
- 执行M3的副作用函数(action)=>{},打印'M3 开始'
- 由于M3中间件的next是store.dispatch,就会执行dispatch方法,打印'======='
- M3执行完next(action)之后,打印'M3 结束',M3副作用执行完毕回到M2
- M2打印'M2 结束', M2副作用执行完毕回到M1
- M1打印'M1 结束', 至此中间件都被执行完毕。
M1、M2、M3的next是如何绑定的呢?
先把M1、M2、M3中间件完整的函数签名列出来
M1:store=>next=>action=>{next(action)}
M2:store=>next=>action=>{next(action)}
M3:store=>next=>action=>{next(action)}
applyMiddleware里执行完middleware.map(mid => mid(middlewareAPI))后就将getState和dispatch绑定了,M1、M2、M3中间件函数签名变成下面这样,分别对应A1,B1,C1
M1的签名A1:next=>action=>{next(action)}
M2的签名B1:next=>action=>{next(action)}
M3的签名C1:next=>action=>{next(action)}
chain是一个数组[(next)=>(action)=>{}, (next)=>(action)=>{}, ...]
compose(...chain)执行完后得到函数Fun:()=> A1(B1(C1(...argument)))
也就是中间件M3执行的结果作为参数传入到M2,M2执行的结果作为参数传给M1
执行Fun传入(store.dispatch),是将store.dispatch作为next先传到了M3,而M2和M1的next分别是M3和M2执行的结果。
M1、M2、M3的函数签名变成下面这样分别对应A2,B2,C2
M1的签名A2:action=>{next(action)}
M2的签名B2:action=>{next(action)}
M3的签名C2:action=>{next(action)}
M3的next是store.dispatch,M2的next是M3的函数签名,M1的next是M2的函数签名
所以 dispatch = M1的签名:action=>{next(action)}
当页面调用store.dispatch时就在执行增强的dispatch,执行M1的函数签名,M1的next是M2的函数签名, M2函数签名中的next是M3的函数签名,最终M3接收action执行了reducer。
洋葱模型的好处:如果有多个中间件,有些中间件需要依赖其他中间件的结果,如果没有洋葱模型执行顺序可能会出乎我们的预期,而洋葱模型可以保证执行的顺序
combineReducers
combineReducers辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore方法。
每个传入combineReducers的 reducer 都需满足以下规则:
- 所有未匹配到的 action,必须把它接收到的第一个参数也就是那个
state原封不动返回。 - 永远不能返回
undefined。遇到这种情况时combineReducers会抛异常。 - 如果传入的
state就是undefined,一定要返回对应 reducer 的初始 state。
function combineReducers(reducers) { // reducers是对象
const reducerKeys = Object.keys(reducers)
const finalReducers = {} // 最终汇总的reducer
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
// reducer是函数才收集到finalReducers
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
/****省略了一些校验****/
return function combination(state = {}, action) {
/****省略了一些校验**/
// hasChanged用来标识状态是否改变,改变了返回新状态否则返回初始状态
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]
// 执行reducer得到新值
const nextStateForKey = reducer(previousStateForKey, action)
/****此处省略了nextStateForKey是undefined的异常处理***/
// 如果新值不是undefined,保存到nextState
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state
}
}
总结
以上就是redux源码解析的所有内容了。相信大家对redux的原理有了充分的了解,如果学过vuex的还可以点此查看Vuex源码解析,对比二者之间的区别。最后的最后,别忘了点赞哟!