前几天写了一篇react另外一个状态管理工具Unstated
的源码解析。 开启了个人看源码之路。想想用了好长时间的redux,但从没有深究过原理,遇到报错更是懵逼,因此就啃了一遍它的源码,写了这篇文章, 分享我对于它的理解。react
看一下redux源码的index.js,看到了咱们最经常使用的几个API:ios
不着急分析,咱们先看一下Redux的基本用法:redux
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
const root = document.getElementById('root')
// reducer 纯函数
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
// 建立一个store
const store = createStore(reducer)
const render = () => ReactDOM.render(
<div>
<span>{store.getState()}</span>
<button onClick=={() => store.dispatch({ type: 'INCREMENT' })}>INCREMENT</button>
<button onClick=={() => store.dispatch({ type: 'DECREMENT' })}>DECREMENT</button>
</div>,
root
)
render()
// store订阅一个更新函数,待dispatch以后,执行这个更新函数,获取新的值
store.subscribe(render)
复制代码
这里实现的是一个点击按钮加减数字的效果,点击触发的行为,与展现在页面上的数字变化,都是经过redux进行的。咱们经过这个例子来分析一下redux是怎么工做的:axios
store.getState()
拿到了当前的数字,初始值为0(在reducer中)createStore总共接收三个参数:reducer
, preloadedState
, enhancer
,api
暴露给咱们几个经常使用的API:数组
咱们先经过接收的参数和暴露出来的api梳理一下它的机制:bash
首先是接收上面提到的三个参数建立一个store,store是存储应用全部状态的地方。同时暴露出三个方法,UI能够经过store.getState()获取到store中的数据, store.subscribe(),做用是让store订阅一个更新UI的函数,将这个函数push到listeners数组中,等待执行。 store.dispatch()是更新store中数据的惟一方法,dispatch被调用后,首先会调用reducer,根据当前的state和action返回新的状态。而后循环调用listeners中的更新函数, 更新函数通常是咱们UI的渲染函数,函数内部会调用store.getState()来获取数据,因此页面会更新。app
看一下createStore函数的结构dom
createStore(reducer, preloadedState, enhancer) {
// 转换参数
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
function getState() {
// 返回当前的state, 能够调用store.getState()获取到store中的数据,
...
}
function subscribe(listener) {
// 订阅一个更新函数(listener),实际上的订阅操做就是把listener放入一个listeners数组
// 而后再取消订阅,将更新函数从listeners数组内删除
// 可是注意,这两个操做都是在dispatch不执行时候进行的。由于dispatch执行时候会循环执行更新函数,要保证listeners数组在这时候不能被改变
...
}
function dispatch(action) {
// 接收action,调用reducer根据action和当前的state,返回一个新的state
// 循环调用listeners数组,执行更新函数,函数内部会经过store.getState()获取state,此时的state为最新的state,完成页面的更新
...
}
return {
dispatch,
subscribe,
getState,
}
}
复制代码
结构就是这样,可是是如何串联起来的呢?下面来看一下完整的代码(删除了一些)async
createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// 有了这一层判断,咱们就能够这样传:createStore(reducer, initialState, enhancer)
// 或者这样: createStore(reducer, enhancer),其中enhancer还会是enhancer。
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// enhancer的做用是扩展store,因此传入createStore来改造,
// 再传入reducer, preloadedState生成改造后的store,这一有一点递归调用的意思
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
let currentReducer = reducer // 当前的reducer,还会有新的reducer
let currentState = preloadedState // 当前的state
let currentListeners = [] // 存储更新函数的数组
let nextListeners = currentListeners // 下次dispatch将会触发的更新函数数组
let isDispatching = false //相似一把锁,若是正在dispatch action,那么就作一些限制
// 这个函数的做用是判断nextListeners 和 currentListeners是不是同一个引用,是的话就拷贝一份,避免修改各自相互影响
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function getState() {
// 正在执行reducer的时候,是不能获取state的,要等到reducer执行完,返回新的state才能够获取
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
}
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
// 因为dispatch函数会在reducer执行完毕后循环执行listeners数组内订阅的更新函数,因此要保证这个时候的listeners数组
// 不变,既不能添加(subscribe)更新函数也不能删除(unsubscribe)更新函数
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()
// 将更新函数推入到listeners数组,实现订阅
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 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?'
)
}
// 正在dispatch的话不能再次dispatch,也就是说不能够同时dispatch两个action
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
// 获取到当前的state
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
// 循环执行当前的linstener
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
// dispatch一个初始的action,做用是不命中你reducer中写的任何关于action的判断,直接返回初始的state
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
// observable replaceReducer和$$observable主要面向库开发者,这里先不作解析
// replaceReducer,
// [$$observable]:
}
}
复制代码
combineReducers用于将多个reducer合并为一个总的reducer,因此能够猜出来, 它最终返回的必定是一个函数,而且形式就是通常的reducer的形式,接收state和action, 返回状态:
function combine(state, action) {
......
return state
}
复制代码
来看一下核心代码:
export default function combineReducers(reducers) {
// 获取到全部reducer的名字,组成数组
const reducerKeys = Object.keys(reducers)
// 这个finalReducers 是最终的有效的reducers
const finalReducers = {}
// 以reducer名为key,reducer处理函数为key,生成finalReducers对象,形式以下
/* {
* reducerName1: f,
* reducerName2: f
* }
*/
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
// assertReducerShape用来检查这每一个reducer有没有默认返回的state,
// 咱们在写reducer时候,都是要在switch中加一个default的,来默认返回初始状态
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
// 这个函数,就是上边说的返回的最后的那个终极reducer,传入createStore,
// 而后在dispatch中调用,也就是currentReducer
// 这个函数的核心是根据finalReducer中存储的全部reducer信息,循环,获取到每一个reducer对应的state,
// 并依据当前dispatch的action,一块儿传入当前循环到的reducer,生成新的state,最终,将全部新生成的
// state做为值,各自的reducerName为键,生成最终的state,就是咱们在reduxDevTool中看到的state树,形式以下:
/* {
* reducerName1: {
* key: 'value'
* },
* reducerName2: {
* key: 'value'
* },
* }
*/
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
// 存放最终的全部的state
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
// 获取每一个reducer的名字
const key = finalReducerKeys[i]
// 获取每一个reducer
const reducer = finalReducers[key]
// 获取每一个reducer的旧状态
const previousStateForKey = state[key]
// 调用该reducer,根据这个reducer的旧状态,和当前action来生成新的state
const nextStateForKey = reducer(previousStateForKey, action)
// 以各自的reducerName为键,新生成的state做为值,生成最终的state object,
nextState[key] = nextStateForKey
// 判断全部的state变化没变化
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 变化了,返回新的state,不然,返回旧的state
return hasChanged ? nextState : state
}
}
复制代码
redux本来的dispatch方法只能接受一个对象做为action
用户操做 -> dispatch(action) -> reducer(prevState, action) -> 新的state -> 界面
这么直接干脆的操做当然好,可让每一步的操做可追踪,方便定位问题,可是带来一个坏处,好比,页面须要发请求获取数据,而且把数据放到action里面, 最终经过reducer的处理,放到store中。这时,如何作呢?
用户操做 -> dispatch(
action
) -> middleware(action
) -> 真正的action -> reducer(prevState, action) -> 新的state -> 界面
重点在于dispatch(action
) -> middleware(action
) 这个操做,这里的action
能够是一个函数,在函数内咱们就能够进行不少操做,包括调用API, 而后在调用API成功后,再dispatch真正的action。想要这么作,那就是须要扩展redux(改造dispatch方法),也就是使用加强器:enhancer:
const store = createStore(rootReducer,
applyMiddleware(thunk),
)
复制代码
applyMiddleware(thunk)
就至关于一个enhancer,它负责扩展redux,说白了就是扩展store的dispatch方法。
既然要改造store,那么就得把store做为参数传递进这个enhancer中,再吐出一个改造好的store。吐出来的这个store的dispatch方法,是enhancer改造store的最终实现目标。
回顾一下createStore中的这部分:
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 把createStore传递进enhancer
return enhancer(createStore)(reducer, preloadedState)
}
复制代码
看下上边的代码,首先判断enhancer,也就是createStore的第三个参数不为undefined且为函数的时候,那么去执行这个enhancer。
咱们看到enhancer(createStore),是把createStore传入,进行改造,先无论这个函数返回啥,咱们先看它执行完以后还须要的参数 (reducer, preloadedState)
, 是否是有点眼熟呢?回想一下createStore的调用方法,createStore(reducer, state)。
由此可知enhancer(createStore)返回的是一个新的createStore,而这个createStore是被改造事后的,它内部的dispatch方法已经不是原来的了。至此,达到了改造store的效果。
那究竟是如何改造的呢? 先不着急,咱们不妨先看一个现成的中间件redux-thunk。要了解redux中间件的机制,必需要理解中间件是怎么运行的。
咱们先来看用不用它有什么区别:
通常状况下,dispatch的action是一个纯对象
store.dispatch({
type:'EXPMALE_TYPE',
payload: {
name:'123',
}
})
复制代码
使用了thunk以后,action能够是函数的形式
function loadData() {
return (dispatch, getState) => { // 函数以内会真正dispatch action
callApi('/url').then(res => {
dispatch({
type:'LOAD_SUCCESS',
data: res.data
})
})
}
}
store.dispatch(loadData()) //派发一个函数
复制代码
通常状况下,dispatch一个函数会直接报错的,由于createStore中的dispatch方法内部判断了action的类型。redux-thunk帮咱们作的事就是改造dispatch,让它能够dispatch一个函数。 看一下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({ dispatch, getState })(next)(action)。
其中,thunk({ dispatch, getState})(next)这部分,看它执行时接收的参数是一个action,那么它必然是一个dispatch方法,在此处至关于改造事后的dispatch,而这部分会在applyMiddleware中去调用,(下边会讲到)
而后从左往右看,{ dispatch, getState }是当前store的dispatch和getState方法,是最原始的,便于在通过中间件处理以后,能够拿到最原始的dispatch去派发真正的action。
next则是被当前中间件改造以前的dispatch。注意这个next,他与前边的dispatch并不同,next是被thunk改造以前的dispatch,也就是说有多是最原始的dispatch,也有多是被其余中间件改造过的dispatch。
为了更好理解,仍是翻译成普通函数嵌套加注释吧
function createThunkMiddleware(extraArgument) {
return function({ dispatch, getState }) { //真正的中间件函数,内部的改造dispatch的函数是精髓
return function(next) { //改造dispatch的函数,这里的next是外部传进来的dispatch,多是被其余中间件处理过的,也多是最本来的
return function(action) { //这个函数就是改造事后的dispatch函数
if (typeof action === 'function') {
// 若是action是函数,那么执行它,而且将store的dispatch和getState传入,便于咱们dispatch的函数内部逻辑执行完以后dispatch真正的action,
// 如上边示例的请求成功后,dispatch的部分
return action(dispatch, getState, extraArgument);
}
// 不然说明是个普通的action,直接dispatch
return next(action);
}
}
}
}
const thunk = createThunkMiddleware();
复制代码
总结一下:说白了,redux-thunk的做用就是判断action是否是一个函数,是就去执行它,不是就用那个可能被别的中间件改造过的,也多是最原始的dispatch(next)去派发这个action。
那么接下来看一下applyMiddleware的源码:
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 => {
// 假设咱们只是用了redux-thunk,那么此时的middleware就至关于thunk,能够往上看一下thunk返回的函数,
// 就是这个: function({ dispatch, getState }),就会明白了
return middleware(middlewareAPI)
})
// 这里的compose函数的做用就是,将全部的中间件函数串联起来,中间件1结束,做为参数传入中间件2,被它处理,
// 以此类推最终返回的是被全部中间件处理完的函数,最开始接收store.dispatch为参数,层层改造后被赋值到新的dispatch变量中
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
复制代码
先看最简单的状况:假设咱们只使用了一个middleware(redux-thunk),就能够暂时抛开compose,那么这里的逻辑就至关于 dispatch = thunk(middlewareAPI)(store.dispatch) 是否是有点熟悉? 在redux-thunk源码中咱们分析过:
真正调用thunk的时候,thunk({ dispatch, getState })(next)(action) 其中,thunk({ dispatch, getState })(next)这部分,至关于改造事后的dispatch,而这部分会在applyMiddleware中去调用
因此,这里就将store的dispatch方法改造完成了,最后用改造好的dispatch覆盖原来store中的dispatch。
来总结一下,
另外说一下,关于redux-thunk的一个参数:extraArgument
这个参数不是特别重要的,通常是传入一个实例,而后在咱们须要在真正dispatch的时候须要这个参数的时候能够获取到,好比传入一个axios 的Instance,那么在请求时候就能够直接用这个instance去请求了
import axiosInstance from '../request'
const store = createStore(rootReducer, applyMiddleware(thunk.withExtraArgument(axiosInstance)))
function loadData() {
return (dispatch, getState, instance) => {
instance.get('/url').then(res => {
dispatch({
type:'LOAD_SUCCESS',
data: res.data
})
})
}
}
store.dispatch(loadData())
复制代码
到这里,redux几个比较核心的概念就讲解完了,不得不说写的真简洁,函数之间的依赖关系让我一度十分懵逼,要理解它仍是要用源码来跑一遍例子, 一遍一遍地看。
总结一下redux就是建立一个store来管理全部状态,触发action来改变store。关于redux的使用场景是很是灵活的,能够结合各类库去用,我用惯了react,用的时候还要配合react-redux。