文章

React状态管理方案对比深度解析

全面对比 Redux、Zustand、Jotai 三大 React 状态管理方案,从设计理念到源码分析,帮助你构建清晰的状态管理选型思维。

React状态管理方案对比深度解析

一句话概括

React 状态管理正在从 Redux 的集中式 store 向 Zustand 的简洁 API 和 Jotai 的原子化方向演进,理解这三种方案的设计哲学和适用场景,是构建可维护 React 应用的关键能力。

背景与意义

React 的状态管理之争是一个持续了近十年的话题。从最初的 setState 到 Redux 的统一管理,再到 Context API 的回归,再到 Zustand 和 Jotai 等新一代方案的崛起,React 状态管理经历了「从混乱到有序再到多元化」的过程。

React 本身是一个 UI 库,它没有像 Vue 那样内置响应式系统。React 的状态更新机制是通过「不可变更新 + 重新渲染」来驱动的——每次状态变化都会触发整个组件的重新渲染(除非使用 React.memo)。这意味着选择合适的状态管理方案不仅是方便的 API 问题,更直接影响应用的渲染性能。

概念与定义

单向数据流:数据在应用中单向流动——View 触发 Action,Action 更新 State,State 驱动 View 更新。React 本身是单向数据流的,状态管理方案是对这种模式的延伸。

集中式 Store:所有的应用状态存储在一个单一的 Store 中(如 Redux)。优点是状态变化可追踪、可调试;缺点是 store 可能变得臃肿。

原子化状态:将状态拆分为最小单位(Atom),组件按需订阅自己关心的原子。当某原子变化时,只重新渲染订阅了该原子的组件。

不可变更新:每次状态更新都创建一个新的状态对象,而不是修改原对象。Redux 和 Zustand 都要求不可变更新。

最小示例

Redux Toolkit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// store.js - Redux Toolkit
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 },
  },
})

export const { increment, decrement } = counterSlice.actions
export const store = configureStore({ reducer: counterSlice.reducer })

Zustand

1
2
3
4
5
6
7
8
// store.js - Zustand
import { create } from 'zustand'

export const useCounterStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}))

Jotai

1
2
3
4
5
// atoms.js - Jotai
import { atom, useAtom } from 'jotai'

export const countAtom = atom(0)
// 使用:const [count, setCount] = useAtom(countAtom)

核心知识点拆解

1. Redux Toolkit:工业级标准方案

Redux Toolkit(RTK)是 Redux 的官方推荐写法,它解决了 Redux 原始写法的样板代码问题。

createSlice 与 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Redux Toolkit 核心:createSlice
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'

// 异步 Action
export const fetchUsers = createAsyncThunk(
  'users/fetchAll',
  async (_, { rejectWithValue }) => {
    try {
      const response = await fetch('/api/users')
      return await response.json()
    } catch (err) {
      return rejectWithValue(err.message)
    }
  }
)

const usersSlice = createSlice({
  name: 'users',
  initialState: {
    items: [],
    loading: 'idle', // 'idle' | 'pending' | 'succeeded' | 'failed'
    error: null,
  },
  reducers: {
    // 普通的同步 reducer
    addUser: (state, action) => {
      // 这里看起来是「修改」,但 RTK 通过 Immer 转成了不可变更新
      state.items.push(action.payload)
    },
    clearUsers: (state) => {
      state.items = []
      state.loading = 'idle'
    },
  },
  extraReducers: (builder) => {
    // 处理 createAsyncThunk 的生命周期
    builder
      .addCase(fetchUsers.pending, (state) => {
        state.loading = 'pending'
      })
      .addCase(fetchUsers.fulfilled, (state, action) => {
        state.loading = 'succeeded'
        state.items = action.payload
      })
      .addCase(fetchUsers.rejected, (state, action) => {
        state.loading = 'failed'
        state.error = action.payload
      })
  },
})

export const { addUser, clearUsers } = usersSlice.actions
export default usersSlice.reducer

RTK Query——数据获取的终极方案:

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
// api.js - RTK Query
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  tagTypes: ['User', 'Post'],
  endpoints: (builder) => ({
    getUsers: builder.query({
      query: () => '/users',
      providesTags: ['User'],
    }),
    getUserById: builder.query({
      query: (id) => `/users/${id}`,
      providesTags: (result, error, id) => [{ type: 'User', id }],
    }),
    updateUser: builder.mutation({
      query: ({ id, ...data }) => ({
        url: `/users/${id}`,
        method: 'PUT',
        body: data,
      }),
      invalidatesTags: (result, error, { id }) => [
        { type: 'User', id },
        'User', // 同时刷新列表
      ],
    }),
  }),
})

export const { useGetUsersQuery, useGetUserByIdQuery, useUpdateUserMutation } = api

2. Zustand:极简主义的拥抱者

Zustand(德语中「状态」的意思)以约 1KB 的体积提供了一个极致简洁的 API:

快速上手

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
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'

// 带中间件的 store
const useStore = create(
  devtools(
    persist(
      (set, get) => ({
        // state
        user: null,
        token: null,
        notifications: [],

        // actions
        login: async (credentials) => {
          const response = await fetch('/api/login', {
            method: 'POST',
            body: JSON.stringify(credentials),
          })
          const data = await response.json()

          // set 可以传入部分状态,自动合并
          set({
            user: data.user,
            token: data.token,
          })
        },

        logout: () => {
          set({ user: null, token: null })
          // get() 可以获取当前状态
          console.log('User logged out, was:', get().user?.name)
        },

        addNotification: (notification) => {
          set((state) => ({
            notifications: [...state.notifications, notification],
          }))
        },

        markAllRead: () => {
          set({ notifications: [] })
        },
      }),
      {
        name: 'auth-storage', // localStorage key
        partialize: (state) => ({
          // 只持久化需要持久化的字段
          user: state.user,
          token: state.token,
        }),
      }
    ),
    { name: 'MyAppStore' }
  )
)

export default useStore

避免不必要的渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ❌ 反例:组件订阅了所有状态
function Profile() {
  const user = useStore((state) => state.user)
  const notifications = useStore((state) => state.notifications)
  // 当 notifications 变化时,这个组件也会重新渲染
  // 即使它只关心 user
}

// ✅ 正解:精确选择
const user = useStore((state) => state.user)
// 或者使用 shallow 比较
import { shallow } from 'zustand/shallow'

function Profile() {
  const { user, login } = useStore(
    (state) => ({ user: state.user, login: state.login }),
    shallow // 浅比较:user 没变就不重新渲染
  )
}

状态切片模式

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
// 将大 store 拆分为多个小 store
import { create } from 'zustand'

// 创建独立的 store 切片
const useAuthStore = create((set) => ({
  user: null,
  token: null,
  login: (user, token) => set({ user, token }),
  logout: () => set({ user: null, token: null }),
}))

const useCartStore = create((set, get) => ({
  items: [],
  addProduct: (product) =>
    set((state) => ({ items: [...state.items, product] })),
  checkout: () => {
    const items = get().items
    const user = useAuthStore.getState().user // 跨 store 访问
    // ...
  },
}))

const useNotificationStore = create((set) => ({
  messages: [],
  push: (msg) => set((s) => ({ messages: [...s.messages, msg] })),
  dismiss: (id) =>
    set((s) => ({ messages: s.messages.filter((m) => m.id !== id) })),
}))

3. Jotai:原子化的先锋

Jotai 受 Recoil 启发,但更简洁、更轻量。它的核心思想是「原子即状态」——每个状态都是一个独立的原子,组件按需订阅。

原子基础

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
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'

// 基础原子
const countAtom = atom(0)
const textAtom = atom('hello')
const userAtom = atom({ name: 'Alice', age: 30 })

// 派生原子(读)
const doubleCountAtom = atom((get) => get(countAtom) * 2)

// 派生原子(读写)
const userDisplayAtom = atom(
  (get) => `${get(userAtom).name} (${get(userAtom).age})`,
  (get, set, newName) => {
    set(userAtom, { ...get(userAtom), name: newName })
  }
)

// 异步原子
const userDataAtom = atom(async (get) => {
  const userId = get(userIdAtom)
  const response = await fetch(`/api/users/${userId}`)
  return response.json()
})

// 组件中使用
function Counter() {
  // 读
  const [count, setCount] = useAtom(countAtom)
  // 只读
  const doubleCount = useAtomValue(doubleCountAtom)
  // 只写
  const setUserDisplay = useSetAtom(userDisplayAtom)

  return (
    <div>
      <p>{count} (double: {doubleCount})</p>
      <button onClick={() => setCount((c) => c + 1)}>+</button>
    </div>
  )
}

原子化的优势——精确重渲染

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
import { atom, useAtom } from 'jotai'

// 两个独立的原子
const firstNameAtom = atom('')
const lastNameAtom = atom('')

// 派生原子
const fullNameAtom = atom((get) =>
  `${get(firstNameAtom)} ${get(lastNameAtom)}`
)

// 组件 A:只关心 firstName
function FirstNameInput() {
  const [firstName, setFirstName] = useAtom(firstNameAtom)
  // 只有 firstNameAtom 变化时才会重新渲染
  // lastNameAtom 的变化不会影响这个组件
  return (
    <input value={firstName} onChange={(e) => setFirstName(e.target.value)} />
  )
}

// 组件 B:只关心 lastName
function LastNameInput() {
  const [lastName, setLastName] = useAtom(lastNameAtom)
  return (
    <input value={lastName} onChange={(e) => setLastName(e.target.value)} />
  )
}

// 组件 C:关心全名
function FullNameDisplay() {
  const fullName = useAtomValue(fullNameAtom)
  // 只有 fullNameAtom 变化时才重新渲染
  // 而 fullNameAtom 只会在 firstName 或 lastName 变化时才变化
  return <p>全名{fullName}</p>
}

这是 Jotai 的核心优势——渲染粒度精确到原子级别,没有额外的选择器或 shallow compare。

4. 三大方案对比

维度Redux ToolkitZustandJotai
体积~11KB gzip~1KB gzip~3KB gzip
架构集中式 Store分离式 Store原子化
学习曲线中高
TypeScript良好优秀优秀
Devtools最佳良好(中间件)良好
渲染优化useSelector + shallowEqualSelector 精确订阅原子级自动
异步处理createAsyncThunk内置 async 支持异步原子
中间件丰富的官方生态中间件 APIJotai 插件系统
适用场景大型企业级应用中小型应用需要细粒度性能

实战案例:电商应用状态管理

让我们用三种方案分别实现一个电商应用的核心状态管理:

场景:购物车 + 产品浏览 + 用户状态

Zustand 实现(简洁优先)

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
// stores/useEcommerceStore.js
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'

const useEcommerceStore = create(
  devtools(
    persist(
      (set, get) => ({
        // ── 产品 ──
        products: [],
        currentProduct: null,
        isLoading: false,

        setProducts: (products) => set({ products }),
        setCurrentProduct: (product) => set({ currentProduct: product }),

        fetchProducts: async (category) => {
          set({ isLoading: true })
          try {
            const res = await fetch(`/api/products?category=${category}`)
            const data = await res.json()
            set({ products: data, isLoading: false })
          } catch (err) {
            set({ error: err.message, isLoading: false })
          }
        },

        // ── 购物车 ──
        cart: [],
        cartOpen: false,

        toggleCart: () => set((s) => ({ cartOpen: !s.cartOpen })),

        addToCart: (product) =>
          set((state) => {
            const existing = state.cart.find((i) => i.id === product.id)
            if (existing) {
              return {
                cart: state.cart.map((i) =>
                  i.id === product.id
                    ? { ...i, quantity: i.quantity + 1 }
                    : i
                ),
              }
            }
            return { cart: [...state.cart, { ...product, quantity: 1 }] }
          }),

        removeFromCart: (productId) =>
          set((state) => ({
            cart: state.cart.filter((i) => i.id !== productId),
          })),

        updateQuantity: (productId, quantity) =>
          set((state) => ({
            cart: quantity <= 0
              ? state.cart.filter((i) => i.id !== productId)
              : state.cart.map((i) =>
                  i.id === productId ? { ...i, quantity } : i
                ),
          })),

        cartTotal: () =>
          get().cart.reduce((sum, i) => sum + i.price * i.quantity, 0),

        clearCart: () => set({ cart: [] }),
      }),
      {
        name: 'ecommerce-storage',
        partialize: (state) => ({ cart: state.cart }),
      }
    ),
    { name: 'EcommerceStore' }
  )
)

export default useEcommerceStore

Jotai 实现(精确渲染)

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
// atoms/index.js - Jotai 原子实现
import { atom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'

// 基础原子
export const productsAtom = atom([])
export const currentCategoryAtom = atom('all')
export const currentProductIdAtom = atom(null)
export const isLoadingAtom = atom(false)

// 派生原子:获取当前产品详情
export const currentProductAtom = atom((get) => {
  const products = get(productsAtom)
  const id = get(currentProductIdAtom)
  return products.find((p) => p.id === id) || null
})

// 筛选后的产品列表
export const filteredProductsAtom = atom((get) => {
  const products = get(productsAtom)
  const category = get(currentCategoryAtom)
  if (category === 'all') return products
  return products.filter((p) => p.category === category)
})

// 购物车:使用 atomWithStorage 自动持久化
export const cartAtom = atomWithStorage('cart', [])

// 购物车派生计算
export const cartStatsAtom = atom((get) => {
  const cart = get(cartAtom)
  return {
    count: cart.reduce((sum, i) => sum + i.quantity, 0),
    subtotal: cart.reduce((sum, i) => sum + i.price * i.quantity, 0),
    items: cart,
  }
})

// 购物车操作原子
export const addToCartAtom = atom(null, (get, set, product) => {
  const cart = get(cartAtom)
  const existing = cart.find((i) => i.id === product.id)
  if (existing) {
    set(cartAtom, cart.map((i) =>
      i.id === product.id ? { ...i, quantity: i.quantity + 1 } : i
    ))
  } else {
    set(cartAtom, [...cart, { ...product, quantity: 1 }])
  }
})

export const removeFromCartAtom = atom(null, (get, set, productId) => {
  set(cartAtom, get(cartAtom).filter((i) => i.id !== productId))
})

// 异步原子:获取产品列表
export const fetchProductsAtom = atom(
  null,
  async (get, set, category) => {
    set(isLoadingAtom, true)
    const data = await fetch(`/api/products?category=${category}`).then((r) => r.json())
    set(productsAtom, data)
    set(isLoadingAtom, false)
  }
)

底层原理(含源码分析)

1. Redux 的发布订阅模型

Redux 的核心是一个非常简单的发布订阅(Pub/Sub)模式:

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
// Redux 源码核心简化
function createStore(reducer, preloadedState) {
  let state = preloadedState
  const listeners = new Set()

  // dispatch:分发 action
  function dispatch(action) {
    // 1. 执行 reducer 产生新 state
    state = reducer(state, action)

    // 2. 通知所有订阅者
    listeners.forEach((listener) => listener())

    return action
  }

  // subscribe:订阅变化
  function subscribe(listener) {
    listeners.add(listener)
    // 返回 unsubscribe 函数
    return () => listeners.delete(listener)
  }

  // getState:获取当前状态
  function getState() {
    return state
  }

  dispatch({ type: '@@INIT' })

  return { dispatch, subscribe, getState }
}

2. Zustand 的 selector 订阅机制

Zustand 的核心是一个可订阅的状态容器,通过 selector 实现精细化的订阅:

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
// Zustand 源码核心简化
function createStore(createState) {
  let state
  const listeners = new Set()

  // 核心:创建状态
  const setState = (partial) => {
    const nextState = typeof partial === 'function'
      ? partial(state)
      : partial

    if (!Object.is(nextState, state)) {
      const previousState = state
      state = Object.assign({}, state, nextState)
      // 通知所有 listener
      listeners.forEach((listener) => listener(state, previousState))
    }
  }

  const getState = () => state
  const subscribe = (listener) => {
    listeners.add(listener)
    return () => listeners.delete(listener)
  }

  const api = { setState, getState, subscribe }

  // 初始化 state
  state = createState(setState, getState, api)

  return api
}

// React 绑定:useStore Hook
function useStore(store, selector = (s) => s) {
  const [state, setState] = useState(() => selector(store.getState()))

  useEffect(() => {
    // 订阅 store 变化
    const unsubscribe = store.subscribe((newState) => {
      // 使用 selector 提取需要的值
      const selected = selector(newState)
      // 只有选择的片段变化时才更新
      setState(selected)
    })
    return unsubscribe
  }, [store])

  return state
}

核心精妙之处:React 渲染是由 setState 触发的。通过在 subscriber 内部使用 setState 而不是通过完整的 store 刷新,Zustand 实现了「selector 级别的精确渲染」——只有组件选择的状态片段变化时才会重新渲染。

3. Jotai 的原子依赖图

Jotai 的核心是构建一个原子依赖图,每个原子都知道它依赖哪些原子,以及被哪些原子依赖:

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
// Jotai 源码核心简化
function atom(read, write) {
  const key = Symbol('atom')

  return {
    key,
    read,   // (get) => value
    write,  // (get, set, value) => void
    // 运行时会被赋予更多属性
  }
}

// 原子状态存储
class AtomStore {
  constructor() {
    this.atomStateMap = new Map()
    this.listeners = new Set()
  }

  // 读取原子值
  get(atom) {
    let state = this.atomStateMap.get(atom.key)

    if (!state) {
      // 首次读取:初始化原子
      const value = atom.read({
        get: (dep) => this.get(dep) // 递归获取依赖原子
      })
      state = { value, deps: new Set() }
      this.atomStateMap.set(atom.key, state)
    }

    return state.value
  }

  // 设置原子值
  set(atom, value) {
    const state = this.atomStateMap.get(atom.key)

    if (typeof value === 'function') {
      value = value(state.value)
    }

    if (!Object.is(state.value, value)) {
      state.value = value
      // 通知订阅者
      this.listeners.forEach((listener) => listener(atom, value))
    }
  }
}

Jotai 的依赖追踪确保只更新需要更新的组件。当 firstNameAtom 变化时,依赖图如下

1
2
3
4
5
6
firstNameAtom ──┐
                 ├── fullNameAtom ──→ FullNameDisplay (重新渲染)
lastNameAtom ───┘

FirstNameInput ←── firstNameAtom (重新渲染)
// lastNameAtom 没有变化,LastNameInput 不会重新渲染

高频面试题解析

Q1: Redux 的 useSelector 是如何避免不必要的渲染的?

考察点:React-Redux 的渲染优化机制。

答案核心

useSelector 内部维护了一个「选择后的值」的引用。每次 store 状态变化时,它会执行选择器函数获取新的值,然后用 === 比较新旧值。如果值相同,就不会触发组件重新渲染。

1
2
3
4
5
6
7
8
9
10
11
12
// useSelector 的核心逻辑
function useSelector(selector) {
  const store = useStore()

  // 每次 store 变化时执行选择器
  const selectedState = useSyncExternalStore(
    store.subscribe,
    () => selector(store.getState())
  )

  return selectedState
}

通过 useSyncExternalStore(React 18 内置),React-Redux 能够精确检测 store 变化并仅更新相关组件。此外,shallowEqualequalityFn 选项允许开发者自定义比较逻辑,进一步减少不必要的渲染。

Q2: Zustand 为什么不使用 Context 而是使用闭包?

考察点:对 React 渲染机制和 Zustand 架构的理解。

答案核心

Zustand 选择使用模块级闭包而不是 React Context 有几个关键原因:

  1. 避免 Context 的 Provider 嵌套:Context 需要在 Provider 树中传递,而闭包可以在任何地方直接访问状态
  2. 消除不必要的重新渲染:Context value 变化会导致所有 Consumer 组件重新渲染,无论它们使用哪部分数据;Zustand 的 selector 机制解决了这个问题
  3. 脱离 React 组件树:Zustand 的状态可以在任何 JavaScript 环境中使用(如纯函数、工具函数),不需要 React 环境
  4. 性能优势:闭包直接引用内存对象,没有 React reconciler 的介入
1
2
3
4
5
6
7
8
// Context 的问题
// 当 store 变化时,所有消费该 Context 的组件都会重新渲染,
// 即使它们只关心 store 中的某一部分

// Zustand 的优势
// 每个组件精确订阅它选择的状态片段
// 状态可以在 React 外部访问
store.getState() // ✅ 任何地方都能访问

Q3: Jotai 的衍生原子在性能上有什么优势?什么时候该用 Jotai 而不是 Zustand?

考察点:对原子化状态管理性能特性的理解。

答案核心

Jotai 的性能优势

  1. 没有选择器开销:组件直接订阅原子,不需要 selector 函数
  2. 精确的重渲染边界:原子变化只会影响订阅该原子的组件,不会影响其兄弟组件
  3. 惰性计算:只有被读取的原子才会计算,未被读取的衍生原子不会执行

选型建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
用 Jotai 当...
  - 状态之间有复杂的依赖关系
  - 需要精确控制渲染粒度
  - 数据流灵活变化

用 Zustand 当...
  - 状态结构相对稳定
  - 需要简洁 API 和低学习成本
  - SSR/Next.js 场景
  - 需要跨组件作用域访问状态

用 Redux Toolkit 当...
  - 大型团队协作
  - 需要中间件和插件系统
  - 复杂的数据获取逻辑(RTK Query)
  - 时间旅行调试是刚需

总结与扩展

React 状态管理的生态正在走向「按需选择」的多元化时代,不再有一个方案能统治所有场景。Redux Toolkit 是大型团队和企业级应用的稳妥之选,Zustand 提供了最简洁的体验和最小的体积,Jotai 则为极致渲染性能提供了原子化的解决方案。

核心建议

  • 学习投入:Redux Toolkit 值得投入时间学习,它是 React 生态中最成熟的状态管理方案
  • 项目节奏:快速原型用 Zustand,项目扩展时再考虑是否迁移
  • 性能敏感:状态变化频繁且需要精确渲染控制的场景,Jotai 是最佳选择

扩展方向

Server State 与 Client State 的分离是当前趋势。越来越多的项目采用「Zustand/Jotai 管理客户端状态 + TanStack Query 管理服务端状态」的组合方案。这种职责分离模式,让状态管理工具专注于它最擅长的部分——管理「本地、同步」的客户端状态,而数据获取的缓存、加载、错误处理等逻辑则交给专用工具。

本文由作者按照 CC BY 4.0 进行授权