Skip to content

状态管理

状态管理是 React 应用开发中的重要环节,尤其是对于复杂的大型应用。本章节将介绍 React 中的状态管理方案。

11.1 为什么需要状态管理?

状态管理的必要性

  • 组件间共享数据:多个组件需要访问和修改同一个数据
  • 复杂的状态逻辑:状态更新依赖于多个因素,逻辑复杂
  • 跨组件通信:避免 props 层层传递的问题
  • 状态持久化:在页面刷新后保持状态

常见的状态管理场景

  • 用户信息:登录状态、用户资料
  • 应用配置:主题、语言设置
  • 购物车:商品列表、数量
  • 表单数据:多步骤表单的状态
  • 全局通知:消息提示、错误信息

11.2 三种状态管理方案

方案1:useReducer + useContext

对于中小型应用,使用 useReducer 结合 useContext 是一个轻量级的状态管理方案。

基本使用

jsx
// src/contexts/StoreContext.jsx
import { createContext, useReducer, useContext } from 'react';

// 初始状态
const initialState = {
  count: 0,
  user: null,
  todos: []
};

// Action 类型
const ActionTypes = {
  INCREMENT: 'INCREMENT',
  DECREMENT: 'DECREMENT',
  SET_USER: 'SET_USER',
  ADD_TODO: 'ADD_TODO',
  REMOVE_TODO: 'REMOVE_TODO'
};

// Reducer 函数
function reducer(state, action) {
  switch (action.type) {
    case ActionTypes.INCREMENT:
      return { ...state, count: state.count + 1 };
    case ActionTypes.DECREMENT:
      return { ...state, count: state.count - 1 };
    case ActionTypes.SET_USER:
      return { ...state, user: action.payload };
    case ActionTypes.ADD_TODO:
      return { ...state, todos: [...state.todos, action.payload] };
    case ActionTypes.REMOVE_TODO:
      return { ...state, todos: state.todos.filter((_, index) => index !== action.payload) };
    default:
      return state;
  }
}

// 创建 Context
const StoreContext = createContext();

// 提供者组件
export function StoreProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  return (
    <StoreContext.Provider value={{ state, dispatch, ActionTypes }}>
      {children}
    </StoreContext.Provider>
  );
}

// 自定义 Hook
export function useStore() {
  const context = useContext(StoreContext);
  if (!context) {
    throw new Error('useStore must be used within a StoreProvider');
  }
  return context;
}

使用全局状态

jsx
// App.jsx
import { StoreProvider } from './contexts/StoreContext';
import Counter from './components/Counter';
import UserProfile from './components/UserProfile';
import TodoList from './components/TodoList';

function App() {
  return (
    <StoreProvider>
      <Counter />
      <UserProfile />
      <TodoList />
    </StoreProvider>
  );
}

// Counter.jsx
import { useStore } from '../contexts/StoreContext';

function Counter() {
  const { state, dispatch, ActionTypes } = useStore();
  
  return (
    <div>
      <p>计数:{state.count}</p>
      <button onClick={() => dispatch({ type: ActionTypes.INCREMENT })}>增加</button>
      <button onClick={() => dispatch({ type: ActionTypes.DECREMENT })}>减少</button>
    </div>
  );
}

// UserProfile.jsx
import { useStore } from '../contexts/StoreContext';

function UserProfile() {
  const { state, dispatch, ActionTypes } = useStore();
  
  function login() {
    dispatch({ 
      type: ActionTypes.SET_USER, 
      payload: { id: 1, name: '张三', email: 'zhangsan@example.com' } 
    });
  }
  
  function logout() {
    dispatch({ type: ActionTypes.SET_USER, payload: null });
  }
  
  return (
    <div>
      {state.user ? (
        <div>
          <h2>{state.user.name}</h2>
          <p>{state.user.email}</p>
          <button onClick={logout}>退出登录</button>
        </div>
      ) : (
        <button onClick={login}>登录</button>
      )}
    </div>
  );
}

// TodoList.jsx
import { useState } from 'react';
import { useStore } from '../contexts/StoreContext';

function TodoList() {
  const { state, dispatch, ActionTypes } = useStore();
  const [inputValue, setInputValue] = useState('');
  
  function addTodo() {
    if (inputValue) {
      dispatch({ type: ActionTypes.ADD_TODO, payload: inputValue });
      setInputValue('');
    }
  }
  
  function removeTodo(index) {
    dispatch({ type: ActionTypes.REMOVE_TODO, payload: index });
  }
  
  return (
    <div>
      <input
        type="text"
        value={inputValue}
        onChange={e => setInputValue(e.target.value)}
        placeholder="请输入任务"
      />
      <button onClick={addTodo}>添加</button>
      <ul>
        {state.todos.map((todo, index) => (
          <li key={index}>
            {todo}
            <button onClick={() => removeTodo(index)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

方案2:Redux Toolkit

Redux Toolkit (RTK) 是官方推荐的 Redux 工具集,它简化了传统 Redux 的使用。

安装

bash
npm install @reduxjs/toolkit react-redux

基本配置

jsx
// src/store/index.js
import { configureStore, createSlice } from '@reduxjs/toolkit';

// 创建 counter slice
const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    }
  }
});

// 创建 user slice
const userSlice = createSlice({
  name: 'user',
  initialState: {
    user: null
  },
  reducers: {
    setUser: (state, action) => {
      state.user = action.payload;
    },
    clearUser: (state) => {
      state.user = null;
    }
  }
});

// 导出 actions
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export const { setUser, clearUser } = userSlice.actions;

// 配置 store
const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
    user: userSlice.reducer
  }
});

export default store;

提供 store

jsx
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

使用 store

jsx
// Counter.jsx
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from '../store';

function Counter() {
  const count = useSelector(state => state.counter.value);
  const dispatch = useDispatch();
  
  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={() => dispatch(increment())}>增加</button>
      <button onClick={() => dispatch(decrement())}>减少</button>
      <button onClick={() => dispatch(incrementByAmount(5))}>增加 5</button>
    </div>
  );
}

// UserProfile.jsx
import { useSelector, useDispatch } from 'react-redux';
import { setUser, clearUser } from '../store';

function UserProfile() {
  const user = useSelector(state => state.user.user);
  const dispatch = useDispatch();
  
  function login() {
    dispatch(setUser({ id: 1, name: '张三', email: 'zhangsan@example.com' }));
  }
  
  function logout() {
    dispatch(clearUser());
  }
  
  return (
    <div>
      {user ? (
        <div>
          <h2>{user.name}</h2>
          <p>{user.email}</p>
          <button onClick={logout}>退出登录</button>
        </div>
      ) : (
        <button onClick={login}>登录</button>
      )}
    </div>
  );
}

方案3:Zustand

Zustand 是一个轻量级的状态管理库,它的 API 简单易用,适合中小型应用。

安装

bash
npm install zustand

基本使用

jsx
// src/store/useStore.js
import create from 'zustand';

const useStore = create((set) => ({
  count: 0,
  user: null,
  todos: [],
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  setUser: (user) => set({ user }),
  clearUser: () => set({ user: null }),
  addTodo: (todo) => set((state) => ({ todos: [...state.todos, todo] })),
  removeTodo: (index) => set((state) => ({
    todos: state.todos.filter((_, i) => i !== index)
  }))
}));

export default useStore;

使用 store

jsx
// Counter.jsx
import useStore from '../store/useStore';

function Counter() {
  const count = useStore(state => state.count);
  const increment = useStore(state => state.increment);
  const decrement = useStore(state => state.decrement);
  
  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={increment}>增加</button>
      <button onClick={decrement}>减少</button>
    </div>
  );
}

// UserProfile.jsx
import useStore from '../store/useStore';

function UserProfile() {
  const user = useStore(state => state.user);
  const setUser = useStore(state => state.setUser);
  const clearUser = useStore(state => state.clearUser);
  
  function login() {
    setUser({ id: 1, name: '张三', email: 'zhangsan@example.com' });
  }
  
  function logout() {
    clearUser();
  }
  
  return (
    <div>
      {user ? (
        <div>
          <h2>{user.name}</h2>
          <p>{user.email}</p>
          <button onClick={logout}>退出登录</button>
        </div>
      ) : (
        <button onClick={login}>登录</button>
      )}
    </div>
  );
}

// TodoList.jsx
import { useState } from 'react';
import useStore from '../store/useStore';

function TodoList() {
  const todos = useStore(state => state.todos);
  const addTodo = useStore(state => state.addTodo);
  const removeTodo = useStore(state => state.removeTodo);
  const [inputValue, setInputValue] = useState('');
  
  function handleAddTodo() {
    if (inputValue) {
      addTodo(inputValue);
      setInputValue('');
    }
  }
  
  return (
    <div>
      <input
        type="text"
        value={inputValue}
        onChange={e => setInputValue(e.target.value)}
        placeholder="请输入任务"
      />
      <button onClick={handleAddTodo}>添加</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            {todo}
            <button onClick={() => removeTodo(index)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

11.3 状态持久化

使用 localStorage

jsx
// src/store/useStore.js
import create from 'zustand';
import { persist } from 'zustand/middleware';

const useStore = create(
  persist(
    (set) => ({
      count: 0,
      user: null,
      todos: [],
      increment: () => set((state) => ({ count: state.count + 1 })),
      decrement: () => set((state) => ({ count: state.count - 1 })),
      setUser: (user) => set({ user }),
      clearUser: () => set({ user: null }),
      addTodo: (todo) => set((state) => ({ todos: [...state.todos, todo] })),
      removeTodo: (index) => set((state) => ({
        todos: state.todos.filter((_, i) => i !== index)
      }))
    }),
    {
      name: 'my-app-storage' // localStorage 中的键名
    }
  )
);

export default useStore;

Redux 持久化

bash
npm install redux-persist
jsx
// src/store/index.js
import { configureStore, createSlice } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';

// 配置
const persistConfig = {
  key: 'root',
  storage,
};

// 创建 slices
const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    }
  }
});

// 持久化 reducer
const persistedReducer = persistReducer(persistConfig, counterSlice.reducer);

// 配置 store
const store = configureStore({
  reducer: {
    counter: persistedReducer
  }
});

// 创建 persistor
export const persistor = persistStore(store);
export const { increment, decrement } = counterSlice.actions;
export default store;
jsx
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import store, { persistor } from './store';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <App />
      </PersistGate>
    </Provider>
  </React.StrictMode>
);

实战练习

练习1:使用 useReducer + useContext

jsx
// src/contexts/TodoContext.jsx
import { createContext, useReducer, useContext } from 'react';

const initialState = {
  todos: [],
  filter: 'all' // all, active, completed
};

const ActionTypes = {
  ADD_TODO: 'ADD_TODO',
  TOGGLE_TODO: 'TOGGLE_TODO',
  REMOVE_TODO: 'REMOVE_TODO',
  SET_FILTER: 'SET_FILTER'
};

function reducer(state, action) {
  switch (action.type) {
    case ActionTypes.ADD_TODO:
      return {
        ...state,
        todos: [...state.todos, { id: Date.now(), text: action.payload, completed: false }]
      };
    case ActionTypes.TOGGLE_TODO:
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
        )
      };
    case ActionTypes.REMOVE_TODO:
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload)
      };
    case ActionTypes.SET_FILTER:
      return {
        ...state,
        filter: action.payload
      };
    default:
      return state;
  }
}

const TodoContext = createContext();

export function TodoProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  return (
    <TodoContext.Provider value={{ state, dispatch, ActionTypes }}>
      {children}
    </TodoContext.Provider>
  );
}

export function useTodo() {
  const context = useContext(TodoContext);
  if (!context) {
    throw new Error('useTodo must be used within a TodoProvider');
  }
  return context;
}

// src/App.jsx
import { TodoProvider } from './contexts/TodoContext';
import TodoForm from './components/TodoForm';
import TodoList from './components/TodoList';
import TodoFilter from './components/TodoFilter';

function App() {
  return (
    <TodoProvider>
      <TodoForm />
      <TodoList />
      <TodoFilter />
    </TodoProvider>
  );
}

export default App;

// src/components/TodoForm.jsx
import { useState } from 'react';
import { useTodo } from '../contexts/TodoContext';

function TodoForm() {
  const { dispatch, ActionTypes } = useTodo();
  const [inputValue, setInputValue] = useState('');
  
  function handleSubmit(e) {
    e.preventDefault();
    if (inputValue) {
      dispatch({ type: ActionTypes.ADD_TODO, payload: inputValue });
      setInputValue('');
    }
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={inputValue}
        onChange={e => setInputValue(e.target.value)}
        placeholder="请输入任务"
      />
      <button type="submit">添加</button>
    </form>
  );
}

export default TodoForm;

// src/components/TodoList.jsx
import { useTodo } from '../contexts/TodoContext';

function TodoList() {
  const { state, dispatch, ActionTypes } = useTodo();
  const { todos, filter } = state;
  
  const filteredTodos = todos.filter(todo => {
    if (filter === 'active') return !todo.completed;
    if (filter === 'completed') return todo.completed;
    return true;
  });
  
  function toggleTodo(id) {
    dispatch({ type: ActionTypes.TOGGLE_TODO, payload: id });
  }
  
  function removeTodo(id) {
    dispatch({ type: ActionTypes.REMOVE_TODO, payload: id });
  }
  
  return (
    <ul>
      {filteredTodos.map(todo => (
        <li key={todo.id}>
          <input
            type="checkbox"
            checked={todo.completed}
            onChange={() => toggleTodo(todo.id)}
          />
          <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
            {todo.text}
          </span>
          <button onClick={() => removeTodo(todo.id)}>删除</button>
        </li>
      ))}
    </ul>
  );
}

export default TodoList;

// src/components/TodoFilter.jsx
import { useTodo } from '../contexts/TodoContext';

function TodoFilter() {
  const { state, dispatch, ActionTypes } = useTodo();
  const { filter } = state;
  
  function setFilter(newFilter) {
    dispatch({ type: ActionTypes.SET_FILTER, payload: newFilter });
  }
  
  return (
    <div>
      <button
        className={filter === 'all' ? 'active' : ''}
        onClick={() => setFilter('all')}
      >
        全部
      </button>
      <button
        className={filter === 'active' ? 'active' : ''}
        onClick={() => setFilter('active')}
      >
        未完成
      </button>
      <button
        className={filter === 'completed' ? 'active' : ''}
        onClick={() => setFilter('completed')}
      >
        已完成
      </button>
    </div>
  );
}

export default TodoFilter;

练习2:使用 Zustand

jsx
// src/store/useTodoStore.js
import create from 'zustand';
import { persist } from 'zustand/middleware';

const useTodoStore = create(
  persist(
    (set) => ({
      todos: [],
      filter: 'all',
      addTodo: (text) => set((state) => ({
        todos: [...state.todos, { id: Date.now(), text, completed: false }]
      })),
      toggleTodo: (id) => set((state) => ({
        todos: state.todos.map(todo =>
          todo.id === id ? { ...todo, completed: !todo.completed } : todo
        )
      })),
      removeTodo: (id) => set((state) => ({
        todos: state.todos.filter(todo => todo.id !== id)
      })),
      setFilter: (filter) => set({ filter })
    }),
    {
      name: 'todo-storage'
    }
  )
);

export default useTodoStore;

// src/components/TodoForm.jsx
import { useState } from 'react';
import useTodoStore from '../store/useTodoStore';

function TodoForm() {
  const addTodo = useTodoStore(state => state.addTodo);
  const [inputValue, setInputValue] = useState('');
  
  function handleSubmit(e) {
    e.preventDefault();
    if (inputValue) {
      addTodo(inputValue);
      setInputValue('');
    }
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={inputValue}
        onChange={e => setInputValue(e.target.value)}
        placeholder="请输入任务"
      />
      <button type="submit">添加</button>
    </form>
  );
}

export default TodoForm;

// src/components/TodoList.jsx
import useTodoStore from '../store/useTodoStore';

function TodoList() {
  const todos = useTodoStore(state => state.todos);
  const filter = useTodoStore(state => state.filter);
  const toggleTodo = useTodoStore(state => state.toggleTodo);
  const removeTodo = useTodoStore(state => state.removeTodo);
  
  const filteredTodos = todos.filter(todo => {
    if (filter === 'active') return !todo.completed;
    if (filter === 'completed') return todo.completed;
    return true;
  });
  
  return (
    <ul>
      {filteredTodos.map(todo => (
        <li key={todo.id}>
          <input
            type="checkbox"
            checked={todo.completed}
            onChange={() => toggleTodo(todo.id)}
          />
          <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
            {todo.text}
          </span>
          <button onClick={() => removeTodo(todo.id)}>删除</button>
        </li>
      ))}
    </ul>
  );
}

export default TodoList;

// src/components/TodoFilter.jsx
import useTodoStore from '../store/useTodoStore';

function TodoFilter() {
  const filter = useTodoStore(state => state.filter);
  const setFilter = useTodoStore(state => state.setFilter);
  
  return (
    <div>
      <button
        className={filter === 'all' ? 'active' : ''}
        onClick={() => setFilter('all')}
      >
        全部
      </button>
      <button
        className={filter === 'active' ? 'active' : ''}
        onClick={() => setFilter('active')}
      >
        未完成
      </button>
      <button
        className={filter === 'completed' ? 'active' : ''}
        onClick={() => setFilter('completed')}
      >
        已完成
      </button>
    </div>
  );
}

export default TodoFilter;

通过本章节的学习,你已经掌握了 React 中的状态管理方案。在实际开发中,根据应用的规模和复杂度,选择合适的状态管理方案可以使代码更加清晰和易于维护。

© 2026 编程马·菜鸟教程 版权所有