Skip to content

React 状态管理

一、状态管理概述

1.1 为什么需要状态管理

  • 组件间共享状态
  • 避免 Prop Drilling
  • 复杂应用的状态逻辑集中管理
  • 状态持久化、时间旅行等高级功能

1.2 方案对比

方案复杂度适用场景
useState/useReducer局部状态
Context简单共享状态
Zustand中小型应用
Jotai/Recoil原子化状态
Redux Toolkit中高大型应用

二、React 内置方案

2.1 useReducer

javascript
const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  );
}

2.2 Context + useReducer

javascript
// 创建 Context
const StateContext = createContext();
const DispatchContext = createContext();

// Provider 组件
function AppProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <StateContext.Provider value={state}>
      <DispatchContext.Provider value={dispatch}>
        {children}
      </DispatchContext.Provider>
    </StateContext.Provider>
  );
}

// 自定义 Hook
function useAppState() {
  return useContext(StateContext);
}

function useAppDispatch() {
  return useContext(DispatchContext);
}

// 使用
function Counter() {
  const state = useAppState();
  const dispatch = useAppDispatch();
  // ...
}

2.3 Context 性能问题

javascript
// ❌ 问题:任何 state 变化都会触发所有消费者重渲染
const AppContext = createContext({ user: null, theme: 'light' });

// ✅ 解决方案 1:拆分 Context
const UserContext = createContext(null);
const ThemeContext = createContext('light');

// ✅ 解决方案 2:useMemo 包裹 value
function Provider({ children }) {
  const [user, setUser] = useState(null);
  const value = useMemo(() => ({ user, setUser }), [user]);
  return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}

// ✅ 解决方案 3:使用 use-context-selector
import { createContext, useContextSelector } from 'use-context-selector';
const count = useContextSelector(AppContext, (v) => v.count);

三、Zustand

3.1 基本使用

javascript
import { create } from 'zustand';

// 创建 Store
const useStore = create((set, get) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
  // 访问其他状态
  doubleCount: () => get().count * 2
}));

// 使用
function Counter() {
  const count = useStore((state) => state.count);
  const increment = useStore((state) => state.increment);

  return (
    <button onClick={increment}>{count}</button>
  );
}

3.2 异步操作

javascript
const useStore = create((set) => ({
  users: [],
  loading: false,
  error: null,

  fetchUsers: async () => {
    set({ loading: true, error: null });
    try {
      const response = await fetch('/api/users');
      const users = await response.json();
      set({ users, loading: false });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  }
}));

3.3 中间件

javascript
import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';

const useStore = create(
  devtools(
    persist(
      (set) => ({
        count: 0,
        increment: () => set((state) => ({ count: state.count + 1 }))
      }),
      {
        name: 'counter-storage'
      }
    )
  )
);

3.4 Slice 模式

javascript
// 拆分 Store
const createUserSlice = (set) => ({
  user: null,
  setUser: (user) => set({ user })
});

const createCartSlice = (set) => ({
  items: [],
  addItem: (item) => set((state) => ({ items: [...state.items, item] }))
});

// 合并
const useStore = create((...a) => ({
  ...createUserSlice(...a),
  ...createCartSlice(...a)
}));

四、Redux Toolkit

4.1 创建 Slice

javascript
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => {
      state.value += 1;  // Immer 允许直接修改
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    }
  }
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

4.2 配置 Store

javascript
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import userReducer from './userSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    user: userReducer
  }
});

// 类型推断
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

4.3 React 集成

javascript
import { Provider, useSelector, useDispatch } from 'react-redux';

// 根组件
<Provider store={store}>
  <App />
</Provider>

// 使用
function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <button onClick={() => dispatch(increment())}>
      {count}
    </button>
  );
}

4.4 异步操作 (createAsyncThunk)

javascript
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const fetchUsers = createAsyncThunk(
  'users/fetchUsers',
  async (_, { rejectWithValue }) => {
    try {
      const response = await fetch('/api/users');
      return response.json();
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

const usersSlice = createSlice({
  name: 'users',
  initialState: { list: [], loading: false, error: null },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUsers.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchUsers.fulfilled, (state, action) => {
        state.loading = false;
        state.list = action.payload;
      })
      .addCase(fetchUsers.rejected, (state, action) => {
        state.loading = false;
        state.error = action.payload;
      });
  }
});

五、Jotai (原子化状态)

5.1 基本使用

javascript
import { atom, useAtom } from 'jotai';

// 创建原子
const countAtom = atom(0);
const doubleCountAtom = atom((get) => get(countAtom) * 2);

// 可写派生原子
const countWithIncrement = atom(
  (get) => get(countAtom),
  (get, set, update) => {
    set(countAtom, get(countAtom) + update);
  }
);

// 使用
function Counter() {
  const [count, setCount] = useAtom(countAtom);
  const [doubleCount] = useAtom(doubleCountAtom);

  return (
    <>
      <p>{count} x 2 = {doubleCount}</p>
      <button onClick={() => setCount((c) => c + 1)}>+</button>
    </>
  );
}

5.2 异步原子

javascript
const userAtom = atom(async () => {
  const response = await fetch('/api/user');
  return response.json();
});

function UserProfile() {
  const [user] = useAtom(userAtom);
  // user 会在数据加载完成后更新
}

六、高频面试题

Q1: Context 的性能问题如何解决?

  1. 拆分多个 Context
  2. useMemo 包裹 Provider value
  3. 使用 use-context-selector
  4. 考虑使用 Zustand 等状态库

Q2: Redux 和 Zustand 的区别?

对比项ReduxZustand
样板代码
学习曲线平缓
中间件丰富基础
适用场景大型应用中小应用

Q3: 什么时候需要状态管理库?

  1. 多组件需要共享状态
  2. 状态逻辑复杂
  3. 需要持久化、时间旅行等功能
  4. Context 性能不满足需求

前端面试知识库