useContext与useReducer #

一、useContext深入 #

1.1 Context基础 #

Context 提供了一种在组件树中共享数据的方式,无需手动传递 props。

javascript
import { createContext, useContext } from 'react';

// 创建Context
const ThemeContext = createContext('light');

// Provider组件
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// 消费Context
function Button() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Button</button>;
}

1.2 Context层级结构 #

text
┌─────────────────────────────────────────────────────┐
│                    App                              │
│  ┌───────────────────────────────────────────────┐  │
│  │         ThemeContext.Provider                 │  │
│  │  value="dark"                                 │  │
│  │  ┌─────────────────────────────────────────┐  │  │
│  │  │              Toolbar                    │  │  │
│  │  │  ┌───────────────────────────────────┐  │  │  │
│  │  │  │           Button                  │  │  │  │
│  │  │  │  useContext(ThemeContext) → "dark"│  │  │  │
│  │  │  └───────────────────────────────────┘  │  │  │
│  │  └─────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘

1.3 动态Context #

javascript
const ThemeContext = createContext();

function App() {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function ThemeToggle() {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      当前主题: {theme}
    </button>
  );
}

1.4 默认值 #

javascript
// 默认值在没有匹配Provider时使用
const UserContext = createContext({
  user: null,
  setUser: () => {}
});

// 组件在Provider外部使用时获得默认值
function Profile() {
  const { user } = useContext(UserContext);
  
  if (!user) {
    return <div>请登录</div>;
  }
  
  return <div>欢迎, {user.name}</div>;
}

1.5 多个Context #

javascript
const ThemeContext = createContext('light');
const UserContext = createContext(null);

function App() {
  const [theme, setTheme] = useState('light');
  const [user, setUser] = useState(null);

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <UserContext.Provider value={{ user, setUser }}>
        <Layout />
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

function Header() {
  const { theme, setTheme } = useContext(ThemeContext);
  const { user, setUser } = useContext(UserContext);

  return (
    <header className={theme}>
      {user ? (
        <span>{user.name}</span>
      ) : (
        <button onClick={() => setUser({ name: 'Guest' })}>登录</button>
      )}
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        切换主题
      </button>
    </header>
  );
}

二、useReducer深入 #

2.1 Reducer模式 #

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 };
    case 'reset':
      return initialState;
    default:
      throw new Error(`Unknown action: ${action.type}`);
  }
}

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

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

2.2 Action模式 #

javascript
// 简单Action
dispatch({ type: 'increment' });

// 带Payload的Action
dispatch({ type: 'set', payload: 10 });

// 复杂Payload
dispatch({
  type: 'update_user',
  payload: {
    name: 'Alice',
    email: 'alice@example.com'
  }
});

2.3 复杂状态管理 #

javascript
const initialState = {
  todos: [],
  filter: 'all',
  loading: false,
  error: null
};

function todoReducer(state, action) {
  switch (action.type) {
    case 'FETCH_START':
      return { ...state, loading: true, error: null };
    
    case 'FETCH_SUCCESS':
      return { ...state, loading: false, todos: action.payload };
    
    case 'FETCH_ERROR':
      return { ...state, loading: false, error: action.payload };
    
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, {
          id: Date.now(),
          text: action.payload,
          completed: false
        }]
      };
    
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
    
    case 'DELETE_TODO':
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload)
      };
    
    case 'SET_FILTER':
      return { ...state, filter: action.payload };
    
    default:
      return state;
  }
}

2.4 惰性初始化 #

javascript
function init(initialState) {
  return {
    ...initialState,
    todos: JSON.parse(localStorage.getItem('todos') || '[]')
  };
}

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, initialState, init);
  
  // ...
}

三、组合使用 #

3.1 基本组合模式 #

javascript
const StateContext = createContext();
const DispatchContext = createContext();

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

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

// 自定义Hook获取状态
function useTodoState() {
  const context = useContext(StateContext);
  if (context === undefined) {
    throw new Error('useTodoState must be used within Provider');
  }
  return context;
}

// 自定义Hook获取dispatch
function useTodoDispatch() {
  const context = useContext(DispatchContext);
  if (context === undefined) {
    throw new Error('useTodoDispatch must be used within Provider');
  }
  return context;
}

// 使用
function TodoItem({ todo }) {
  const state = useTodoState();
  const dispatch = useTodoDispatch();

  return (
    <div>
      <span>{todo.text}</span>
      <button onClick={() => dispatch({ type: 'toggle', payload: todo.id })}>
        {todo.completed ? '取消' : '完成'}
      </button>
    </div>
  );
}

3.2 完整示例:Todo应用 #

javascript
const TodoContext = createContext();

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

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

function TodoProvider({ children }) {
  const [state, dispatch] = useReducer(todoReducer, initialState);

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

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

function TodoApp() {
  return (
    <TodoProvider>
      <TodoHeader />
      <TodoList />
      <TodoFooter />
    </TodoProvider>
  );
}

function TodoHeader() {
  const { dispatch } = useTodo();
  const [text, setText] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (text.trim()) {
      dispatch({ type: 'ADD_TODO', payload: text });
      setText('');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="添加任务"
      />
      <button type="submit">添加</button>
    </form>
  );
}

function TodoList() {
  const { state } = useTodo();
  
  const filteredTodos = state.todos.filter(todo => {
    if (state.filter === 'active') return !todo.completed;
    if (state.filter === 'completed') return todo.completed;
    return true;
  });

  return (
    <ul>
      {filteredTodos.map(todo => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </ul>
  );
}

function TodoItem({ todo }) {
  const { dispatch } = useTodo();

  return (
    <li>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
      />
      <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
        {todo.text}
      </span>
      <button onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}>
        删除
      </button>
    </li>
  );
}

function TodoFooter() {
  const { state, dispatch } = useTodo();
  const remaining = state.todos.filter(t => !t.completed).length;

  return (
    <div>
      <span>{remaining} 项未完成</span>
      <button onClick={() => dispatch({ type: 'SET_FILTER', payload: 'all' })}>
        全部
      </button>
      <button onClick={() => dispatch({ type: 'SET_FILTER', payload: 'active' })}>
        未完成
      </button>
      <button onClick={() => dispatch({ type: 'SET_FILTER', payload: 'completed' })}>
        已完成
      </button>
    </div>
  );
}

四、最佳实践 #

4.1 分离Context #

javascript
// ✅ 分离状态和dispatch
const StateContext = createContext();
const DispatchContext = createContext();

// 避免不必要的重新渲染
function Display() {
  const state = useContext(StateContext);
  return <div>{state.count}</div>;
}

function Controls() {
  const dispatch = useContext(DispatchContext);
  return <button onClick={() => dispatch({ type: 'increment' })}>+</button>;
}

4.2 自定义Hook封装 #

javascript
function useTodoContext() {
  const state = useContext(StateContext);
  const dispatch = useContext(DispatchContext);

  const addTodo = useCallback((text) => {
    dispatch({ type: 'ADD_TODO', payload: text });
  }, [dispatch]);

  const toggleTodo = useCallback((id) => {
    dispatch({ type: 'TOGGLE_TODO', payload: id });
  }, [dispatch]);

  const deleteTodo = useCallback((id) => {
    dispatch({ type: 'DELETE_TODO', payload: id });
  }, [dispatch]);

  return {
    ...state,
    addTodo,
    toggleTodo,
    deleteTodo
  };
}

4.3 类型安全(TypeScript) #

typescript
type State = {
  todos: Todo[];
  filter: FilterType;
};

type Action =
  | { type: 'ADD_TODO'; payload: string }
  | { type: 'TOGGLE_TODO'; payload: number }
  | { type: 'DELETE_TODO'; payload: number }
  | { type: 'SET_FILTER'; payload: FilterType };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, { id: Date.now(), text: action.payload, completed: false }]
      };
    // ...
  }
}

五、性能优化 #

5.1 避免不必要的渲染 #

javascript
// 使用React.memo
const TodoItem = React.memo(function TodoItem({ todo, onToggle, onDelete }) {
  return (
    <li>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      <span>{todo.text}</span>
      <button onClick={() => onDelete(todo.id)}>删除</button>
    </li>
  );
});

5.2 拆分Context #

javascript
// 拆分为多个Context
const TodosContext = createContext([]);
const FilterContext = createContext('all');

function TodoList() {
  const todos = useContext(TodosContext);
  const filter = useContext(FilterContext);
  
  const filteredTodos = todos.filter(todo => {
    if (filter === 'active') return !todo.completed;
    if (filter === 'completed') return todo.completed;
    return true;
  });

  return <ul>{filteredTodos.map(todo => <TodoItem key={todo.id} todo={todo} />)}</ul>;
}

六、总结 #

要点 说明
useContext 访问Context值
useReducer 复杂状态管理
组合使用 实现全局状态管理
分离Context 优化渲染性能

核心原则:

  • 使用 Context 避免 prop drilling
  • 使用 useReducer 管理复杂状态
  • 分离状态和 dispatch 优化性能
  • 封装自定义 Hook 提供更好的 API
最后更新:2026-03-26