在写 react
的项目中, redux
经常被使用,所以明白 redux 的源码就显得很重要,虽然最近我看到有人推荐了其他的状态管理库,但是目前 redux
仍然是使用面最广的。
1. 从官方示例 todos 出发
我们先看 index.js
文件中内容:
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './components/App'
import rootReducer from './reducers'
const store = createStore(rootReducer)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
这里跟 redux
相关的就只有 createStore
函数,我们使用的时候知道,这个就是创建 store
。我们更新 store 中的数据可以通过 store.dispatch
函数来完成,我们知道这样只能修改 store
中的数据,实际上界面的刷新并非这个控制,所以对于我们来说要想更新界面,那么就需要与 react-redux
,而这个是另外的库,我们现在暂时先跳过,将来补上,我们就假定只要执行 store.dispatch
这个函数就更新。
我们先看这个 api
的说明,看看各个参数的含义:
这个示例就传入一个参数,就是 reducer
,这是个函数,函数接受两个参数,一个是 state
,也就是当前的状态;还有一个是 action
,就是下一步的行为,一般包含一个 type
属性,用来表明此时具体的行为。现在我们看看示例中传入的 rootReducer
是怎样编写的:
import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'
export default combineReducers({
todos,
visibilityFilter
})
这里又出现一个新的 api
, combineReducers
是将定义的多个 reducer
合并成一个,到时候看源码再具体说说,我们看其中一个 reducer
:
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
case 'TOGGLE_TODO':
return state.map(todo =>
(todo.id === action.id)
? {...todo, completed: !todo.completed}
: todo
)
default:
return state
}
}
export default todos
这里定义了两个行为,一个是 ADD_TODO
,是添加 todo
的;还有一个是 TOGGLE_TODO
,是切换 todo
完成状态的。现在我们看看是怎样更新数据的,也就是调用 dispatch
的地方:
dispatch({
type: 'TOGGLE_TODO',
id
})
我们知道上面的 action 也就是这里传入的值。
2. createStore
根据示例我们知道这个函数返回的是一个对象:
{
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
2.1 store.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 是不是包含 type 属性
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.'
)
}
// 是否正在 dispatch 中
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
// isDispatching 置为 true ,保证总是只有一个 dispatch 进行时
isDispatching = true
// 拿着之前的 state 和传入的 action 得到当前的 state
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 通知所有的监听者
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
// 返回当前 action ,如果有中间件这里的返回值就有用处了
return action
}
我们发现非常简单就是更新 state 的情况下同时触发监听。
下面我们看一个更简单的,也就是 getState
。
2.2 store.getState
function getState(): S {
// 如果正在 dispatch 中,那么就抛出错误
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
}
就是返回当前的 state
,如果更新了这个值就变成最新的那个值。我们上面看到当调用 dispatch
的时候就会触发监听。
2.3 store.subscribe
我们来看是怎么监听的,也就是 subscribe
函数的实现。
function subscribe(listener: () => void) {
// 如果不是方法就报错
if (typeof listener !== 'function') {
throw new Error(
`Expected the listener to be a function. Instead, received: '${kindOf(
listener
)}'`
)
}
// 如果正在 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/store#subscribelistener for more details.'
)
}
// 记录是否订阅,默认是 true
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.'
)
}
// 取消订阅了就置为 false
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
// 从数组中删除
nextListeners.splice(index, 1)
currentListeners = null
}
}
现在我们已经明白 redux 的核心实现了,其他的一般很少使用。
2.4 原理流程图
所以我们再梳理梳理其核心原理。
跟网上的比起来,我新增了订阅器和触发,主要体现订阅的这一过程,所以 redux
很简单,就是我们大家熟知的观察者模式。
3. combineReducers
下面我们看一下上面遇到的 combineReducers
函数的实现:
function combineReducers(reducers: ReducersMapObject) {
// 拿到所有的 key ,所以 key 的命令取决于这个
const reducerKeys = Object.keys(reducers)
// 保存对应值是方法的 reducer
const finalReducers: ReducersMapObject = {}
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]
}
}
// 取出是方法的 key
const finalReducerKeys = Object.keys(finalReducers)
// This is used to make sure we don't warn about the same
// keys multiple times.
let unexpectedKeyCache: { [key: string]: true }
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError: unknown
try {
// 检查每个 reducer 返回的值是否正确
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
// 返回合并的 reducer
return function combination(
state: StateFromReducersMapObject<typeof reducers> = {},
action: AnyAction
) {
// 如果有错误就直接抛出错误
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: StateFromReducersMapObject<typeof reducers> = {}
// 循环去更新每个 reducer 的值,所以 action.type 应该全局唯一,除非真的想同时更新多个
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)
if (typeof nextStateForKey === 'undefined') {
const actionType = action && action.type
throw new Error(
`When called with an action of type ${
actionType ? `"${String(actionType)}"` : '(unknown type)'
}, the slice reducer for key "${key}" returned undefined. ` +
`To ignore an action, you must explicitly return the previous state. ` +
`If you want this reducer to hold no value, you can return null instead of undefined.`
)
}
nextState[key] = nextStateForKey
// 看看是否有改变,如果是 true 就就不用判断后面的了
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 这里主要是看看长度是否发生改变
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
// 如果有改变就返回新的,否则发返回之前的,这样就不会刷新了
return hasChanged ? nextState : state
}
}
4. compose
从右到左来组合多个函数。
function compose(...funcs: Function[]) {
if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return <T>(arg: T) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce(
(a, b) =>
(...args: any) =>
a(b(...args))
)
}
这里的函数是后面的中间件的执行的结果传递给前面的中间件。比如你的数组为: [a, b, c, d, e]
,那么就相当于 a(b(c(d(e(...args)))))
,按道理是先执行 e(...args)
,当然实际上也是,只不过由于这些中间件返回的都是函数,所以最后回调实际结果为 a -> b -> c -> d -> e
。
5. applyMiddleware
使用包含自定义功能的 middleware 来扩展 Redux 是一种推荐的方式。Middleware 可以让你包装 store 的 dispatch 方法来达到你想要的目的。同时, middleware 还拥有“可组合”这一关键特性。多个 middleware 可以被组合到一起使用,形成 middleware 链。其中,每个 middleware 都不需要关心链中它前后的 middleware 的任何信息。
参数
- ...middleware (arguments): 遵循 Redux middleware API 的函数。每个 middleware 接受 Store 的 dispatch 和 getState 函数作为命名参数,并返回一个函数。该函数会被传入被称为 next 的下一个 middleware 的 dispatch 方法,并返回一个接收 action 的新函数,这个函数可以直接调用 next(action),或者在其他需要的时刻调用,甚至根本不去调用它。调用链中最后一个 middleware 会接受真实的 store 的 dispatch 方法作为 next 参数,并借此结束调用链。所以,middleware 的函数签名是 ({ getState, dispatch }) => next => action。
返回值
(Function) 一个应用了 middleware 后的 store enhancer。这个 store enhancer 的签名是 createStore => createStore,但是最简单的使用方法就是直接作为最后一个 enhancer 参数传递给 createStore() 函数。
function applyMiddleware(
...middlewares: Middleware[]
): StoreEnhancer<any> {
return createStore =>
<S, A extends AnyAction>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => {
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)
}
// 给中间件传递 getState 和 dispatch ,这样中间件能方便拿到数据和进行 action
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 拿到最新的 dispatch 函数,新的 dispatch 执行才会执行中间件
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
到这里中间件也明白了,只不过看到这里还是不知道怎么自定义中间件,不用着急,我们写一个简单的中间件看看:
const createTestMiddleware = ({getState, dispatch}) => next => action => {
// 这里就是自己发挥的空间了
// 执行 next 传递 action
return next(action)
}
这里的 next
在上一个中间件返回的结果,这里总是像:
action => {}
这样的函数,所以只有执行了 next(action)
这样的函数才会传递,不然中间件就会中断。而我们定义的函数 action => {}
就是中间件的 dispatch
,全部中间件的 dispatch
完成了就会执行 redux
自己定义的 dispatch
函数了;所以上面的 applyMiddleware
函数中传入的就是 store.dispatch
。