状态管理演进史:从全局变量到响应式信号的技术变迁
状态管理是前端开发绕不开的话题。从最初的全局变量,到 Flux 架构的单向数据流,再到如今的细粒度响应式系统,这条演进之路折射出整个前端工程化的发展脉络。理解这段历史,才能在技术选型时做出明智判断。
史前时代:全局变量与 jQuery
最原始的状态管理
在框架出现之前,状态管理就是"把数据存在某个地方":
// 2010 年的代码
var userState = {
name: '',
isLoggedIn: false,
cart: []
}
// 任何地方都可以读写
function login(name) {
userState.name = name
userState.isLoggedIn = true
// 手动更新 DOM
$('#username').text(name)
$('#login-btn').hide()
$('#logout-btn').show()
}
function addToCart(item) {
userState.cart.push(item)
// 再次手动更新
$('#cart-count').text(userState.cart.length)
}
问题丛生
| 问题 | 表现 |
|---|---|
| 不可追踪 | 不知道谁在什么时候修改了状态 |
| 手动同步 | 状态变化后必须手动更新 DOM |
| 命名冲突 | 全局变量容易被覆盖 |
| 难以测试 | 状态散落各处,无法隔离测试 |
这个阶段的"状态管理"本质上是无管理——完全依赖开发者的自觉和记忆力。
MVC 的尝试:Backbone.js
2010 年,Backbone.js 引入了 MVC 模式的雏形:
// Backbone Model - 带事件的数据容器
var User = Backbone.Model.extend({
defaults: {
name: '',
isLoggedIn: false
}
})
var user = new User()
// View 监听 Model 变化
var UserView = Backbone.View.extend({
initialize: function() {
this.listenTo(this.model, 'change:name', this.render)
},
render: function() {
this.$el.html(this.model.get('name'))
return this
}
})
// 修改 Model,View 自动更新
user.set('name', '张三') // 触发 change 事件
进步:
- 状态变化有了事件通知
- 数据和视图开始分离
局限:
- 双向绑定导致数据流混乱
- 多个 View 监听同一 Model 时容易冲突
- 没有解决"状态从哪来、到哪去"的问题
Flux 革命:单向数据流
2014 年,Facebook 在 React 发布的同时提出了 Flux 架构,彻底改变了状态管理的思路。
问题的起源
Facebook 的通知系统存在一个著名的 Bug:消息数显示不一致。根本原因是双向数据流导致的状态不可预测。
Flux 的核心思想
┌─────────────────────────────────────────────────────────┐
│ │
│ Action ──► Dispatcher ──► Store ──► View │
│ ▲ │ │
│ └──────────────────────────────────┘ │
│ (用户交互触发新 Action) │
│ │
└─────────────────────────────────────────────────────────┘
// Flux 时代的代码
const AppDispatcher = new Flux.Dispatcher()
// Action - 描述发生了什么
const Actions = {
login(name) {
AppDispatcher.dispatch({
type: 'USER_LOGIN',
payload: { name }
})
}
}
// Store - 存储状态,响应 Action
const UserStore = {
_state: { name: '', isLoggedIn: false },
reduce(action) {
switch (action.type) {
case 'USER_LOGIN':
this._state = {
name: action.payload.name,
isLoggedIn: true
}
this.emit('change')
break
}
}
}
AppDispatcher.register(UserStore.reduce.bind(UserStore))
// View 监听 Store
UserStore.on('change', () => {
renderUI(UserStore.getState())
})
Flux 的贡献
- 单向数据流:状态只能通过 Action 修改
- 可追踪:所有变化都经过 Dispatcher
- 可预测:相同的 Action 序列产生相同的状态
Redux:把 Flux 推向极致
2015 年,Dan Abramov 推出 Redux,将 Flux 的理念精简和标准化。
三大原则
- 单一数据源:整个应用的状态存储在一棵对象树中
- 状态只读:唯一改变状态的方法是触发 Action
- 纯函数修改:Reducer 是纯函数,返回新状态
// Redux 的经典写法
// action types
const LOGIN = 'LOGIN'
const LOGOUT = 'LOGOUT'
// reducer - 纯函数
function userReducer(state = { name: '', isLoggedIn: false }, action) {
switch (action.type) {
case LOGIN:
return {
...state,
name: action.payload.name,
isLoggedIn: true
}
case LOGOUT:
return {
name: '',
isLoggedIn: false
}
default:
return state
}
}
// 创建 store
const store = createStore(userReducer)
// 订阅变化
store.subscribe(() => {
console.log('State changed:', store.getState())
})
// 派发 action
store.dispatch({ type: LOGIN, payload: { name: '张三' } })
Redux 的生态爆发
| 库 | 作用 |
|---|---|
react-redux | React 绑定 |
redux-thunk | 异步 Action |
redux-saga | 复杂异步流 |
redux-persist | 状态持久化 |
redux-devtools | 时间旅行调试 |
Redux 的代价
// 添加一个简单的计数器功能,需要写多少代码?
// 1. 定义 Action Type
const INCREMENT = 'counter/INCREMENT'
const DECREMENT = 'counter/DECREMENT'
// 2. 创建 Action Creator
const increment = () => ({ type: INCREMENT })
const decrement = () => ({ type: DECREMENT })
// 3. 编写 Reducer
const counterReducer = (state = 0, action) => {
switch (action.type) {
case INCREMENT: return state + 1
case DECREMENT: return state - 1
default: return state
}
}
// 4. 连接组件
const mapStateToProps = state => ({ count: state.counter })
const mapDispatchToProps = { increment, decrement }
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
// 一个简单功能,至少需要修改 4 个文件
这种"样板代码"问题催生了 Redux Toolkit:
// Redux Toolkit - 大幅简化
import { createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: {
increment: state => state + 1,
decrement: state => state - 1
}
})
export const { increment, decrement } = counterSlice.actions
export default counterSlice.reducer
MobX:另一条路——响应式编程
与 Redux 同期,MobX 走了一条完全不同的路:基于响应式的自动追踪。
import { makeAutoObservable, autorun } from 'mobx'
class UserStore {
name = ''
isLoggedIn = false
constructor() {
makeAutoObservable(this) // 自动追踪所有属性
}
login(name) {
this.name = name
this.isLoggedIn = true
}
logout() {
this.name = ''
this.isLoggedIn = false
}
}
const userStore = new UserStore()
// autorun 自动追踪依赖
autorun(() => {
console.log(`当前用户: ${userStore.name}`)
})
userStore.login('张三') // 控制台输出: 当前用户: 张三
Redux vs MobX
| 特性 | Redux | MobX |
|---|---|---|
| 哲学 | 函数式,不可变 | 面向对象,可变 |
| 样板代码 | 多(Toolkit 改善) | 少 |
| 学习曲线 | 陡峭 | 平缓 |
| 调试 | 时间旅行 | 依赖追踪图 |
| 性能优化 | 手动(selector) | 自动(细粒度追踪) |
组件化时代:状态下沉
随着 React Hooks 的推出,状态管理开始回归组件层面。
useState:最简单的状态
function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(c => c + 1)}>
{count}
</button>
)
}
useReducer:组件内的 Redux
function counterReducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 }
case 'decrement': return { count: state.count - 1 }
default: throw new Error()
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 })
return (
<>
<span>{state.count}</span>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
)
}
useContext + useReducer:轻量全局状态
const StateContext = createContext()
const DispatchContext = createContext()
function StateProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
)
}
// 任意组件使用
function SomeComponent() {
const state = useContext(StateContext)
const dispatch = useContext(DispatchContext)
// ...
}
现代方案:Zustand、Jotai、Valtio
2020 年后,新一代状态管理库百花齐放,各有侧重。
Zustand:极简主义
import { create } from 'zustand'
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
function Counter() {
const { count, increment } = useStore()
return <button onClick={increment}>{count}</button>
}
特点:
- 无 Provider 包裹
- 自动处理选择器优化
- 中间件支持(persist、devtools)
Jotai:原子化状态
import { atom, useAtom } from 'jotai'
// 原子 - 最小状态单元
const countAtom = atom(0)
const doubledAtom = atom((get) => get(countAtom) * 2) // 派生原子
function Counter() {
const [count, setCount] = useAtom(countAtom)
const doubled = useAtomValue(doubledAtom)
return (
<div>
<span>{count} x 2 = {doubled}</span>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
)
}
特点:
- 按需订阅,天然性能优化
- 无全局 store,更灵活
- 适合细粒度状态
Valtio:Proxy 魔法
import { proxy, useSnapshot } from 'valtio'
const state = proxy({
count: 0,
user: { name: '' }
})
function Counter() {
const snap = useSnapshot(state)
return (
<button onClick={() => { state.count++ }}>
{snap.count}
</button>
)
}
// 直接修改,自动追踪
state.count = 10
state.user.name = '张三'
特点:
- 最接近原生对象操作
- 基于 Proxy 的细粒度追踪
- 学习成本最低
最新趋势:Signals 信号
2023 年,Signals 成为热门话题。Vue、Preact、Solid 都采用了这种模式。
什么是 Signals
Signal 是一种细粒度响应式原语,当值变化时,只有真正依赖它的部分会更新。
// Preact Signals
import { signal, computed, effect } from '@preact/signals'
const count = signal(0)
const doubled = computed(() => count.value * 2)
effect(() => {
console.log(`Count: ${count.value}, Doubled: ${doubled.value}`)
})
count.value++ // 触发 effect
Signals vs useState
// useState - 组件级重渲染
function Counter() {
const [count, setCount] = useState(0)
console.log('Counter render') // 每次点击都打印
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(c => c + 1)}>+</button>
<ExpensiveComponent /> {/* 也会重渲染! */}
</div>
)
}
// Signal - 精确更新
const count = signal(0)
function Counter() {
console.log('Counter render') // 只打印一次!
return (
<div>
<span>{count}</span> {/* 只有这里更新 */}
<button onClick={() => count.value++}>+</button>
<ExpensiveComponent /> {/* 不受影响 */}
</div>
)
}
Vue 的响应式系统
Vue 3 的 Composition API 本质就是 Signals:
import { ref, computed, watchEffect } from 'vue'
const count = ref(0)
const doubled = computed(() => count.value * 2)
watchEffect(() => {
console.log(`Count: ${count.value}`)
})
count.value++ // 精确触发依赖
演进规律总结
技术演进脉络
全局变量 → MVC → Flux/Redux → Hooks → 原子化/Signals
│ │ │ │ │
无管理 事件驱动 单向数据流 组件内聚 细粒度响应
核心问题的解决进度
| 问题 | jQuery | Flux | Redux | MobX | Signals |
|---|---|---|---|---|---|
| 可追踪 | ❌ | ✅ | ✅ | ✅ | ✅ |
| 可预测 | ❌ | ✅ | ✅ | ⚠️ | ✅ |
| 性能优化 | 手动 | 手动 | 手动 | 自动 | 自动 |
| 开发体验 | 差 | 中 | 中 | 好 | 好 |
| 学习成本 | 低 | 中 | 高 | 中 | 低 |
如何选择
| 场景 | 推荐方案 |
|---|---|
| 简单应用 | useState + useContext |
| 中型应用 | Zustand / Jotai |
| 复杂应用(需调试) | Redux Toolkit |
| Vue 应用 | Pinia(内置 Signals) |
| 性能敏感场景 | Signals / Valtio |
结语
状态管理的演进史,本质是对复杂性的不断驯服。从"随便写"到"有规矩",从"全量更新"到"精确更新",技术在螺旋上升。
不必执着于某个特定方案——理解演进逻辑比掌握特定工具更重要。当你理解了单向数据流的价值、细粒度追踪的原理,就能在任何新工具面前快速上手。
下一个十年,状态管理还会继续演进。但万变不离其宗:让数据流动可预测、让更新精确可控。


