手写:简易状态管理库深度解析
从零实现一个迷你状态管理库,覆盖 Redux 和 Vuex 的核心逻辑,深入理解发布订阅模式、中间件机制和响应式状态绑定的底层原理。
一句话概括
亲手构建一个迷你状态管理库是理解现代状态管理方案的最佳方式——通过实现 Redux 风格的 createStore、中间件链和 Vuex 风格的响应式状态绑定,你将深入掌握单向数据流、发布订阅模式和响应式系统的核心原理。
背景与意义
现代前端开发中,状态管理库已经成为了大型应用的标配。但很多开发者使用这些库多年,对其内部机制的理解仍然停留在「黑盒」层面。这种「会用但不懂」的状态会导致:
- 调试困难:遇到诡异的渲染问题时不知道是库的 bug 还是自己的问题
- 性能瓶颈:不理解 useSelector 的实现原理就不知道为什么会引起不必要的渲染
- 选型失误:不理解各种方案的本质差异就在项目中选择状态管理方案
手写简易状态管理库的意义不在于教大家「造轮子」,而在于「拆轮子」——通过实现最核心的逻辑,让底层原理变得清晰可见。
概念与定义
发布订阅模式(Pub/Sub):Store 本质上是发布订阅模式的一种实现。当状态变化时,Store 发布「change」事件,所有订阅者(组件)接收通知并更新。
Reducer:一个纯函数 (prevState, action) → newState。它接收当前状态和一个 action,返回新的状态。没有副作用,没有异步操作。
中间件(Middleware):装饰器模式的一种应用。在 dispatch action 到 reducer 之间插入一层处理逻辑,可以拦截、修改、延迟或记录 action。
响应式状态:状态被「包裹」后,读取操作的依赖被自动追踪,写入操作会通知所有依赖方。Vue 的 reactive() 和 MobX 的 observable() 都基于这个原理。
最小示例——手写 Redux 核心
20 行代码实现 Redux 最核心的 createStore:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function createStore(reducer, initialState) {
let state = initialState
const listeners = new Set()
return {
getState: () => state,
dispatch: (action) => {
state = reducer(state, action)
listeners.forEach((listener) => listener())
return action
},
subscribe: (listener) => {
listeners.add(listener)
return () => listeners.delete(listener)
},
}
}
// 使用
const counterReducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT': return state + 1
case 'DECREMENT': return state - 1
default: return state
}
}
const store = createStore(counterReducer, 0)
store.subscribe(() => console.log('State:', store.getState()))
store.dispatch({ type: 'INCREMENT' }) // State: 1
store.dispatch({ type: 'INCREMENT' }) // State: 2
核心知识点拆解
1. 迷你 Redux:完整的 createStore + 中间件
让我们从 20 行的最小版本扩展到完整的 Redux 风格状态管理库:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// mini-redux.js
class MiniRedux {
constructor(reducer, preloadedState, enhancer) {
// 如果有 enhancer(中间件),先包装
if (enhancer) {
return enhancer(this.createStore)(reducer, preloadedState)
}
this.reducer = reducer
this.state = preloadedState
this.listeners = new Set()
this.isDispatching = false
// 初始化状态
this.dispatch({ type: '@@INIT' })
}
getState() {
if (this.isDispatching) {
throw new Error('Cannot getState while dispatching')
}
return this.state
}
dispatch(action) {
// 验证 action 格式
if (typeof action !== 'object' || action === null) {
throw new Error('Actions must be plain objects')
}
if (typeof action.type === 'undefined') {
throw new Error('Actions must have a type property')
}
if (this.isDispatching) {
throw new Error('Cannot dispatch while dispatching')
}
try {
this.isDispatching = true
this.state = this.reducer(this.state, action)
} finally {
this.isDispatching = false
}
this.listeners.forEach((listener) => listener())
return action
}
subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('listener must be a function')
}
if (this.isDispatching) {
throw new Error('Cannot subscribe while dispatching')
}
let isSubscribed = true
this.listeners.add(listener)
// 返回取消订阅函数
return () => {
if (!isSubscribed) return
isSubscribed = false
this.listeners.delete(listener)
}
}
replaceReducer(nextReducer) {
this.reducer = nextReducer
this.dispatch({ type: '@@REPLACE' })
}
}
// 辅助函数:combineReducers
function combineReducers(reducers) {
return (state = {}, action) => {
const nextState = {}
let hasChanged = false
for (const key in reducers) {
if (reducers.hasOwnProperty(key)) {
const previousStateForKey = state[key]
const nextStateForKey = reducers[key](previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
throw new Error(`Reducer "${key}" returned undefined`)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
}
return hasChanged ? nextState : state
}
}
2. 中间件系统:从零构建
中间件是 Redux 最强大的特性之一。以下是从零实现中间件链:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// mini-redux-middleware.js
// applyMiddleware:将中间件链应用到 createStore
function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState) => {
const store = createStore(reducer, preloadedState)
let dispatch = () => {
throw new Error('Middleware chain not yet initialized')
}
// 给每个中间件传入 getState 和 dispatch
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action),
}
// 用 middlewareAPI 初始化所有中间件
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
// 组合中间件 -> 返回最终的 dispatch
dispatch = compose(...chain)(store.dispatch)
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)))
}
// 示例中间件 1:日志中间件
const loggerMiddleware = (store) => (next) => (action) => {
console.group(`Action: ${action.type}`)
console.log('Prev state:', store.getState())
console.log('Action:', action)
const result = next(action)
console.log('Next state:', store.getState())
console.groupEnd()
return result
}
// 示例中间件 2:Thunk 中间件(处理异步 action)
const thunkMiddleware = (store) => (next) => (action) => {
if (typeof action === 'function') {
return action(store.dispatch, store.getState)
}
return next(action)
}
// 示例中间件 3:性能监控中间件
const perfMiddleware = (store) => (next) => (action) => {
const start = performance.now()
const result = next(action)
const duration = performance.now() - start
if (duration > 16) { // 超过一帧(16ms)的 action
console.warn(`Slow action: ${action.type} took ${duration.toFixed(2)}ms`)
}
return result
}
// 使用组合
const store = MiniRedux(
rootReducer,
initialState,
applyMiddleware(thunkMiddleware, loggerMiddleware, perfMiddleware)
)
中间件的执行流程:
1
2
3
4
5
6
store.dispatch(action)
→ thunk(next1)(action) // 第一层中间件
→ logger(next2)(action) // 第二层中间件
→ perf(next3)(action) // 第三层中间件
→ originalDispatch // 原始 dispatch
→ reducer(state, action)
每一层中间件都可以选择:
- 拦截 action(比如 thunk 发现 action 是函数,就不继续传了)
- 修改 action(比如添加时间戳)
- 延迟 action(比如 debounce 中间件)
- 在 action 通过后执行额外逻辑(比如日志的「后处理」)
3. 响应式状态绑定(手写 Vuex 核心)
Vuex 的核心是将状态「响应式化」。以下是使用 Proxy 实现的简易响应式 store:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// mini-vuex.js - Proxy 版响应式 store
class ReactiveStore {
constructor(options = {}) {
const { state = {}, mutations = {}, actions = {}, getters = {} } = options
// 使用 Proxy 包装 state,实现响应式
this._state = this._makeReactive(state)
this._mutations = mutations
this._actions = actions
this._getters = {}
// 注册 getters
this._initGetters(getters)
// 订阅者列表
this._watchers = new Map()
}
// Proxy 实现响应式
_makeReactive(obj, path = []) {
const self = this
return new Proxy(obj, {
get(target, key, receiver) {
const value = Reflect.get(target, key, receiver)
// 自动依赖收集
if (self._activeWatcher) {
const fullPath = [...path, key].join('.')
if (!self._watchers.has(fullPath)) {
self._watchers.set(fullPath, new Set())
}
self._watchers.get(fullPath).add(self._activeWatcher)
}
// 嵌套对象递归响应式
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
return self._makeReactive(value, [...path, key])
}
return value
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (oldValue !== value) {
const fullPath = [...path, key].join('.')
// 通知所有相关 watcher
for (const [watcherPath, watchers] of self._watchers) {
if (fullPath.startsWith(watcherPath)) {
watchers.forEach((fn) => fn(value, oldValue))
}
}
}
return result
},
})
}
// 初始化 getters
_initGetters(getters) {
for (const [name, fn] of Object.entries(getters)) {
Object.defineProperty(this._getters, name, {
get: () => fn(this.state),
enumerable: true,
})
}
}
get state() {
return this._state
}
get getters() {
return this._getters
}
// commit mutation
commit(type, payload) {
const mutation = this._mutations[type]
if (!mutation) {
throw new Error(`Unknown mutation type: ${type}`)
}
mutation(this.state, payload)
}
// dispatch action
dispatch(type, payload) {
const action = this._actions[type]
if (!action) {
throw new Error(`Unknown action type: ${type}`)
}
return action(
{
state: this.state,
commit: this.commit.bind(this),
dispatch: this.dispatch.bind(this),
getters: this.getters,
},
payload
)
}
// 监听特定路径的变化
watch(path, callback) {
if (!this._watchers.has(path)) {
this._watchers.set(path, new Set())
}
this._watchers.get(path).add(callback)
return () => {
this._watchers.get(path)?.delete(callback)
}
}
}
// 使用示例
const store = new ReactiveStore({
state: {
count: 0,
user: {
name: 'Alice',
settings: {
theme: 'dark',
},
},
},
mutations: {
INCREMENT(state) {
state.count++
},
SET_THEME(state, theme) {
state.user.settings.theme = theme
},
},
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => commit('INCREMENT'), 1000)
},
},
})
// 自动依赖收集 + 响应式更新
store.watch('count', (newVal, oldVal) => {
console.log(`Count changed: ${oldVal} → ${newVal}`)
})
store.watch('user.settings.theme', (newVal) => {
console.log(`Theme changed to: ${newVal}`)
document.documentElement.setAttribute('data-theme', newVal)
})
store.commit('INCREMENT') // Count changed: 0 → 1
store.commit('SET_THEME', 'light') // Theme changed to: light
4. 与 React 的绑定:connect 和 useSelector
手写 React-Redux 的核心绑定逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// mini-react-redux.js
import { useContext, useState, useEffect, useRef, useSyncExternalStore, createContext } from 'react'
const ReduxContext = createContext(null)
// Provider 组件
function Provider({ store, children }) {
return (
<ReduxContext.Provider value={store}>
{children}
</ReduxContext.Provider>
)
}
// useSelector Hook(基于 useSyncExternalStore)
function useSelector(selector) {
const store = useContext(ReduxContext)
if (!store) throw new Error('useSelector must be used within a Provider')
return useSyncExternalStore(
// subscribe:订阅 store 变化
(callback) => store.subscribe(callback),
// getSnapshot:获取当前选中的状态
() => selector(store.getState()),
// getServerSnapshot:SSR 用(可选)
() => selector(store.getState())
)
}
// useDispatch Hook
function useDispatch() {
const store = useContext(ReduxContext)
return store.dispatch
}
// HOC connect(类组件兼容模式)
function connect(mapStateToProps, mapDispatchToProps) {
return (WrappedComponent) => {
return function ConnectedComponent(props) {
const store = useContext(ReduxContext)
const stateProps = mapStateToProps ? mapStateToProps(store.getState(), props) : {}
const dispatchProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, props)
: { dispatch: store.dispatch }
const state = useSyncExternalStore(
(cb) => store.subscribe(cb),
() => mapStateToProps?.(store.getState(), props) || {},
)
return <WrappedComponent {...props} {...stateProps} {...dispatchProps} />
}
}
}
export { Provider, useSelector, useDispatch, connect }
实战案例:完整的状态管理库
将上述所有组件组合成一个完整、可用的迷你状态管理库:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// complete-store.js - 完整状态管理库
class CompleteStore {
constructor(options = {}) {
this._state = {}
this._reducers = {}
this._listeners = new Set()
this._middlewares = []
this._effectCleanups = new Map()
this._init(options)
}
_init(options) {
// 初始化 reducer
if (typeof options.reducer === 'function') {
this._rootReducer = options.reducer
} else if (typeof options.reducers === 'object') {
this._rootReducer = this._combineReducers(options.reducers)
} else {
throw new Error('Must provide reducer or reducers')
}
// 初始化状态(通过 @@INIT action)
this._state = this._rootReducer(undefined, { type: '@@INIT' })
// 设置中间件
if (options.middlewares?.length) {
this._enhanceDispatch(options.middlewares)
}
// 初始化副作用(类似 Pinia 的 setup store 自动 effect)
if (options.effects) {
this._registerEffects(options.effects)
}
}
// combineReducers 实现
_combineReducers(reducers) {
return (state = {}, action) => {
const nextState = {}
let changed = false
for (const [key, reducer] of Object.entries(reducers)) {
const prev = state[key]
const next = reducer(prev, action)
nextState[key] = next
changed = changed || next !== prev
}
return changed ? nextState : state
}
}
// 中间件链增强
_enhanceDispatch(middlewares) {
const chain = middlewares.map((mw) => mw({
getState: () => this._state,
dispatch: (action) => this._dispatch(action),
}))
// 从右到左组合
const originalDispatch = this._dispatch.bind(this)
this.dispatch = chain.reduceRight(
(next, mw) => mw(next),
originalDispatch
)
}
// 内部 dispatch
_dispatch(action) {
if (typeof action === 'object') {
this._state = this._rootReducer(this._state, action)
this._listeners.forEach((fn) => fn(this._state))
}
return action
}
// 外部 dispatch
dispatch(action) {
return this._dispatch(action)
}
// 获取状态
getState() {
return this._state
}
// 订阅
subscribe(listener) {
this._listeners.add(listener)
return () => this._listeners.delete(listener)
}
// 注册副作用(类似 Pinia 的 setup return 自动运行)
_registerEffects(effects) {
for (const [key, effectFn] of Object.entries(effects)) {
if (typeof effectFn === 'function') {
const cleanup = effectFn(this)
if (typeof cleanup === 'function') {
this._effectCleanups.set(key, cleanup)
}
}
}
}
// 销毁(cleanup 所有副作用)
destroy() {
this._effectCleanups.forEach((cleanup) => cleanup())
this._effectCleanups.clear()
this._listeners.clear()
}
}
// ── 使用示例 ──
const store = new CompleteStore({
reducers: {
counter: (state = 0, action) => {
switch (action.type) {
case 'ADD': return state + action.payload
case 'SUBTRACT': return state - action.payload
default: return state
}
},
todos: (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, { id: Date.now(), text: action.payload, done: false }]
case 'TOGGLE_TODO':
return state.map((t) =>
t.id === action.payload ? { ...t, done: !t.done } : t
)
default: return state
}
},
},
middlewares: [
(store) => (next) => (action) => {
console.log('[Middleware] dispatching:', action.type)
return next(action)
},
],
effects: {
// 每次 todos 变化时打印统计
logTodoStats: (store) => {
let prevTodos = store.getState().todos
const unsubscribe = store.subscribe((state) => {
if (state.todos !== prevTodos) {
console.log(`Todos: ${state.todos.length} items, ${state.todos.filter((t) => t.done).length} done`)
prevTodos = state.todos
}
})
// 返回 cleanup 函数
return unsubscribe
},
},
})
// 测试
store.dispatch({ type: 'ADD_TODO', payload: '学习手写状态管理' })
store.dispatch({ type: 'ADD_TODO', payload: '实践 MiniRedux' })
store.dispatch({ type: 'TOGGLE_TODO', payload: 1 }) // 假设这是第一个 todo 的 id
store.dispatch({ type: 'ADD', payload: 10 })
console.log('Final state:', store.getState())
底层原理(含源码分析)
1. Redux dispatch 的「洋葱模型」
中间件的嵌套形成了经典的「洋葱模型」:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌─────────────────┐
│ dispatch(action) │
└────────┬────────┘
│
┌────────▼────────┐
│ Middleware 1 │
│ (thunk) │
└────────┬────────┘
│
┌────────▼────────┐
│ Middleware 2 │
│ (logger) │
└────────┬────────┘
│
┌────────▼────────┐
│ Middleware 3 │
│ (perf) │
└────────┬────────┘
│
┌────────▼────────┐
│ originalDispatch │
└────────┬────────┘
│
┌────────▼────────┐
│ reducer │
└─────────────────┘
这个模型的精妙之处在于:
- 可组合:每个中间件只关心自己的逻辑,通过
next(action)传递控制权 - 可拦截:中间件可以选择不调用
next()来拦截 action - 副作用后置:中间件可以在
next()之后执行副作用(如日志的「打印新状态」)
2. Proxy 与 defineProperty 的响应式实现对比
Vue 2 使用 Object.defineProperty,Vue 3 使用 Proxy。手写版可以对比两者的差异:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// defineProperty 方式(Vue 2 风格)
function defineReactive(obj, key, val) {
const dep = new Set()
let value = val
Object.defineProperty(obj, key, {
get() {
if (activeWatcher) dep.add(activeWatcher)
return value
},
set(newVal) {
if (newVal !== value) {
value = newVal
dep.forEach((w) => w())
}
},
})
}
// 局限:数组变动无法检测、新增属性无法检测、需要递归遍历所有键
// Proxy 方式(Vue 3 风格)
function proxyReactive(obj) {
return new Proxy(obj, {
get(target, key) {
if (activeWatcher) track(target, key)
const val = Reflect.get(target, key)
return typeof val === 'object' ? proxyReactive(val) : val
},
set(target, key, value) {
const old = Reflect.get(target, key)
const result = Reflect.set(target, key, value)
if (old !== value) trigger(target, key)
return result
},
})
}
// 优势:数组、新增属性、Map、Set 都能自动处理,懒递归(只在访问时递归)
3. Immer 的「看起来可变,实际上不可变」原理
Redux Toolkit 使用 Immer 来让开发者写「看起来可变」的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Immer 核心原理简化
function produce(baseState, producer) {
// 创建 Proxy,拦截所有写入操作
const draft = createDraft(baseState)
// 在 draft 上执行修改
producer(draft)
// 比较 draft 和 baseState,生成新的不可变对象
return finalize(draft, baseState)
}
function createDraft(base) {
const drafts = new Map()
function createProxy(obj) {
if (drafts.has(obj)) return drafts.get(obj)
const draft = new Proxy(obj, {
get(target, key) {
// 读取时返回原始值(保持引用)
const value = Reflect.get(target, key)
if (value !== null && typeof value === 'object') {
return createProxy(value)
}
return value
},
set(target, key, value) {
// 修改时标记为已修改
const result = Reflect.set(target, key, value)
markChanged(target)
return result
},
})
drafts.set(obj, draft)
return draft
}
return createProxy(base)
}
高频面试题解析
Q1: Redux 中 dispatch action 后,组件是如何知道自己需要重新渲染的?
考察点:React-Redux 的渲染触发机制。
答案核心:
流程如下:
1
2
3
4
5
6
7
dispatch(action)
→ reducer 计算新 state
→ store.subscribe(callback) 被触发(因为 state 变化了)
→ callback 内部调用 useSyncExternalStore 的 check 逻辑
→ useSelector 的选择器重新执行,获取新值
→ 新值与旧值比较(===)
→ 如果不同,React 安排该组件重新渲染
关键点是 useSyncExternalStore(React 18 引入)的 subscribe 机制。它自动处理了 store 订阅和 React 渲染调度之间的协调。在 React 18 之前,React-Redux 通过 forceUpdate 强制组件重新渲染,然后由 useSelector 的选择器决定是否要「跳过 render」。
Q2: Proxy 和 Object.defineProperty 在实现响应式时有什么核心差异?为什么 Vue 3 改用 Proxy?
考察点:对响应式实现技术细节的理解。
答案核心:
| 特性 | Object.defineProperty | Proxy |
|---|---|---|
| 监听方式 | 逐个定义属性 | 代理整个对象 |
| 新增属性 | ❌ 无法自动检测 | ✅ 自动检测 |
| 删除属性 | ❌ 无法自动检测 | ✅ 自动检测 |
| 数组索引 | ❌ 需要特殊处理 | ✅ 自然支持 |
| 数组 push/pop | ❌ 需要重写方法 | ✅ 自然支持 |
| Map/Set | ❌ 不支持 | ✅ 需要额外处理 |
| 性能 | 初始化慢(递归遍历) | 惰性(按需代理) |
| 兼容性 | IE9+ | 不支持 IE |
Vue 3 改用 Proxy 的主要原因排序:
- 消除限制:无需
Vue.set()、Vue.delete()、this.$set等 API - 数组支持:不需要重写数组方法,直接用
arr[0] = x就能触发更新 - 性能更好:惰性代理,只在访问时递归,而不是初始化时全部递归
- 支持更多数据类型:Map、Set、WeakMap 等
Q3: 手写一个「时间旅行」调试功能(Redux Devtools 核心原理)
考察点:对状态管理可追溯性的理解。
答案核心:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 时间旅行实现
function createTimeTravelStore(reducer, initialState) {
const states = [initialState] // 所有历史状态
const listeners = new Set()
let currentIndex = 0 // 当前所处位置
return {
getState: () => states[currentIndex],
getHistory: () => states,
dispatch: (action) => {
// 当前不是最新状态时,清除后面的历史
if (currentIndex < states.length - 1) {
states.splice(currentIndex + 1)
}
const newState = reducer(states[currentIndex], action)
states.push(newState)
currentIndex = states.length - 1
listeners.forEach((fn) => fn())
return action
},
// 回到过去
jumpToState: (index) => {
if (index >= 0 && index < states.length) {
currentIndex = index
listeners.forEach((fn) => fn())
}
},
subscribe: (listener) => {
listeners.add(listener)
return () => listeners.delete(listener)
},
}
}
总结与扩展
本文从零开始构建了一个完整的状态管理库,涵盖了 Redux 风格的 createStore、中间件系统、combineReducers,Vuex/Proxy 风格的响应式状态绑定,以及与 React 的绑定(useSelector、Provider、connect)。
核心要点回顾:
- 状态管理 = getState + dispatch + subscribe:三个核心方法构成了所有状态管理库的基础
- 中间件 = 高阶函数包裹:每个中间件接收
store => next => action =>模式 - 响应式 = Proxy + 依赖收集:访问时收集依赖,修改时通知更新
- React 集成 = useSyncExternalStore:React 18 提供的桥接机制
扩展思考:
编写状态管理库时,有几个「隐性的工程问题」会影响生产可用性:
- 内存泄漏:组件卸载时未取消 subscribe,回调仍然运行
- 死锁检测:在 dispatch 过程中再次 dispatch(无限循环)
- 微任务 vs 宏任务:通知监听器是同步还是异步?不同的选择影响渲染时序
- 选择器缓存:避免每次调用选择器都创建新对象(类似 reselect)
深入理解这些技术细节后,你不仅是一个「会用」状态管理库的开发者,更是一个「知道它为什么这么设计」的工程师。