useContext与useReducer #

一、Context 概述 #

1.1 什么是 Context #

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

text
App (Provider)
├── Header
│   └── UserAvatar  ← 可以访问 Context
├── Main
│   └── Profile
│       └── UserSettings  ← 可以访问 Context
└── Footer

1.2 使用场景 #

  • 主题配置
  • 用户信息
  • 语言设置
  • 应用配置

二、createContext #

2.1 创建 Context #

jsx
import { createContext } from 'preact';

// 创建带默认值的 Context
const ThemeContext = createContext('light');
const UserContext = createContext(null);
const LanguageContext = createContext('zh-CN');

2.2 Provider 组件 #

jsx
function App() {
  const [theme, setTheme] = useState('dark');
  const [user, setUser] = useState({ name: 'Alice' });

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

2.3 嵌套 Provider #

jsx
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <UserContext.Provider value={{ name: 'Alice' }}>
        <LanguageContext.Provider value="zh-CN">
          <AppContent />
        </LanguageContext.Provider>
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

三、useContext #

3.1 基本用法 #

jsx
import { useContext } from 'preact';

function ThemedButton() {
  const theme = useContext(ThemeContext);
  
  return (
    <button class={`btn btn-${theme}`}>
      Themed Button
    </button>
  );
}

3.2 消费多个 Context #

jsx
function UserProfile() {
  const theme = useContext(ThemeContext);
  const { user } = useContext(UserContext);
  const language = useContext(LanguageContext);

  return (
    <div class={`profile profile-${theme}`}>
      <h1>{user.name}</h1>
      <p>Language: {language}</p>
    </div>
  );
}

3.3 更新 Context #

jsx
function App() {
  const [user, setUser] = useState(null);

  return (
    <UserContext.Provider value={{ user, setUser }}>
      <Header />
      <Main />
    </UserContext.Provider>
  );
}

function LoginButton() {
  const { user, setUser } = useContext(UserContext);

  if (user) {
    return (
      <div>
        <span>{user.name}</span>
        <button onClick={() => setUser(null)}>Logout</button>
      </div>
    );
  }

  return (
    <button onClick={() => setUser({ name: 'Alice' })}>
      Login
    </button>
  );
}

3.4 默认值 #

jsx
const ThemeContext = createContext('light');

// 当没有 Provider 时使用默认值
function ThemedButton() {
  const theme = useContext(ThemeContext);
  // 如果没有 Provider,theme 为 'light'
  return <button class={theme}>Button</button>;
}

四、useReducer #

4.1 基本概念 #

useReducer 是 useState 的替代方案,适合复杂状态逻辑。

jsx
const [state, dispatch] = useReducer(reducer, initialState);

4.2 基本用法 #

jsx
import { useReducer } from 'preact/hooks';

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');
  }
}

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' })}>Reset</button>
    </div>
  );
}

4.3 带 Payload 的 Action #

jsx
const initialState = {
  todos: []
};

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

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, initialState);
  const [input, setInput] = useState('');

  const addTodo = () => {
    if (input.trim()) {
      dispatch({ type: 'add', payload: input });
      setInput('');
    }
  };

  return (
    <div>
      <input
        value={input}
        onInput={(e) => setInput(e.target.value)}
        onKeyDown={(e) => e.key === 'Enter' && addTodo()}
      />
      <button onClick={addTodo}>Add</button>
      <ul>
        {state.todos.map(todo => (
          <li 
            key={todo.id}
            style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          >
            {todo.text}
            <button onClick={() => dispatch({ type: 'toggle', payload: todo.id })}>
              Toggle
            </button>
            <button onClick={() => dispatch({ type: 'delete', payload: todo.id })}>
              Delete
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

4.4 惰性初始化 #

jsx
function init(initialState) {
  return {
    ...initialState,
    initialized: true
  };
}

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

五、组合使用 #

5.1 Context + useReducer #

jsx
const TodoContext = createContext(null);

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

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

  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 App() {
  return (
    <TodoProvider>
      <TodoApp />
    </TodoProvider>
  );
}

function TodoList() {
  const { state, dispatch } = useTodo();

  return (
    <ul>
      {state.todos.map(todo => (
        <li key={todo.id}>
          {todo.text}
          <button onClick={() => dispatch({ type: 'toggle', payload: todo.id })}>
            Toggle
          </button>
        </li>
      ))}
    </ul>
  );
}

5.2 完整示例:购物车 #

jsx
const CartContext = createContext(null);

const initialState = {
  items: [],
  total: 0
};

function cartReducer(state, action) {
  switch (action.type) {
    case 'add': {
      const existingIndex = state.items.findIndex(
        item => item.id === action.payload.id
      );
      
      if (existingIndex >= 0) {
        const items = [...state.items];
        items[existingIndex] = {
          ...items[existingIndex],
          quantity: items[existingIndex].quantity + 1
        };
        return {
          items,
          total: state.total + action.payload.price
        };
      }
      
      return {
        items: [...state.items, { ...action.payload, quantity: 1 }],
        total: state.total + action.payload.price
      };
    }
    case 'remove': {
      const item = state.items.find(i => i.id === action.payload);
      return {
        items: state.items.filter(i => i.id !== action.payload),
        total: state.total - item.price * item.quantity
      };
    }
    case 'update_quantity': {
      const { id, quantity } = action.payload;
      const items = state.items.map(item => {
        if (item.id === id) {
          const diff = quantity - item.quantity;
          return { ...item, quantity };
        }
        return item;
      });
      
      const item = state.items.find(i => i.id === id);
      const diff = quantity - item.quantity;
      
      return {
        items,
        total: state.total + item.price * diff
      };
    }
    case 'clear':
      return initialState;
    default:
      return state;
  }
}

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

  const actions = {
    addItem: (item) => dispatch({ type: 'add', payload: item }),
    removeItem: (id) => dispatch({ type: 'remove', payload: id }),
    updateQuantity: (id, quantity) => 
      dispatch({ type: 'update_quantity', payload: { id, quantity } }),
    clearCart: () => dispatch({ type: 'clear' })
  };

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

function useCart() {
  return useContext(CartContext);
}

// 使用
function ProductList({ products }) {
  const { addItem } = useCart();

  return (
    <div>
      {products.map(product => (
        <div key={product.id}>
          <h3>{product.name}</h3>
          <p>${product.price}</p>
          <button onClick={() => addItem(product)}>
            Add to Cart
          </button>
        </div>
      ))}
    </div>
  );
}

function CartSummary() {
  const { state, removeItem, updateQuantity } = useCart();

  return (
    <div>
      <h2>Cart ({state.items.length} items)</h2>
      <ul>
        {state.items.map(item => (
          <li key={item.id}>
            {item.name} x {item.quantity}
            <button onClick={() => updateQuantity(item.id, item.quantity + 1)}>
              +
            </button>
            <button onClick={() => updateQuantity(item.id, item.quantity - 1)}>
              -
            </button>
            <button onClick={() => removeItem(item.id)}>
              Remove
            </button>
          </li>
        ))}
      </ul>
      <p>Total: ${state.total}</p>
    </div>
  );
}

六、最佳实践 #

6.1 拆分 Context #

jsx
// 分离状态和操作
const TodoStateContext = createContext(null);
const TodoDispatchContext = createContext(null);

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

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

function useTodoState() {
  return useContext(TodoStateContext);
}

function useTodoDispatch() {
  return useContext(TodoDispatchContext);
}

6.2 错误处理 #

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

6.3 性能优化 #

jsx
// 使用 useMemo 缓存 value
function TodoProvider({ children }) {
  const [state, dispatch] = useReducer(todoReducer, initialState);

  const value = useMemo(() => ({ state, dispatch }), [state]);

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

七、总结 #

要点 说明
createContext 创建 Context
Provider 提供数据
useContext 消费数据
useReducer 复杂状态管理
组合使用 全局状态管理

核心原则:

  • Context 用于跨组件共享数据
  • useReducer 用于复杂状态逻辑
  • 组合使用实现全局状态管理
  • 提供自定义 Hook 简化使用
最后更新:2026-03-28