redux作为react生态圈中最常见的状态库,从源码来探究一下redux是如何做状态管理的。redux源码并不复杂,了解源码之前,我们先看看我们会怎么使用redux。
import {createStore, combineReducers, applyMiddleware} from 'redux'
import reduxThunk from 'redux-thunk'
const initState = {
list: []
}
// 纯函数,用做状态更新
function reducer(state = initState, action){
switch(action.type){
case 'todoAdd':
return {
list: state.list.concat(action.text)
}
case 'todoRemove':
return {
list: state.list.filter((v) => v !== action.text)
}
default:
return state
}
}
/*
// 多个reducer可以使用combineReducers合并成一个reducer
const reducer = combineReducers({
todo,
user,
})
*/
// 接受reducer返回store reducer多数情况下不止一个你可能会用combineReducers
// applyMiddleware的作用就是注册redux中间件重新包装dispatch赋予dispatch新能力
// reduxThunk赋予action有处理异步的能力
let store = createStore(reducer, applyMiddleware(reduxThunk))
//订阅store更新
store.subscribe(() => {
// 在这里可以获取到最新的状态 来更新你的应用
console.log(store.getState())
})
//派发action,修改可监听状态的唯一方式
/*{
type: 'todoAdd',
text: '吃饭',
} 这就是action的格式
*/
store.dispatch({
type: 'todoAdd',
text: '吃饭',
})
store.dispatch({
type: 'todoAdd',
text: '睡觉',
})
store.dispatch({
type: 'todoRemove',
text: '睡觉',
})
接下来我们从例子入手来分析源码
createStore
这是我们创建store的入口,源码如下:
export default function createStore<
S,
A extends Action,
Ext = {},
StateExt = never
>(
reducer: Reducer,
preloadedState?: PreloadedState | StoreEnhancer,
enhancer?: StoreEnhancer
): Store, A, StateExt, Ext> & Ext {
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.'
)
}
// 如果第二个参数是函数,把第二个参数当作enhancer,也就是说preloadedState可以忽略不传
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState as StoreEnhancer
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
// 检测 enhancer 的类型是否合法
if (typeof enhancer !== 'function') {
throw new Error(
`Expected the enhancer to be a function. Instead, received: '${kindOf(
enhancer
)}'`
)
}
// 扩展包装createStore
// 其实就是applyMiddleware返回的函数
return enhancer(createStore)(
reducer,
preloadedState as PreloadedState
) as Store, A, StateExt, Ext> & Ext
}
// 检查reducer合法性
if (typeof reducer !== 'function') {
throw new Error(
`Expected the root reducer to be a function. Instead, received: '${kindOf(
reducer
)}'`
)
}
let currentReducer = reducer
let currentState = preloadedState as S
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
let isDispatching = false
/**
* This makes a shallow copy of currentListeners so we can use
* nextListeners as a temporary list while dispatching.
*
* This prevents any bugs around consumers calling
* subscribe/unsubscribe in the middle of a dispatch.
*/
// 浅拷贝监听列表
// 浅拷贝的目的可能是防止正在进行派发通知的时候可能又有监听加入进来防止顺序出错
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
// 返回当前状态
/**
* Reads the state tree managed by the store.
*
* @returns The current state tree of your application.
*/
function getState(): S {
// 如果是正在做派发更改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 as S
}
/**
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
*
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param listener A callback to be invoked on every dispatch.
* @returns A function to remove this change listener.
*/
// 订阅store状态的变化
function subscribe(listener: () => void) {
// 非函数抛错
if (typeof listener !== 'function') {
throw new Error(
`Expected the listener to be a function. Instead, received: '${kindOf(
listener
)}'`
)
}
// 如果正在做状态更改的操作抛出异常 状态更新完毕才能做监听
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/store#subscribelistener for more details.'
)
}
// 设置标志为正在监听中
let isSubscribed = true
// 浅拷贝监听函数 至 nextListeners中
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/store#subscribelistener for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* @param action A plain object representing “what changed”. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
function dispatch(action: A) {
// action必须是普通对象
if (!isPlainObject(action)) {
throw new Error(
`Actions must be plain objects. Instead, the actual type was: '${kindOf(
action
)}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
)
}
// action类型必须存在
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
)
}
// 是否正在做状态更改操作
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
// 调用 reducer 返回新的状态
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
}
/**
* Replaces the reducer currently used by the store to calculate the state.
*
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
*
* @param nextReducer The reducer for the store to use instead.
* @returns The same store instance with a new reducer in place.
*/
function replaceReducer(
nextReducer: Reducer
): Store, NewActions, StateExt, Ext> & Ext {
if (typeof nextReducer !== 'function') {
throw new Error(
`Expected the nextReducer to be a function. Instead, received: '${kindOf(
nextReducer
)}`
)
}
// TODO: do this more elegantly
;(currentReducer as unknown as Reducer) = nextReducer
// This action has a similar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
// will receive the previous state. This effectively populates
// the new state tree with any relevant data from the old one.
dispatch({ type: ActionTypes.REPLACE } as A)
// change the type of the store by casting it to the new store
return store as unknown as Store<
ExtendState,
NewActions,
StateExt,
Ext
> &
Ext
}
/**
* Interoperability point for observable/reactive libraries.
* @returns A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
const outerSubscribe = subscribe
return {
/**
* The minimal observable subscription method.
* @param observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns 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: unknown) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError(
`Expected the observer to be an object. Instead, received: '${kindOf(
observer
)}'`
)
}
function observeState() {
const observerAsObserver = observer as Observer
if (observerAsObserver.next) {
observerAsObserver.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT } as A)
const store = {
dispatch: dispatch as Dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
} as unknown as Store, A, StateExt, Ext> & Ext
return store
}
createStore接受三个参数:
- reducer 更新state的村函数
- preloadedState 初始化的状态值
- enhancer 增强函数会返回一个新的store对象,对于我们的例子中就是applyMiddleware的返回函数
返回的store对象包含如下方法:
- dispatch 派发,也就是更新状态的入口
- subscribe 订阅状态更新
- getState 获取当前状态
- replaceReducer 用得少暂不关心,看源码是用来reducer插队的,也就是插队更新
从createStore主函数开始
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.'
)
}
// 如果第二个参数是函数,把第二个参数当作enhancer,也就是说preloadedState可以忽略不传
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState as StoreEnhancer
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
// 检测 enhancer 的类型是否合法
if (typeof enhancer !== 'function') {
throw new Error(
`Expected the enhancer to be a function. Instead, received: '${kindOf(
enhancer
)}'`
)
}
// 扩展包装createStore
// 其实就是applyMiddleware返回的函数
return enhancer(createStore)(
reducer,
preloadedState as PreloadedState
) as Store, A, StateExt, Ext> & Ext
}
// 检查reducer合法性
if (typeof reducer !== 'function') {
throw new Error(
`Expected the root reducer to be a function. Instead, received: '${kindOf(
reducer
)}'`
)
}
let currentReducer = reducer
let currentState = preloadedState as S
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
let isDispatching = false
第一个if判断实在判断enhancer是不是连续传了两个,第二个if判定,本身create接受三个参数按照顺序是reducer,preloadedState(初始化状态),enhancer(扩展函数),createStore允许只传递两个函数,不传preloadedState。所以出现了enhancer = preloadedState这样子的代码,第三个if表示enhancer存在要扩展store的能力。至于是什么能力,大部分情况下是看你的redux中间件的功能是什么。
接下来getState就是简单的返回当前的状态对象
subscribe
订阅,redux的状态更新是基于发布订阅模式的,也就是说有状态更新就会出发subscribe的订阅回掉。subscribe源码如下:
// 订阅store状态的变化
function subscribe(listener: () => void) {
// 非函数抛错
if (typeof listener !== 'function') {
throw new Error(
`Expected the listener to be a function. Instead, received: '${kindOf(
listener
)}'`
)
}
// 如果正在做状态更改的操作抛出异常 状态更新完毕才能做监听
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/store#subscribelistener for more details.'
)
}
// 设置标志为正在监听中
let isSubscribed = true
// 浅拷贝监听函数 至 nextListeners中
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/store#subscribelistener for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
前面两个判定依然是在做合理性校验,依次判定监听回掉是否是函数判定,以及当前是否有dispatch操作正在进行中未完成。ensureCanMutateNextListeners的作用是做subscribe回掉函数的浅拷贝。将当前监听对象拷贝到nextListeners中,浅拷贝的目的是防止新来的监听打乱原来的监听对象顺序,所以没有直接操作原来的监听队列。subscribe会返回一个取消监听的函数
dispatch
触发更新的dispatch函数,源码如下:
function dispatch(action: A) {
// action必须是普通对象
if (!isPlainObject(action)) {
throw new Error(
`Actions must be plain objects. Instead, the actual type was: '${kindOf(
action
)}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
)
}
// action类型必须存在
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
)
}
// 是否正在做状态更改操作
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
// 调用 reducer 返回新的状态
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
}
前面的判定都是在做合理性校验,isPlainObject在检查action是否是一个普通对象,普通对象只能是通过对象字面量或者new Object创建出来的。所以我们一般写action都是{type: '**', paload: {} }这种方式。其他的action.type必须存在,isDispatching的作用是防止上一个更新操作还没有完成,新的dispatch又开始了,这样子会出现状态混乱,所以如果上次更新操作没有完成,就不能进行dispatch。做完
reducer的更新操作就开始顺序执行listeners,listeners就是store.subscribe的监听函数。通知监听函数状态发生了变化,你可以开始做状态更新之后的操作,比如:更新视图
applyMiddleware
applyMiddleware的作用是注册redux中间件就像我们上面的例子createStore的第二个参数就是注册了redux-thunk这个中间件,给action赋予了处理异步的能力,那这是怎么办到的呢。先看applyMiddleware源码:
export default function applyMiddleware(
...middlewares: Middleware[]
): StoreEnhancer {
return (createStore: StoreEnhancerStoreCreator) =>
(
reducer: Reducer,
preloadedState?: PreloadedState
) => {
const store = createStore(reducer, preloadedState)
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 重新包装dispatch 这里就可以给dispatch赋予异步能力了
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
可以看见applyMiddleware接受中间件列表。然后返回了一个新的store对象。所以applyMiddleware的作用就是通过传递过来的中间件来扩展store,再看applyMiddleware返回的函数:
(createStore: StoreEnhancerStoreCreator) =>
(
reducer: Reducer,
preloadedState?: PreloadedState
) => {...}
返回了一个函数接受createStore,然后在接受reducer,preloadedState做操作,这个返回函数就和createStore中的enhancer是一样的。会看createStore中enhancer的调用:
enhancer(createStore)(
reducer,
preloadedState as PreloadedState
) as Store, A, StateExt, Ext> & Ext
他们是一样的。
接下来我们查看applyMiddleware是如何处理中间件重新包装的,核心代码:
const store = createStore(reducer, preloadedState)
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 重新包装dispatch 这里就可以给dispatch赋予异步能力了
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
看看redux中间件的格式,redux中间件代码需要如下的样子:
const middlware = (store) => (next) => (action) => {
return next()
}
为什么需要这样子,因为redux绑定中间件和store的代码其实就是去柯里化的过程:
第一步:
const chain = middlewares.map(middleware => middleware(middlewareAPI))
这一步就是去柯里化第一层,传递store进去也就是middlewareAPI。
第二步:
dispatch = compose(...chain)(store.dispatch)
先看看compose这个函数,代码如下:
export default function compose(...funcs: Function[]) {
if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return (arg: T) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce(
(a, b) =>
(...args: any) =>
a(b(...args))
)
}
经过第一步的去柯里化操作,现在的中间件已经变成了这个样子:
const chain = [ (next) => (action) => {...} ];
当然由于闭包chain中所有的元素都可以访问到middlewareAPI,这便是第一步的作用绑定middlewareAPI到中间件中去。再看compose函数主要就是通过reduce,reduce这个api数组调用,reduce的回掉会接受两个参数a, b。a是上一次reduce调用的结果,b是当前的元素。一直到数组遍历完。这就是第二步去柯里化的操作。
也就是说。如果有a,b,c三个中间件,这一步会这样子。a(b(c())).c的结果返回给b作为参数b的结果返回给a作为参数。业界形象的说这叫“洋葱模型”。所以这一步操作是在给next确定值,next的格式就是(action) => {...}这样子,就是一个dispatch的样子,直到最后返回一个新的(action) => {...}也就是dispath。
那如果我们要处理异步,我们就可以写一个异步中间件,如下:
const syncMiddlware = (store) => (next) => (action) => {
// 我们可以吧action写成函数,函数接受dispatch,也就是让action决定什么时候去派发,这样子就可以在action中做异步处理了
if(typeof action === 'function'){
action(store.dispatch)
}else{
return next(action)
}
}