Redux
以下源码参考自 Redux v5.0,为方便阅读去除了其中的 ts 代码
Redux 比较关键的几个点是:
- 单向数据流
- 单例模式,确保单个应用只有一个 store 实例
- 发布订阅模式
- 中间件、compose
下面从部分关键点的源码出发进一步理解 Redux 的实现,整体执行流程如下:
createStore
创建一个 store 实例,接收三个参数
- reducer:纯函数,接收上一个 state 和 action,根据不同 action 返回新的 state
- preloadedState:设置 store 的默认值
- enhancer:扩展 store 的功能
// 部分源码
createStore(reducer, preloadedState, enhancer) {
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(
`Expected the enhancer to be a function. Instead, received: '${kindOf(
enhancer
)}'`
)
}
return enhancer(createStore)(
reducer,
preloadedState
)
}
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = [] // 存储更新回调
let nextListeners = currentListeners // 下次 dispatch 将触发的更新回调
let isDispatching = false // 防止 dispatch action 冲突
function getState() {
// ...
return currentState
}
function subscribe(listener) {
// ...
}
function dispatch(action) {
// ...
}
return {
dispatch,
subscribe,
getState,
}
}
getState
获取当前的 state
function getState() {
// ...
return currentState;
}
subscribe
可以存储一些更新回调函数,在 reducer
返回新状态的时候统一执行更新
function subscribe(listener) {
let isSubscribed = true
ensureCanMutateNextListeners()
// 将更新函数推入到 listeners 数组,完成订阅
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(...)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
// 取消订阅
nextListeners.splice(index, 1)
}
}
dispatch
通过派发一个 action 去通知相应的 reducer 更新 state,且只能通过这种方式修改,确保数据单向流动
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(...)
}
if (typeof action.type === 'undefined') {
throw new Error(...)
}
if (isDispatching) {
throw new Error(...)
}
try {
// 上锁,如果已经在dispatch中,则不允许再度发起 dispatch
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
}
combineReducers
合并多个 reducer,生成一个 rootReducer,通过它来统一处理 state
function combineReducers(reducers) {
...
let hasChanged = false
// 存放最终的所有的state
const nextState = {}
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)
// 以各自的reducerName为键,新生成的state作为值,生成最终的state object,
nextState[key] = nextStateForKey
// 判断所有的state变化没变化
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 变化了,返回新的state,否则,返回旧的state
return hasChanged ? nextState : state
}
中间件 middleware
一般 redux 工作流程是:dispatch 一个 action,rootReducer 找到指定 reducer,然后 reducer 根据 action 来执行相应的更新函数,更新 store。而 middleware 会转换 dispatch 函数,并用新的 dispatch 处理我们的 action,随后再交付 rootReducer 处理.
之前 createStore 函数里面有个 enhancer,其实就是在这里面执行中间件,最终返回一个改造后的 store
// createStore
...
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(
reducer,
preloadedState
)
}
...
applyMiddleware
applyMiddleware 的作用主要是接收中间件,改造 diapatch,返回一个 enhancer
function applyMiddleware(...middlewares) {
return (createStore) =>
(...args) => {
const store = createStore(...args);
// 传递到中间件的参数
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
};
const chain = middlewares.map((middleware) => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
// compose的作用类似于下面这样:
// compose(a,b,c) ==> (...args) => a(b(c(...args) ) )
return {
...store,
dispatch,
};
};
}
// compose
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))
);
}
react-thunk
react-thunk 是 redux 中用于处理异步的中间件,本质上是改写了 dispatch 函数,使其可以接受一个函数作为参数。以 thunk 为例子,看下 redux 的中间件是怎么使用的。
先看下用法
import { createStore,applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(rootReducer, applyMiddleware(thunk))
// applyMiddleware(thunk)就是一个 enhancer
function callSbInOneSecond(name) {
return (dispatch, getState) => {
setTimeout({
dispatch({
type:'CALL_SOMEBODY',
payload: {
name
}
})
}, 1000)
}
}
store.dispatch(callSbInOneSecond('JacksonZhou'))
先掌握用法,再看源码,会更好理解 ~
function createThunkMiddleware(extraArgument) {
return function (_ref) {
// ①
var dispatch = _ref.dispatch,
getState = _ref.getState;
return function (next) {
// ②
return function (action) {
if (typeof action === "function") {
// 允许接收函数类型的action
return action(dispatch, getState, extraArgument);
}
return next(action);
};
};
};
}
var thunk = createThunkMiddleware;
结合上面的 createThunkMiddleware 和 applyMiddleware 再来走一遍 thunk
...
var thunk = createThunkMiddleware
createStore(rootReducer, applyMiddleware(thunk)) // applyMiddleware 接收中间件
...
return applyMiddleware(thunk)(createStore)( // 返回一个 enhancer
reducer,
preloadedState
)
...
// applyMiddleware(thunk)
const chain = middlewares.map(middleware => ①middleware(middlewareAPI))
dispatch = ②compose(...chain)(store.dispatch)
// next 其实就是 store.dispatch
return {
...store,
dispatch
}
// 此时已经返回一个修改了dispatch的store,new action都会走这 new dispatch