前端框架 精选推荐

状态管理演进史:从全局变量到响应式信号的技术变迁

HTMLPAGE 团队
22 分钟阅读

系统回顾前端状态管理的发展历程,从 jQuery 时代的全局变量到现代的 Signals 信号机制,深入分析每个阶段的技术特点和演进逻辑。

#状态管理 #Redux #MobX #Signals #前端架构

状态管理演进史:从全局变量到响应式信号的技术变迁

状态管理是前端开发绕不开的话题。从最初的全局变量,到 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 的贡献

  1. 单向数据流:状态只能通过 Action 修改
  2. 可追踪:所有变化都经过 Dispatcher
  3. 可预测:相同的 Action 序列产生相同的状态

Redux:把 Flux 推向极致

2015 年,Dan Abramov 推出 Redux,将 Flux 的理念精简和标准化。

三大原则

  1. 单一数据源:整个应用的状态存储在一棵对象树中
  2. 状态只读:唯一改变状态的方法是触发 Action
  3. 纯函数修改: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-reduxReact 绑定
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

特性ReduxMobX
哲学函数式,不可变面向对象,可变
样板代码多(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
   │          │        │           │          │
 无管理    事件驱动  单向数据流  组件内聚  细粒度响应

核心问题的解决进度

问题jQueryFluxReduxMobXSignals
可追踪
可预测⚠️
性能优化手动手动手动自动自动
开发体验
学习成本

如何选择

场景推荐方案
简单应用useState + useContext
中型应用Zustand / Jotai
复杂应用(需调试)Redux Toolkit
Vue 应用Pinia(内置 Signals)
性能敏感场景Signals / Valtio

结语

状态管理的演进史,本质是对复杂性的不断驯服。从"随便写"到"有规矩",从"全量更新"到"精确更新",技术在螺旋上升。

不必执着于某个特定方案——理解演进逻辑比掌握特定工具更重要。当你理解了单向数据流的价值、细粒度追踪的原理,就能在任何新工具面前快速上手。

下一个十年,状态管理还会继续演进。但万变不离其宗:让数据流动可预测、让更新精确可控

延伸阅读