redux 原理探秘:从 redux 到 react-redux 和 redux-toolkit
在上一篇文章中介绍了 redux 和核心概念、核心原则和相关技术栈架构,下面这篇文章我们开始深入研究 redux 和相关工具 react-redux、redux-toolkit 的实现原理
redux 实现原理
在介绍 redux 核心方法之前,先整体回顾一下 redux 的核心方法
- createStore 是创建 Store 的入口,也是实现发布-订阅模式的核心
- 通过 getState 方法能够获取到当前的状态
- 通过 subscribe 方法注册订阅回调函数,通过 dispatch 执行 Reducer 更新状态并触发订阅者函数执行,实现发布-订阅模式
- 通过 Observable 方法将 Store 转化为一个可观察对象
createStore 方法
createStore 方法核心实现步骤如下
- 定义了 getState、subscribe、dispatch、observable 方法,用于实现获取 Store 的状态和发布-订阅模式
- 通过 dispatch 方法初始化所有状态
- 最后返回包含 getState、subscribe、dispatch、observable 方法的 store 对象
1export function createStore(
2 reducer: Reducer<S, A, PreloadedState>,
3 preloadedState?: PreloadedState | StoreEnhancer<Ext, StateExt> | undefined,
4 enhancer?: StoreEnhancer<Ext, StateExt>
5): Store<S, A, StateExt> & Ext {
6 // 定义状态变量
7 let currentReducer = reducer
8 let currentState: S | PreloadedState | undefined = preloadedState as
9 | PreloadedState
10 | undefined
11
12 // 获取当前状态快照
13 function getState() {}
14
15 // 注册回调函数
16 function subscribe(listener: () => void) {}
17
18 // 派发 action,触发状态更新
19 function dispatch(action: A) {}
20
21 // 实现了 Observable 协议的函数,使得 Store 可以被观察
22 function observable() {}
23
24 // 创建 store 后初始化所有状态
25 dispatch({ type: ActionTypes.INIT } as A)
26
27 const store = {
28 dispatch: dispatch as Dispatch<A>,
29 subscribe,
30 getState,
31 [$$observable]: observable,
32 } as unknown as Store<S, A, StateExt> & Ext
33 return store
34}
getState 方法
getState 方法比较简单,就是返回了当前的状态 currentState
1export function createStore() {
2 function getState(): S {
3 return currentState as S
4 }
5}
subscribe 方法
subscribe 方法用于订阅者注册回调函数,有几个关键点
- 回调函数被存储在一个 Map 对象中,但是在方法中却定义了两个 Map 对象: currentListeners 和 nextListeners,这是为了防止在 dispatch 过程中对订阅者列表进行修改引起的潜在问题
- 在添加或删除订阅方法前,都会通过 ensureCanMutateNextListeners 方法创建一份 nextListeners 副本,添加和删除操作都是在 nextListeners 副本上进行
- 当需要通知订阅者时(即执行 dispatch 方法),通过将 nextListeners 复制到 currentListeners,再遍历 currentListeners 触发所有订阅者执行
- 在 subscribe 方法执行时,为每个订阅者分配了一个唯一的 id(即 listenerId),在取消订阅的 unsubscribe 方法中,通过闭包能够访问到定义的 listenerId,从而实现移除监听器的效果
1export function createStore() {
2 // 定义状态变量
3 let currentReducer = reducer
4 let currentState: S | PreloadedState | undefined = preloadedState as
5 | PreloadedState
6 | undefined
7 let currentListeners: Map<number, ListenerCallback> | null = new Map()
8 let nextListeners = currentListeners
9
10 function subscribe(listener: () => void) {
11 // 标记为已订阅状态
12 let isSubscribed = true
13
14 // 避免直接修改 currentListeners
15 ensureCanMutateNextListeners()
16 // 为每个订阅者分配一个唯一的 id
17 const listenerId = listenerIdCounter++
18 // 将监听器添加到 nextListeners
19 nextListeners.set(listenerId, listener)
20
21 // 返回一个取消订阅函数
22 // 实现逻辑就是通过 id 从 nextListeners 移除监听器
23 return function unsubscribe() {
24 if (!isSubscribed) return
25
26 isSubscribed = false
27
28 ensureCanMutateNextListeners()
29 nextListeners.delete(listenerId)
30 currentListeners = null
31 }
32 }
33
34 // 保护 currentListeners 数组不被意外修改
35 function ensureCanMutateNextListeners() {
36 // 创建一份 nextListeners 副本,避免直接修改 currentListeners
37 if (nextListeners === currentListeners) {
38 nextListeners = new Map()
39 currentListeners.forEach((listener, key) => {
40 nextListeners.set(key, listener)
41 })
42 }
43 }
44
45 return store
46}
dispatch 方法
dispatch 方法用于执行 Reducer 更新状态并通知所有订阅者执行,具体实现有两个关键点
- 在执行 reducer 更新状态前,会通过 isDispatching 设置为 true 的方式加锁,确保不会执行其他 action,reducer 更新状态结束后,再关闭锁
- 在遍历通知订阅者前,将 nextListeners 复制到 currentListeners 再遍历执行,避免对于 nextListeners 的操作造成订阅者执行错误
1export function createStore() {
2 // 定义状态变量
3 let currentReducer = reducer
4 let currentState: S | PreloadedState | undefined = preloadedState as
5 | PreloadedState
6 | undefined
7 let currentListeners: Map<number, ListenerCallback> | null = new Map()
8 let nextListeners = currentListeners
9 let isDispatching = false
10
11 function dispatch(action: A) {
12 // 确保不会执行其他 action
13 if (isDispatching) {
14 throw new Error('Reducers may not dispatch actions.')
15 }
16
17 try
18 // isDispatching 设置为 true,相当于加锁
19 isDispatching = true
20 // 执行 reducer 函数更新状态
21 currentState = currentReducer(currentState, action)
22 } finally {
23 // 结束 dispatch 执行关闭锁
24 isDispatching = false
25 }
26
27 // 修改状态后,触发所有订阅者执行
28 // 注意这里将 nextListeners 复制到 currentListeners 再遍历执行,
29 // 避免对于 nextListeners 的操作造成订阅者执行错误
30 const listeners = (currentListeners = nextListeners)
31 listeners.forEach((listener) => {
32 listener()
33 })
34
35 // 返回 action,符合 dispatch 的标准行为
36 return action
37 }
38
39 return store
40}
observable 方法
observable 方法将 Store 转换为一个符合 Observable 提案的可观察对象,可以应用在到响应式编程中或者与 RxJS 集成
可以这样理解可观察对象:Store 就是一个黑盒,内部的状态如何变化是无法知道的,而转变为可观察对象(observable)之后,就像带上了一副透视眼镜,每当状态变化时,都能够通过透视眼镜(observable 的 next 方法),获取到最新的状态
observable 方法实现步骤如下
- 定义 observeState 方法观察 observer 对象状态的变化,通过 observer 的 next 属性传入当前的状态
- 立即执行一次 observeState 方法确保当前状态被观察,然后调用 subscribe 方法注册当前观察者,这样当后续的状态变化时,都能够通过发布-订阅模式获取到最新的状态
1export function createStore() {
2 function observable() {
3 // 引用 store 的 subscribe 方法
4 const outerSubscribe = subscribe
5
6 return {
7 // 实现 subscribe 方法,接收一个 observer 对象
8 subscribe(observer) {
9 // 定义一个函数来观察状态变化
10 function observeState() {
11 // 确保 observer 有 next 方法
12 if (observer.next) {
13 // 调用 next 方法并传入当前状态
14 observer.next(getState())
15 }
16 }
17
18 // 立即执行一次以确保当前状态被观察
19 observeState()
20 // 调用外部的 subscribe 方法注册观察者
21 const unsubscribe = outerSubscribe(observeState)
22 // 返回一个取消订阅的方法
23 return { unsubscribe }
24 },
25
26 // 返回 observable 对象本身,符合 Observable 协议
27 [$$observable]() {
28 return this
29 },
30 }
31 }
32}
react-redux 实现原理
react-redux 是 react 组件获取和操作 redux 状态的桥梁,提供了三类 api 用来操作状态
- Provider 组件:包裹在应用最外层,使用 react 的 context 来传递 Store
- connect:一个高阶函数,用于将 Store 和 Action 映射到 react 组件的 props
- hooks:用于在 react 组件中使用 Store 和 Action,简化 connect 的操作
因为现在更推荐使用 hooks 操作 Store,所以接下来我们分析 react-redux 中更为常用 Provider 组件和 hooks 的实现原理
Provider 组件
首先先看 Provider 组件的实现原理,实现步骤如下
- 接收 Store 和可选的 context 作为参数
- 定义包含 store 和订阅逻辑的 contextValue
- 使用 react 的 useIsomorphicLayoutEffect hook 监听 store 状态变化,进行订阅和通知
- 通过 Context.Provider 将 contextValue 传递给子组件,保证 react 的子组件能够访问 Store
1import { useMemo } from 'react'
2
3function Provider<A extends Action<string> = UnknownAction, S = unknown>({
4 store,
5 context,
6 children,
7}: ProviderProps<A, S>) {
8 // Redux store 和订阅逻辑
9 const contextValue = useMemo(() => {
10 // 创建一个订阅对象,它负责监听 store 的变化
11 const subscription = createSubscription(store)
12 return {
13 store,
14 subscription,
15 }
16 }, [store])
17
18 // 获取当前的 store 状态
19 const previousState = useMemo(() => store.getState(), [store])
20
21 // 客户端渲染优先使用 useLayoutEffect,否则使用 useEffect
22 useIsomorphicLayoutEffect(() => {
23 const { subscription } = contextValue
24 // 设置订阅对象的状态变化时的回调函数,尝试订阅 store 的变化
25 subscription.onStateChange = subscription.notifyNestedSubs
26 subscription.trySubscribe()
27
28 // 如果上次渲染后 store 状态发生了变化,通知所有子订阅者
29 if (previousState !== store.getState()) {
30 subscription.notifyNestedSubs()
31 }
32
33 // 组件卸载时的清理逻辑,取消订阅并清理回调函数
34 return () => {
35 subscription.tryUnsubscribe()
36 subscription.onStateChange = undefined
37 }
38 }, [contextValue, previousState])
39
40 // 优先使用自定义的 context,否则使用默认的 ReactReduxContext
41 const Context = context || ReactReduxContext
42
43 // 使用 Context.Provider 包裹子组件,并传递 contextValue
44 // 允许子组件通过 context 访问到 Redux store 和订阅逻辑
45 return <Context.Provider value={contextValue}>{children}</Context.Provider>
46}
hooks 简化操作
使用 react-redux 提供的 hooks 在 react 组件中直接操作 Store,相比于使用 connect 更加简单,下面就分析两个最常用的的 hooks:useSelector 和 useDispatch
useSelector 用于在 react 组件中,通过回调函数获取某一部分自己需要的 Store,核心实现原理如下
- 从 react 组件的上下文 Context 中获取 Store
- 通过传入的回调函数 selector 获取需要的 Store
1import { useCallback, Context } from 'react'
2
3export function createSelectorHook(context = ReactReduxContext): UseSelector {
4 const useReduxContext = useDefaultReduxContext
5
6 return function useSelector<TState, Selected extends unknown>(
7 selector: (state: TState) => Selected
8 ): Selected {
9 // 从 Context 中获取 Store
10 const { store } = useReduxContext()
11
12 // 使用 useCallback hooks 包裹提高性能
13 const wrappedSelector = useCallback<typeof selector>(
14 {
15 [selector.name](state: TState) {
16 // 通过传入的回调函数 selector 获取 Store
17 const selected = selector(state)
18
19 return selected
20 },
21 }[selector.name],
22 [selector]
23 )
24
25 // 返回选择的状态
26 return selectedState
27 }
28}
29
30export const useSelector = createSelectorHook()
useDispatch 用于在 react 组件中获取 dispatch 方法,实现原理也很简单,直接返回了 Store 的 dispatch 方法
1export function createDispatchHook(context = ReactReduxContext) {
2 const useStore =
3 context === ReactReduxContext ? useDefaultStore : createStoreHook(context)
4
5 return function useDispatch() {
6 // 本质就是返回了 Store 的 dispatch 方法
7 const store = useStore()
8 return store.dispatch
9 }
10}
11
12export const useDispatch = createDispatchHook()
redux-toolkit
redux-toolkit 提供了一系列简化 redux 配置和操作的方法,下面我们分析两个最常用的方法
- configureStore:简化了 createStore 方法的配置
- createSlice:将初始状态 Store、Action、Reducer 统一放在一个切片(Slice)集中管理
configureStore 方法
configureStore 方法最终会调用 redux 的 createStore 方法创建 Store,但是简化了如下部分的配置
- 中间件配置:包含了一些常用的默认中间件(如:redux-trunk),并且不用手动配置 applyMiddleware 就可以添加中间件
- DevTools 配置:自动集成 Redux DevTools,并且支持自定义配置,并且生产环境时可以自动禁用 DevTools
- Reducer 组合:可以接受一个 Reducer 对象,将多个 Reducer 组合起来
- 增强器配置,简化了增强器的操作,无需手动组合
1export function configureStore<
2 S = any,
3 A extends Action = UnknownAction,
4 M extends Tuple<Middlewares<S>> = Tuple<[ThunkMiddlewareFor<S>]>,
5 E extends Tuple<Enhancers> = Tuple<
6 [StoreEnhancer<{ dispatch: ExtractDispatchExtensions<M> }>, StoreEnhancer]
7 >,
8 P = S,
9>(options: ConfigureStoreOptions<S, A, M, E, P>): EnhancedStore<S, A, E> {
10 // 构建获取默认中间件的函数
11 const getDefaultMiddleware = buildGetDefaultMiddleware<S>()
12
13 const {
14 reducer = undefined,
15 middleware,
16 devTools = true,
17 preloadedState = undefined,
18 enhancers = undefined,
19 } = options || {}
20
21 let rootReducer: Reducer<S, A, P>
22
23 // 根据 reducer 类型创建 rootReducer
24 if (typeof reducer === 'function') {
25 rootReducer = reducer
26 } else if (isPlainObject(reducer)) {
27 rootReducer = combineReducers(reducer) as unknown as Reducer<S, A, P>
28 }
29
30 // 定义最终的中间件数组
31 let finalMiddleware: Tuple<Middlewares<S>>
32 if (typeof middleware === 'function') {
33 finalMiddleware = middleware(getDefaultMiddleware)
34 } else {
35 finalMiddleware = getDefaultMiddleware()
36 }
37
38 // 配置 compose 函数,用于增强器的组合
39 let finalCompose = compose
40
41 // devTool 相关配置
42 if (devTools) {
43 finalCompose = composeWithDevTools({
44 // Enable capture of stack traces for dispatched Redux actions
45 trace: !IS_PRODUCTION,
46 ...(typeof devTools === 'object' && devTools),
47 })
48 }
49
50 // 创建中间件增强器
51 const middlewareEnhancer = applyMiddleware(...finalMiddleware)
52
53 // 构建获取默认增强器的函数
54 const getDefaultEnhancers = buildGetDefaultEnhancers<M>(middlewareEnhancer)
55 // 获取最终的增强器数组
56 let storeEnhancers =
57 typeof enhancers === 'function'
58 ? enhancers(getDefaultEnhancers)
59 : getDefaultEnhancers()
60
61 const composedEnhancer: StoreEnhancer<any> = finalCompose(...storeEnhancers)
62
63 // 最终调用 redux 的 createStore 创建 Store
64 return createStore(rootReducer, preloadedState as P, composedEnhancer)
65}
createSlice 方法
createSlice 方法目标是创建一个 slice 切片统一管理 Store、Action、Reducer 并简化配置,具体实现步骤如下
- 定义 slice 名称和 reducer 路径
- 配置 reducers,支持函数或对象
- 创建 context 管理 reducers 和 action creators
- 遍历并处理 reducers,生成 action types 和 reducers
- 构建最终的 reducer 和 slice 对象,包括 reducer、actions、caseReducers 和其他实用方法
1export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) {
2 return function createSlice(options) {
3 const { name, reducerPath = name } = options
4
5 // 将 reducer 配置为函数或对象形式
6 const reducers =
7 (typeof options.reducers === 'function'
8 ? options.reducers(buildReducerCreators<State>())
9 : options.reducers) || {}
10
11 const reducerNames = Object.keys(reducers)
12
13 // 创建 context 管理 reducers 和 action creators
14 const context: ReducerHandlingContext<State> = {
15 sliceCaseReducersByName: {},
16 sliceCaseReducersByType: {},
17 actionCreators: {},
18 sliceMatchers: [],
19 }
20
21 // 定义处理 reducers 的方法
22 const contextMethods: ReducerHandlingContextMethods<State> = {
23 // 添加一个 case reducer 和对应的 action 类型
24 addCase(typeOrActionCreator, reducer) {},
25 // 添加一个 matche 和对应的 reducer,处理不基于特定 action 类型的通用逻辑
26 addMatcher(matcher, reducer) {},
27 // 将一个 action creator 暴露在 slice 的 actions 对象中
28 exposeAction(name, actionCreator) {},
29 // 将一个 case reducer 暴露在 slice 的 caseReducers 对象中
30 exposeCaseReducer(name, reducer) {},
31 }
32
33 // 遍历 reducers 并应用
34 reducerNames.forEach((reducerName) => {
35 const reducerDefinition = reducers[reducerName]
36 const reducerDetails: ReducerDetails = {
37 reducerName,
38 type: getType(name, reducerName),
39 createNotation: typeof options.reducers === 'function',
40 }
41 handleNormalReducerDefinition<State>(
42 reducerDetails,
43 reducerDefinition,
44 contextMethods
45 )
46 })
47
48 // 构建最终的 reducer 函数
49 function buildReducer() {
50 const [
51 extraReducers = {},
52 actionMatchers = [],
53 defaultCaseReducer = undefined,
54 ] =
55 typeof options.extraReducers === 'function'
56 ? executeReducerBuilderCallback(options.extraReducers)
57 : [options.extraReducers]
58
59 const finalCaseReducers = {
60 ...extraReducers,
61 ...context.sliceCaseReducersByType,
62 }
63
64 return createReducer(options.initialState, (builder) => {
65 for (let key in finalCaseReducers) {
66 builder.addCase(key, finalCaseReducers[key] as CaseReducer<any>)
67 }
68 for (let sM of context.sliceMatchers) {
69 builder.addMatcher(sM.matcher, sM.reducer)
70 }
71 for (let m of actionMatchers) {
72 builder.addMatcher(m.matcher, m.reducer)
73 }
74 if (defaultCaseReducer) {
75 builder.addDefaultCase(defaultCaseReducer)
76 }
77 })
78 }
79
80 // 定义和返回 slice 对象
81 let _reducer: ReducerWithInitialState<State>
82
83 const slice = {
84 // slice 的唯一标识名
85 name,
86 // 在 State 树中的路径,通常与 slice 名称相同
87 reducerPath,
88 // 定义如何根据接收到的 action 和当前 state,并计算新 state 的函数
89 reducer(state, action) {},
90 // 自动生成的 action creators,与 slice 中的 reducers 相关联
91 actions: context.actionCreators,
92 // case reducers 集合,每个 case reducer 对应处理一种 action
93 caseReducers: context.sliceCaseReducersByName,
94 // 获取 slice 的初始状态函数
95 getInitialState() {},
96 // 创建和缓存对应 slice 状态的选择器函数
97 getSelectors(selectState: (rootState: any) => State = selectSelf) {
98 const selectorCache = emplace(injectedSelectorCache, this, {
99 insert: () => new WeakMap(),
100 })
101
102 return emplace(selectorCache, selectState, {
103 insert: () => {
104 const map: Record<string, Selector<any, any>> = {}
105 for (const [name, selector] of Object.entries(
106 options.selectors ?? {}
107 )) {
108 map[name] = wrapSelector(
109 this,
110 selector,
111 selectState,
112 this !== slice
113 )
114 }
115 return map
116 },
117 }) as any
118 },
119 // 从全局状态中提取出当前 slice 的状态
120 selectSlice(state) {},
121 // 获取包含 slice 的所有选择器
122 get selectors() {
123 return this.getSelectors(this.selectSlice)
124 },
125 // 将 reducer 动态注入到 store 中(通常用在代码拆分或动态加载 reducer 场景)
126 injectInto(injectable, { reducerPath: pathOpt, ...config } = {}) {},
127 }
128
129 return slice
130 }
131}
132
133export const createSlice = buildCreateSlice()