在 React 开发中,状态管理是一个绕不开的话题。随着应用规模的增长,组件之间的数据传递和状态同步会变得愈发复杂。如何选择一款合适的状态管理方案,直接影响着开发体验和代码可维护性。本文将带你梳理当前主流的状态管理方案,并重点推荐一套简洁高效的组合:Hox + ahooks。
为什么需要状态管理
随着应用规模扩大,我们需要在不同组件之间共享数据。想象一下:用户在某个页面修改了头像,这个变化需要实时反映在导航栏、侧边栏等多个位置。如果没有统一的状态管理,就只能通过层层传递 props,代码冗余且难以维护。
状态管理有三个核心目标:统一管理 ------所有共享数据存储在可预测的位置;响应式更新 ------状态变化时依赖它的组件自动重渲染;可预测性------状态变化可控、可追踪。
React 中需要管理的状态分为三类:本地状态 ------组件内部 useState 管理,通常不跨组件共享;服务端状态 ------从 API 获取的数据,需要缓存、轮询等能力;全局状态------多组件共享的状态,如用户信息、主题、购物车等。
主流方案概览
React 生态中的状态管理方案百花齐放,各有各的设计哲学和适用场景。为了帮助大家建立一个整体认知,我们先来看一下主流方案的对比。
| 方案 | 学习成本 | 包体积 | 社区活跃度 | 适用场景 |
|---|---|---|---|---|
| Redux Toolkit | 中高 | 约12KB | 非常高 | 大型企业级项目 |
| Zustand | 低 | 约1KB | 高 | 中小型项目 |
| Hox | 低 | 约2KB | 低 | 追求极简体验 |
| useState + Context | 低 | 0 | 内置 | 简单场景 |
从 npm 下载量来看,Redux 仍然占据主导地位,但 Zustand 的增长速度非常惊人,在 2024 年的 State of React 调研中,Zustand 的满意度和使用率都位居前列。值得注意的是,传统的 useState 配合 Context 仍然是很多开发者的首选,这说明在很多场景下,我们其实不需要引入额外的状态管理库。
Redux Toolkit
Redux 是 React 状态管理领域的鼻祖,核心理念是单向数据流和不可变状态。但原始 Redux 过度设计:繁琐的 action types、冗长的 reducers、重复的模板代码,让很多开发者望而却步。
Redux Toolkit 是官方推荐的新一代工具集,通过简化的 API 大大降低了门槛。核心包括:createSlice ------在一个文件中定义 state、reducer 和 action;createAsyncThunk ------处理异步逻辑;configureStore------简化 store 创建。
javascript
import { createSlice, configureStore } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1 },
decrement: (state) => { state.value -= 1 },
},
})
const store = configureStore({ reducer: counterSlice.reducer })
export const { increment, decrement } = counterSlice.actions
组件中使用 useSelector 和 useDispatch:
javascript
import { useSelector, useDispatch } from 'react-redux'
import { increment, decrement } from './store'
function Counter() {
const count = useSelector((state) => state.counter.value)
const dispatch = useDispatch()
return (
<div>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
)
}
Redux Toolkit 适合对状态管理有严格要求的大型项目,需要时间旅行调试、复杂中间件等高级特性。但如果项目规模不大,引入 Redux 可能带来不必要的复杂度。
Zustand + tanstack-query
Zustand 是一个轻量级的状态管理库,其名字在德语中意为"状态"。Zustand 的设计理念是极简主义,它没有 Redux 那么多约束性的概念,只需要几行代码就能创建一个可全局共享的状态。Zustand 使用 Hooks API 来创建和消费状态,这使得它与 React 的开发模式完美契合。
Zustand 的核心优势在于它的简洁性。创建一个 store 只需要调用 create 函数,传入一个返回状态和方法的函数即可。与 Redux 不同的是,Zustand 不需要 Provider 包裹,组件可以直接通过 Hook 来消费状态。这种设计大大减少了组件树的复杂度和不必要的重新渲染。
javascript
import { create } from 'zustand'
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
function Counter() {
const { count, increment, decrement } = useCounterStore()
return (
<div>
<span>{count}</span>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
)
}
而 tanstack-query(原 React Query)则是专门用于管理服务端状态的神器。它解决了数据获取、缓存、同步、轮询等一系列常见的后端数据管理需求。tanstack-query 的核心理念是将服务端数据视为一种特殊的"状态",它应该独立于 UI 状态来管理。通过内置的缓存机制、后台刷新、乐观更新等功能,tanstack-query 大大简化了前后端数据交互的复杂度。
Zustand + tanstack-query 的组合在 2024 年备受推崇,这种组合兼顾了全局状态管理和服务端数据管理的需求,且两者都保持了极简的 API 设计。对于中型项目来说,这是一个性价比极高的选择。
其他方案
mobx、valtio、jotai,略。
为什么推荐 Hox + ahooks
在众多状态管理方案中,我想特别推荐 Hox + ahooks 这个组合。Hox 是一个专注于状态共享的轻量级库,而 ahooks 则是阿里巴巴开源的高质量 React Hooks 库。两者结合,能够提供一种极其简洁、直观的状态管理体验。
Hox 的核心理念是"状态即模型,模型即 Hook"。这意味着你可以用编写普通 React Hook 的方式来编写状态模型,不需要学习任何新的概念。在 Hox 中,创建一个全局状态与创建一个本地状态几乎没有区别,这大大降低了状态管理的复杂度。当你需要将一个组件的本地状态改为全局共享状态时,只需要将 useState 替换成 Hox 提供的 createGlobalStore 函数即可。
这种设计的优势是显而易见的。首先,它几乎不需要额外的学习成本,熟悉 React Hooks 的开发者可以立即上手;其次,它支持 TypeScript 类型自动推断,无需手动声明复杂类型;最后,它的 API 设计与 React 思维高度一致,不会产生心智负担。
ahooks 则是 React Hooks 工具库的佼佼者,它提供了大量实用的 Hook,覆盖了状态管理、DOM 操作、网络请求、传感器等众多场景。ahooks 的特点是高质量、可靠性强,由阿里巴巴前端团队维护,已在大量生产项目中得到验证。特别值得一提的是,ahooks 完美支持 SSR,这对于需要支持服务端渲染的项目来说是重要优势。
Hox + ahooks 的组合完美互补:Hox 解决全局状态共享问题,ahooks 则提供了丰富的工具 Hook 来处理各类复杂场景。从本质上讲,ahooks 解决的是"怎么做"的问题,而 Hox 解决的是"在哪存"的问题,两者配合使用,能够覆盖绝大多数前端状态管理需求。
Hox 核心用法
了解了 Hox 的设计理念,接下来我们深入探讨它的具体用法。Hox 的 API 设计非常简洁,只有两个核心函数:createGlobalStore 用于创建状态模型,useModel 用于在组件中消费状态。
安装
bash
npm install hox
# 或
yarn add hox
# 或
pnpm add hox
创建状态模型
使用 Hox 创建一个全局状态模型非常简单,只需要调用 createGlobalStore 函数并传入一个返回状态和方法的函数即可。这个函数的写法与普通的自定义 Hook 完全一致,你可以在其中使用 useState、useReducer、useEffect 等任何 React Hooks。
javascript
// models/useCounter.js
import { createGlobalStore } from 'hox'
import { useState } from 'react'
export default createGlobalStore(function useCounter() {
const [count, setCount] = useState(0)
const increment = () => setCount(c => c + 1)
const decrement = () => setCount(c => c - 1)
const reset = () => setCount(0)
return {
count,
increment,
decrement,
reset
}
})
这段代码看起来与普通的自定义 Hook 几乎一模一样,唯一的区别是使用了 createGlobalStore 函数进行包裹。createGlobalStore 函数会确保这个 Hook 返回的状态和方法能够在多个组件之间共享。
统一挂载全局状态
hox 的 createGlobalStore 生成的状态需要以组件的形式挂载的 react 树上。用 HoxRoot 包起来即可:
typescript
import { HoxRoot } from 'hox'
ReactDOM.render(
<HoxRoot>
<App />
</HoxRoot>,
domContainer
)
在组件中使用
在组件中使用 Hox 创建的状态同样简单,只需要导入对应的模型并调用即可:
javascript
import useCounter from '../models/useCounter'
function Counter() {
const { count, increment, decrement, reset } = useCounter()
return (
<div>
<h2>计数: {count}</h2>
<button onClick={increment}>增加</button>
<button onClick={decrement}>减少</button>
<button onClick={reset}>重置</button>
</div>
)
}
可以看到,使用方式与普通 Hook 完全相同。Hox 会自动处理状态的共享和响应式更新,你不需要关心 Provider 的配置,也不需要担心状态泄漏到其他不相关的组件。
优化订阅
hox 的优化订阅是通过返回一个数组做浅比较,hook 本身还是全部返回的。
typescript
const { count } = useCounter(s => [s.count])
TypeScript 支持
Hox 对 TypeScript 提供了开箱即用的支持。当你使用 TypeScript 编写模型时,类型推断会自动完成,无需额外的类型声明:
typescript
// models/useCounter.ts
import { createGlobalStore } from 'hox'
import { useState } from 'react'
interface CounterState {
count: number
increment: () => void
decrement: () => void
reset: () => void
}
export default createGlobalStore<CounterState>(function useCounter() {
const [count, setCount] = useState(0)
const increment = () => setCount(c => c + 1)
const decrement = () => setCount(c => c - 1)
const reset = () => setCount(0)
return {
count,
increment,
decrement,
reset
}
})
不过,由于 Hox 采用了独特的类型推断机制,即使你不在 createGlobalStore 中显式声明类型,VS Code 等编辑器通常也能自动推断出正确的类型。
配合 ahooks 使用(可以是其他任意的 hook 库)
能够无痛复用各种 hooks 并将之于 store 整合在一起就是我认为 hox + ahooks 比 zustand + tanstack-query 要好的理由。 zustand 就没有这种类似的自由组合自定义 hook 的能力,相信我,在 zustand 里面实现请求的节流、防抖、轮询等逻辑就是噩梦,把请求放在 tanstack-query 这种不伦不类的用法真看不懂。
ahooks 是阿里巴巴开源的高质量 React Hooks 库,它提供了丰富的工具 Hook,能够与 Hox 形成完美的互补。ahooks 的特点是大而全、文档详细、质量可靠,已被大量国内外企业采用。
Hox 的一个重要优势是:你可以在全局状态模型中自由使用任何 React Hooks,包括 ahooks。这意味着你可以把 ahooks 的能力直接封装进全局状态,让状态管理模型更加强大。
useRequest:在 Hox 模型中管理网络请求
useRequest 是 ahooks 最核心的 Hook 之一,专门用于管理网络请求的状态。将 useRequest 融入 Hox 模型,可以轻松实现数据获取、轮询、缓存、防抖等功能:
javascript
// models/useUser.js
import { createGlobalStore } from 'hox'
import { useRequest } from 'ahooks'
export default createGlobalStore(function useUser() {
const { data, loading, error, run, refresh, mutate } = useRequest(
(userId) => fetch(`/api/users/${userId}`).then(res => res.json()),
{
manual: false,
defaultParams: [1],
}
)
const updateUser = async (userId, updates) => {
const originalData = data.value
mutate((currentData) => ({
...currentData,
...updates
}))
try {
await fetch(`/api/users/${userId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
})
refresh()
} catch (err) {
mutate(() => originalData)
}
}
return {
user: data,
loading,
error,
refresh,
updateUser,
}
})
这样一来,使用该状态的组件只需要调用 useUser() 即可自动获得请求状态,无需在每个组件中重复编写请求逻辑。
useRequest 的配置项非常丰富:pollingInterval 可以设置轮询间隔,实现定时刷新;debounceInterval 可以将请求防抖处理,避免频繁请求;refreshOnWindowFocus 可以在窗口重新获得焦点时自动刷新数据;cacheKey 和 cacheTime 则提供了数据缓存能力。
useDebounce:在 Hox 模型中处理防抖
对于需要防抖或节流的场景,可以在 Hox 模型中直接使用 ahooks:
javascript
// models/useSearch.js
import { createGlobalStore } from 'hox'
import { useState, useEffect } from 'react'
import { useDebounce } from 'ahooks'
export default createGlobalStore(function useSearch() {
const [keyword, setKeyword] = useState('')
const [results, setResults] = useState([])
const debouncedKeyword = useDebounce(keyword, { wait: 500 })
useEffect(() => {
if (!debouncedKeyword) {
setResults([])
return
}
fetch(`/api/search?q=${debouncedKeyword}`)
.then(res => res.json())
.then(data => setResults(data))
}, [debouncedKeyword])
return {
keyword,
setKeyword,
results,
}
})
useLocalStorageState:在 Hox 模型中持久化状态
如果需要将状态持久化到 localStorage,useLocalStorageState 提供了优雅的解决方案:
javascript
// models/useSettings.js
import { createGlobalStore } from 'hox'
import { useLocalStorageState } from 'ahooks'
export default createGlobalStore(function useSettings() {
const [theme, setTheme] = useLocalStorageState('app-theme', 'light')
const [language, setLanguage] = useLocalStorageState('app-language', 'zh-CN')
const toggleTheme = () => {
setTheme(t => t === 'light' ? 'dark' : 'light')
}
return {
theme,
language,
setTheme,
setLanguage,
toggleTheme,
}
})
这个 Hook 会自动处理序列化、反序列化,以及跨标签页同步等细节。组件中使用时:
javascript
import useSettings from '../models/useSettings'
function ThemeToggle() {
const { theme, toggleTheme } = useSettings()
return (
<button onClick={toggleTheme}>
当前主题: {theme}
</button>
)
}
这些 Hook 大多数都是独立的,可以直接与 Hox 或其他状态管理方案配合使用。ahooks 的设计理念是"即插即用",你不需要为了使用某个 Hook 而引入整个库,可以按需导入。
适用场景
任何技术方案都有其适用范围,Hox + ahooks 也不例外。理解这些方案的适用场景,能够帮助我们做出更明智的技术决策。
Hox + ahooks 最适合以下场景:首先是中小型项目,这类项目通常不需要复杂的状态架构,但仍然需要状态共享能力;其次是追求开发效率的团队,Hox 的学习曲线几乎为零,开发者可以立即投入生产;第三是对代码简洁性有要求的项目,Hox + ahooks 的组合代码量极小,可读性好;第四是需要快速迭代的项目,由于 Hox 的零心智负担特性,重构和调整都变得轻而易举;最后是个人项目或初创项目,这类场景通常追求快速上线而非长期可维护性。
对于大型企业级项目,Redux Toolkit 仍然是更稳妥的选择。Redux 的严格约束在大型团队中能够发挥优势:统一的状态结构使得代码审查更容易,强大的调试工具能够快速定位问题,完善的中间件生态能够满足各类扩展需求。虽然 Redux 的学习曲线较陡,但一旦团队掌握,往往能够保持较高的一致性。
总结
状态管理是 React 开发中的核心议题,选择合适的方案对项目成功至关重要。本文详细介绍了当前主流的状态管理方案:Redux Toolkit 适合大型项目和对规范性有高要求的团队;Zustand + tanstack-query 则是中型项目的热门选择,兼顾了简洁性和功能性;而 Hox + ahooks 的组合,以其极简的设计理念和零学习成本,成为中小型项目的理想选择。
Hox 的核心优势可以概括为三点:首先是简单,它使用与普通 Hook 完全一致的 API,不需要额外的概念;其次是直观,状态管理逻辑与组件逻辑写在同样的位置,代码可读性极高;第三是灵活,它既支持简单的全局状态,也能处理复杂的异步逻辑和副作用。
在实际项目中,我建议采用渐进式的技术选型策略:从小处着手,先使用本地状态和 Context 来解决简单需求;当发现状态开始变得难以管理时,再引入 Hox 来抽象全局状态;遇到复杂的网络请求场景时,补充 ahooks 的 useRequest。这种方式能够避免过早引入复杂性,让项目保持轻盈的同时具备扩展能力。
最后,技术的选择永远应该服务于业务需求。没有最好的方案,只有最适合的方案。希望本文能够帮助你更好地理解 React 状态管理的生态,并在实际项目中做出明智的技术决策。