Redux是一个很好的状态管理库,提出了单向数据流、中间件等概念。Redux三大核心概念是:state(状态)、action以及reducer(根据旧 state 和 action 来计算新 state),并且限制只能使用纯函数来对状态进行修改。
Redux设计原则
单一数据源
整个应用的 全局 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
状态不可变
state 是只读的,只能通过触发 action 来改变状态。
使用纯函数
Reducer 是纯函数。
什么是纯函数?
如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。
该函数不会产生任何可观察的副作用。
createStore
redux 通过createStore
方法来创建 store,它很好地遵循了上述的redux的设计原则,即单一数据源、状态不可变,并且可以通过store enhancer和中间件来提高可扩展性
export function createStore<
S, // state
A extends Action, // action
Ext extends {} = {}, // 增强器
StateExt extends {} = {}, // 状态扩展
PreloadedState = S // 预加载状态
>(
reducer: Reducer<S, A, PreloadedState>,
preloadedState?: PreloadedState | StoreEnhancer<Ext, StateExt> | undefined,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S, A, UnknownIfNonSpecific<StateExt>> & Ext {
... // 参数检查
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
// 如果提供了增强器,就用它来包装 createStore 函数,并且传入 reducer 和 preloadedState
return enhancer(createStore)(
reducer,
preloadedState as PreloadedState | undefined
)
}
// 初始化当前的 reducer 和 currentState
let currentReducer = reducer
let currentState: S | PreloadedState | undefined = preloadedState as
| PreloadedState
| undefined
let currentListeners: Map<number, ListenerCallback> | null = new Map()
let nextListeners = currentListeners
let listenerIdCounter = 0
let isDispatching = false
// 使用 Map 来管理监听器,每个 listener 对应唯一的 id
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = new Map()
currentListeners.forEach((listener, key) => {
nextListeners.set(key, listener)
})
}
}
// 返回当前状态树
function getState(): S {
return currentState as S
}
// 添加一个监听器,在每次 dispatch 时被调用,通过 getState 获取当前状态
function subscribe(listener: () => void) {
let isSubscribed = true
ensureCanMutateNextListeners()
const listenerId = listenerIdCounter++
nextListeners.set(listenerId, listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
nextListeners.delete(listenerId)
currentListeners = null
}
}
// 调用 reducer 并更新状态,并通知所有监听器
function dispatch(action: A) {
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
listeners.forEach(listener => {
listener()
})
return action
}
// 替换当前的 reducer,在动态加载 reducer 和热重载时很有用
function replaceReducer(nextReducer: Reducer<S, A>): void {
currentReducer = nextReducer as unknown as Reducer<S, A, PreloadedState>
dispatch({ type: ActionTypes.REPLACE } as A)
}
// 提供与 observable/reactive 库的互操作性
// 返回一个最小的 observable,用于监听状态变化
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer: unknown) {
function observeState() {
const observerAsObserver = observer as Observer<S>
if (observerAsObserver.next) {
observerAsObserver.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
// 创建 store 时,自动 dispatch 一个 INIT 动作,确保所有 reducer 返回初始状态。
dispatch({ type: ActionTypes.INIT } as A)
// 返回 store 对象,向外暴露 dispatch, subscribe, getState, replaceReducer 等 API
const store = {
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
} as unknown as Store<S, A, StateExt> & Ext
return store
}
Enhancer
在 Redux 中,store enhancer 是一种高级功能,它允许你在创建 store 时对 store 的行为进行修改。它本质上就是在createStore
上额外包裹了一层,可以对store上暴露的dispatch
、getState
、subscribe
进行重写。
下面是一个基本的例子:
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
function loggerEnhancer(createStore) {
return function (reducer, preloadedState, enhancer) {
const store = createStore(reducer, preloadedState, enhancer);
const dispatch = store.dispatch;
store.dispatch = function (action) {
console.log('Dispatching action:', action);
return dispatch(action);
};
return store;
};
}
const store = createStore(
rootReducer,
undefined,
applyMiddleware(thunk),
loggerEnhancer
);
export default store;
Middleware
Redux 推荐使用中间件来扩展功能,比如通过包装dispatch
方法来实现需求。并且middleware是可组合的。多个 middleware 可以被组合到一起使用,形成 middleware 链。
export default function applyMiddleware(
...middlewares: Middleware[]
): StoreEnhancer<any> {
return createStore => (reducer, 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 = compose<typeof dispatch>(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
这段代码的重点是compose
函数,它通过数组 reduce 方法,将传入的多函数参数,进行函数柯里化处理,然后逐个执行函数。
export default function compose(...funcs: Function[]) {
if (funcs.length === 0) {
return <T>(arg: T) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce(
(a, b) =>
(...args: any) =>
a(b(...args))
)
}
Redux Toolkit
Redux Toolkit(简称RTK)是官方推荐的Redux写法。@reduxjs/toolkit
包封装了redux
包的核心能力,简化了redux的使用。
Redux Toolkit有两个关键的API,configureStore
和createSlice
。
configureStore
configureStore
接收 reducer、中间件、增强器、预加载状态和 devtools 作为参数,进行一系列处理,最终调用createStore
方法创建一个配置完善的 store。
export function configureStore<
S = any, // state的类型
A extends Action = UnknownAction, // action 的类型
M extends Tuple<Middlewares<S>> = Tuple<[ThunkMiddlewareFor<S>]>, // 中间件元组的类型
E extends Tuple<Enhancers> = Tuple<
[StoreEnhancer<{ dispatch: ExtractDispatchExtensions<M> }>, StoreEnhancer]
>, // 增强器元组的类型
P = S, // 预加载状态的类型
>(options: ConfigureStoreOptions<S, A, M, E, P>): EnhancedStore<S, A, E> {
// 构建默认的中间件列表,通常包括 redux-thunk 中间件。可以通过 middleware 参数进行自定义。
const getDefaultMiddleware = buildGetDefaultMiddleware<S>()
const {
reducer = undefined,
middleware,
devTools = true,
preloadedState = undefined,
enhancers = undefined,
} = options || {}
let rootReducer: Reducer<S, A, P>
// 处理 reducer
if (typeof reducer === 'function') {
rootReducer = reducer // 如果是函数,直接使用
} else if (isPlainObject(reducer)) {
// 如果是对象,就创建根 reducer
rootReducer = combineReducers(reducer) as unknown as Reducer<S, A, P>
} else {
throw new Error(
'`reducer` is a required argument, and must be a function or an object of functions that can be passed to combineReducers',
)
}
if (!IS_PRODUCTION && middleware && typeof middleware !== 'function') {
throw new Error('`middleware` field must be a callback')
}
// 中间件处理
let finalMiddleware: Tuple<Middlewares<S>>
if (typeof middleware === 'function') {
// 如果是函数,调用这个函数并且传入默认中间件列表
finalMiddleware = middleware(getDefaultMiddleware)
// 检查中间件列表是否有效
if (!IS_PRODUCTION && !Array.isArray(finalMiddleware)) {
throw new Error(
'when using a middleware builder function, an array of middleware must be returned',
)
}
} else {
// 不是函数,使用默认中间件列表
finalMiddleware = getDefaultMiddleware()
}
let finalCompose = compose
// 集成 devtools
if (devTools) {
finalCompose = composeWithDevTools({
trace: !IS_PRODUCTION,
...(typeof devTools === 'object' && devTools),
})
}
// 增强器处理
const middlewareEnhancer = applyMiddleware(...finalMiddleware)
const getDefaultEnhancers = buildGetDefaultEnhancers<M>(middlewareEnhancer)
let storeEnhancers =
typeof enhancers === 'function'
? enhancers(getDefaultEnhancers) // 调用增强器函数并传入默认增强器列表
: getDefaultEnhancers() // 不是函数就用默认增强器列表
const composedEnhancer: StoreEnhancer<any> = finalCompose(...storeEnhancers)
// 创建 store,传入根reducer、预加载状态和组合后的增强器
return createStore(rootReducer, preloadedState as P, composedEnhancer)
}
createSlice
让你使用 Immer 来编写 reducer,可以使用 "mutating" JS 语法,比如 state.value = 123
,不需要使用拓展运算符。内部基于你的 reducer 名称生成 action type 字符串。并且它能很好地兼容 TS。
function createSlice<
State, // 状态
CaseReducers extends SliceCaseReducers<State>, // reducer
Name extends string, // name
Selectors extends SliceSelectors<State>, // slice selector
ReducerPath extends string = Name, // reducer 路径
>(
options: CreateSliceOptions<
State,
CaseReducers,
Name,
ReducerPath,
Selectors
>,
): Slice<State, CaseReducers, Name, ReducerPath, Selectors> {
const { name, reducerPath = name as unknown as ReducerPath } = options
// reducers 处理
// 如果是函数,就调用这个函数并传入 buildReducerCreators
// 否则直接使用 reducers 对象
const reducers =
(typeof options.reducers === 'function'
? options.reducers(buildReducerCreators<State>())
: options.reducers) || {}
const reducerNames = Object.keys(reducers)
// 创建 context,后面为每个 reducer 创建一个类型和名称,存储在 context 中
const context: ReducerHandlingContext<State> = {
sliceCaseReducersByName: {},
sliceCaseReducersByType: {},
actionCreators: {},
sliceMatchers: [],
}
// 上下文方法
const contextMethods: ReducerHandlingContextMethods<State> = {
// 添加 case reducers
addCase(
typeOrActionCreator: string | TypedActionCreator<any>,
reducer: CaseReducer<State>,
) {
const type =
typeof typeOrActionCreator === 'string'
? typeOrActionCreator
: typeOrActionCreator.type
context.sliceCaseReducersByType[type] = reducer
return contextMethods
},
// 添加匹配器
addMatcher(matcher, reducer) {
context.sliceMatchers.push({ matcher, reducer })
return contextMethods
},
exposeAction(name, actionCreator) {
context.actionCreators[name] = actionCreator
return contextMethods
},
exposeCaseReducer(name, reducer) {
context.sliceCaseReducersByName[name] = reducer
return contextMethods
},
}
// 处理 reducer 定义
reducerNames.forEach((reducerName) => {
const reducerDefinition = reducers[reducerName]
const reducerDetails: ReducerDetails = {
reducerName,
type: getType(name, reducerName),
createNotation: typeof options.reducers === 'function',
}
// 区分异步 thunk 和普通 reducer 的定义
if (isAsyncThunkSliceReducerDefinition<State>(reducerDefinition)) {
handleThunkCaseReducerDefinition(
reducerDetails,
reducerDefinition,
contextMethods,
cAT,
)
} else {
handleNormalReducerDefinition<State>(
reducerDetails,
reducerDefinition,
contextMethods,
)
}
})
// 构建最终的 reducer
function buildReducer() {
const [
extraReducers = {},
actionMatchers = [],
defaultCaseReducer = undefined,
] =
typeof options.extraReducers === 'function'
? executeReducerBuilderCallback(options.extraReducers)
: [options.extraReducers]
// 结合额外的 reducers、action matchers和默认的case reducer
const finalCaseReducers = {
...extraReducers,
...context.sliceCaseReducersByType,
}
// 调用 createReducer 来创建新的 reducer
return createReducer(options.initialState, (builder) => {
for (let key in finalCaseReducers) {
builder.addCase(key, finalCaseReducers[key] as CaseReducer<any>)
}
for (let sM of context.sliceMatchers) {
builder.addMatcher(sM.matcher, sM.reducer)
}
for (let m of actionMatchers) {
builder.addMatcher(m.matcher, m.reducer)
}
if (defaultCaseReducer) {
builder.addDefaultCase(defaultCaseReducer)
}
})
}
const selectSelf = (state: State) => state
// 用于缓存注入的 selectors
const injectedSelectorCache = new Map<
boolean,
WeakMap<
(rootState: any) => State | undefined,
Record<string, (rootState: any) => any>
>
>()
let _reducer: ReducerWithInitialState<State>
// 获取当前的 reducer
function reducer(state: State | undefined, action: UnknownAction) {
if (!_reducer) _reducer = buildReducer()
return _reducer(state, action)
}
// 获取初始状态
function getInitialState() {
if (!_reducer) _reducer = buildReducer()
return _reducer.getInitialState()
}
// 构建 selectors
function makeSelectorProps<CurrentReducerPath extends string = ReducerPath>(
reducerPath: CurrentReducerPath,
injected = false,
): Pick<
Slice<State, CaseReducers, Name, CurrentReducerPath, Selectors>,
'getSelectors' | 'selectors' | 'selectSlice' | 'reducerPath'
> {
function selectSlice(state: { [K in CurrentReducerPath]: State }) {
...
}
function getSelectors(
selectState: (rootState: any) => State = selectSelf,
) {
...
}
return {
reducerPath,
getSelectors,
get selectors() {
return getSelectors(selectSlice)
},
selectSlice,
}
}
// 返回 slice 对象
const slice: Slice<State, CaseReducers, Name, ReducerPath, Selectors> = {
name,
reducer,
actions: context.actionCreators as any,
caseReducers: context.sliceCaseReducersByName as any,
getInitialState,
...makeSelectorProps(reducerPath),
// 注入功能,允许将 slice 注入到其他 slice 中,以便共享状态和 actions
injectInto(injectable, { reducerPath: pathOpt, ...config } = {}) {
const newReducerPath = pathOpt ?? reducerPath
injectable.inject({ reducerPath: newReducerPath, reducer }, config)
return {
...slice,
...makeSelectorProps(newReducerPath, true),
} as any
},
}
return slice
}