useReduce 与 useContext

useReduceruseContext 是 React 中两个强大的 Hook,它们可以单独使用,也可以结合使用来管理复杂的状态。

  • useReducer 适合管理包含多个子值的服装状态逻辑
  • useContext 用于再组件树种共享数据,避免props的层层传递

useReducer

适合管理包含多个子值的复杂状态逻辑

基础语法

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

基础使用

import React, { useReducer } from "react";

// 定义 reducer 函数
function counterReducer(state, action) {
	switch (action.type) {
		case "increment":
			return { count: state.count + 1 };
		case "decrement":
			return { count: state.count - 1 };
		case "reset":
			return { count: 0 };
		default:
			return state;
	}
}

function Counter() {
	// 定义处理方法以及默认值
	const [state, dispatch] = useReducer(counterReducer, { count: 0 });

	return (
		<div>
			<p>Count: {state.count}</p>
			// 调用不同type 从而处理不同逻辑
			<button onClick={() => dispatch({ type: "increment" })}>+</button>
			<button onClick={() => dispatch({ type: "decrement" })}>-</button>
			<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
		</div>
	);
}

useContext

在组件树中共享数据,避免 props 层层传递

基础使用

import React, { createContext, useContext } from "react";

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

// 父组件提供 Context
function App() {
	return (
		<ThemeContext.Provider value='dark'>
			<Header />
		</ThemeContext.Provider>
	);
}

// 子组件消费 Context
function Header() {
	const theme = useContext(ThemeContext);
	return <h1 className={theme}>Hello World {theme}</h1>;
}

同样也可以给设置默认配置

import React, { createContext, useContext, useState } from "react";

const themeSetting = createContext({ theme: string, setTheme: (theme: Theme) => void } | undefined)(undefined);

// 创建自定义 hook
const useThemeHook = () => {
	const context = createContext(themeSetting);
	if (!context) {
		throw new Error("useAppContext must be used within AppProvider");
	}
	return context;
};

// 创建主题提供者组件
const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
	const { theme, setTheme } = useState("light");
	return (
		<themeSetting.Provider value={{ theme: theme, setTheme }}>
			{children}
		</themeSetting.Provider>
	);
};

// 主题切换组件
const ThemeChange = () => {
  const { theme, setTheme } = useThemeHook()
  return (
    <div>
      <div>当前主题: {theme}</div>
      <button onClick={() => setTheme('light')}>light</button>
      <button onClick={() => setTheme('dark')}>dark</button>
    </div>
  )
}

return (
  <ThemeProvider>
    <ThemeChange></ThemeChange>
  </ThemeProvider>
)

简单的主题色切换

useReduceruseContext配合来实现一套简单的主题色切换

import React, { createContext, useContext, useReducer } from "react";

// 1. 定义初始状态
const initialState = {
	user: null,
	theme: "light",
	notifications: [],
};

// 2. 定义 reducer
function appReducer(state, action) {
	switch (action.type) {
		case "SET_USER":
			return { ...state, user: action.payload };
		case "TOGGLE_THEME":
			return { ...state, theme: state.theme === "light" ? "dark" : "light" };
		case "ADD_NOTIFICATION":
			return {
				...state,
				notifications: [
					...state.notifications,
					{
						id: Date.now(),
						message: action.payload,
						timestamp: new Date().toLocaleTimeString(),
					},
				],
			};
		case "REMOVE_NOTIFICATION":
			return {
				...state,
				notifications: state.notifications.filter(
					(n) => n.id !== action.payload
				),
			};
		case "CLEAR_NOTIFICATIONS":
			return { ...state, notifications: [] };
		default:
			return state;
	}
}

// 3. 创建 Context
const AppContext = createContext();

// 4. 创建 Provider 组件
function AppProvider({ children }) {
	const [state, dispatch] = useReducer(appReducer, initialState);

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

// 5. 创建自定义 Hook 方便使用
function useAppContext() {
	const context = useContext(AppContext);
	if (!context) {
		throw new Error("useAppContext must be used within AppProvider");
	}
	return context;
}

// 6. 组件示例
function Header() {
	const { state, dispatch } = useAppContext();

	return (
		<header
			className={`p-4 border-b ${
				state.theme === "dark"
					? "bg-gray-800 text-white"
					: "bg-white text-black"
			}`}
		>
			<div className='flex justify-between items-center'>
				<h1 className='text-xl font-bold'>My App</h1>
				<div className='flex gap-2'>
					<button
						onClick={() => dispatch({ type: "TOGGLE_THEME" })}
						className='px-3 py-1 rounded bg-blue-500 text-white hover:bg-blue-600'
					>
						切换主题 ({state.theme})
					</button>
					{state.user ? (
						<span>Welcome, {state.user.name}!</span>
					) : (
						<button
							onClick={() =>
								dispatch({
									type: "SET_USER",
									payload: { name: "John Doe", id: 1 },
								})
							}
							className='px-3 py-1 rounded bg-green-500 text-white hover:bg-green-600'
						>
							登录
						</button>
					)}
				</div>
			</div>
		</header>
	);
}

function NotificationPanel() {
	const { state, dispatch } = useAppContext();

	return (
		<div
			className={`p-4 ${
				state.theme === "dark" ? "bg-gray-700" : "bg-gray-100"
			}`}
		>
			<div className='flex justify-between items-center mb-4'>
				<h2 className='text-lg font-semibold'>通知中心</h2>
				<div className='flex gap-2'>
					<button
						onClick={() =>
							dispatch({
								type: "ADD_NOTIFICATION",
								payload: `新消息 #${Math.floor(Math.random() * 1000)}`,
							})
						}
						className='px-3 py-1 rounded bg-blue-500 text-white hover:bg-blue-600 text-sm'
					>
						添加通知
					</button>
					{state.notifications.length > 0 && (
						<button
							onClick={() => dispatch({ type: "CLEAR_NOTIFICATIONS" })}
							className='px-3 py-1 rounded bg-red-500 text-white hover:bg-red-600 text-sm'
						>
							清空所有
						</button>
					)}
				</div>
			</div>

			<div className='space-y-2'>
				{state.notifications.length === 0 ? (
					<p className='text-gray-500'>暂无通知</p>
				) : (
					state.notifications.map((notification) => (
						<div
							key={notification.id}
							className={`p-3 rounded border flex justify-between items-center ${
								state.theme === "dark"
									? "bg-gray-600 border-gray-500"
									: "bg-white border-gray-300"
							}`}
						>
							<div>
								<p className='font-medium'>{notification.message}</p>
								<p className='text-sm text-gray-500'>
									{notification.timestamp}
								</p>
							</div>
							<button
								onClick={() =>
									dispatch({
										type: "REMOVE_NOTIFICATION",
										payload: notification.id,
									})
								}
								className='text-red-500 hover:text-red-700 text-sm'
							>
								删除
							</button>
						</div>
					))
				)}
			</div>
		</div>
	);
}

function UserProfile() {
	const { state, dispatch } = useAppContext();

	if (!state.user) {
		return (
			<div
				className={`p-4 ${
					state.theme === "dark" ? "bg-gray-700" : "bg-gray-100"
				}`}
			>
				<p>请先登录</p>
			</div>
		);
	}

	return (
		<div
			className={`p-4 ${
				state.theme === "dark" ? "bg-gray-700" : "bg-gray-100"
			}`}
		>
			<h2 className='text-lg font-semibold mb-2'>用户信息</h2>
			<p>姓名: {state.user.name}</p>
			<p>ID: {state.user.id}</p>
			<button
				onClick={() => dispatch({ type: "SET_USER", payload: null })}
				className='mt-2 px-3 py-1 rounded bg-red-500 text-white hover:bg-red-600'
			>
				退出登录
			</button>
		</div>
	);
}

// 7. 主应用组件
function App() {
	return (
		<AppProvider>
			<div className='min-h-screen'>
				<Header />
				<div className='grid grid-cols-1 md:grid-cols-2 gap-4 p-4'>
					<NotificationPanel />
					<UserProfile />
				</div>
			</div>
		</AppProvider>
	);
}

export default App;