React Hooks
本章将介绍 React Hooks,这是 React 16.8 引入的新特性,允许在函数组件中使用状态和其他 React 特性。Hooks 改变了我们编写 React 的方式,使得逻辑复用更加简单且直观。
什么是 Hooks?
Hooks 是让你在函数组件中"钩入" React 状态和生命周期等特性的函数。它们的名字通常以 use 开头,例如 useState、useEffect。
在 Hooks 出现之前,函数组件无法拥有状态,生命周期只能在类组件中使用。这导致组件逻辑分散在不同的生命周期方法中,难以复用和维护。Hooks 的出现解决了这些问题,让函数组件拥有了完整的能力。
Hooks 的核心准则
- 只在顶层调用 Hook:不要在循环、条件或嵌套函数中调用 Hook。React 依赖调用顺序来正确匹配状态,如果在条件语句中调用,顺序可能会改变。
- 只在 React 函数中调用 Hook:不要在普通 JavaScript 函数中调用(除自定义 Hook 之外)。
// ❌ 错误:在条件语句中调用 Hook
function Component({ shouldFetch }) {
if (shouldFetch) {
useEffect(() => { fetchData(); }, []); // 顺序不确定!
}
// ...
}
// ✅ 正确:始终在顶层调用
function Component({ shouldFetch }) {
useEffect(() => {
if (shouldFetch) {
fetchData();
}
}, [shouldFetch]);
// ...
}
useState:管理状态
useState 是最常用的 Hook,用于在函数组件中添加状态。它返回一个包含两个元素的数组:当前状态值和更新状态的函数。
基本用法
function Counter() { const [count, setCount] = useState(0); return ( <div style={{ padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}> <p>当前计数: <strong style={{ color: '#2563eb' }}>{count}</strong></p> <div style={{ display: 'flex', gap: '10px' }}> <button onClick={() => setCount(count + 1)} style={{ padding: '8px 16px', background: '#2563eb', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }} > 增加 </button> <button onClick={() => setCount(0)} style={{ padding: '8px 16px', background: '#ef4444', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }} > 重置 </button> </div> </div> ); }
函数式更新
当新状态依赖于之前的状态时,推荐传递一个函数给 set 函数。这可以避免在闭包中捕获旧的状态值。
// 推荐做法:即使在异步操作中也能获取最新状态
setCount(prevCount => prevCount + 1);
这种写法在处理多次连续更新时特别重要。React 会将更新排入队列,确保每次更新都基于最新的状态:
function MultipleUpdates() {
const [count, setCount] = useState(0);
const handleClick = () => {
// ❌ 错误:三个调用都是基于同一个旧值
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// 最终 count 只增加 1
// ✅ 正确:每个更新都基于前一个更新的结果
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// 最终 count 增加 3
};
}
惰性初始化
如果初始状态需要复杂计算,可以传递一个函数给 useState,它只在首次渲染时执行:
// ❌ 每次渲染都会调用 expensiveCalculation()
const [state, setState] = useState(expensiveCalculation());
// ✅ 只在首次渲染时调用
const [state, setState] = useState(() => expensiveCalculation());
更新对象和数组
状态应该是不可变的。更新对象或数组时,需要创建新的引用:
// 更新对象
const [user, setUser] = useState({ name: '张三', age: 25 });
setUser({ ...user, age: 26 }); // 创建新对象
// 更新数组
const [items, setItems] = useState([1, 2, 3]);
setItems([...items, 4]); // 添加元素
setItems(items.filter(item => item !== 2)); // 删除元素
useEffect:处理副作用
useEffect 用于处理副作用,如数据获取、订阅、手动修改 DOM 等。它在组件渲染完成后执行。
执行时机
useEffect 在浏览器完成绘制后异步执行,不会阻塞浏览器渲染。如果副作用需要同步执行(如测量 DOM),应使用 useLayoutEffect。
function Timer() { const [seconds, setSeconds] = useState(0); const [isActive, setIsActive] = useState(false); useEffect(() => { let interval = null; if (isActive) { interval = setInterval(() => { setSeconds(s => s + 1); }, 1000); } // 清理函数:组件卸载或依赖变化前执行 return () => clearInterval(interval); }, [isActive]); // 仅在 isActive 变化时重新运行 return ( <div style={{ padding: '10px' }}> <h3>计时器: {seconds} 秒</h3> <button onClick={() => setIsActive(!isActive)}> {isActive ? '暂停' : '开始'} </button> <button onClick={() => setSeconds(0)} style={{ marginLeft: '10px' }}> 重置 </button> </div> ); }
依赖数组的作用
依赖数组决定了 useEffect 何时重新执行:
// 空数组:只在挂载时执行一次
useEffect(() => {
console.log('组件挂载');
}, []);
// 有依赖:依赖变化时执行
useEffect(() => {
console.log('count 变化了:', count);
}, [count]);
// 无依赖数组:每次渲染都执行
useEffect(() => {
console.log('每次渲染都执行');
});
清理函数
当副作用涉及订阅、定时器等资源时,需要在清理函数中释放:
useEffect(() => {
const subscription = someAPI.subscribe();
return () => subscription.unsubscribe(); // 清理
}, []);
常见模式:数据获取
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false; // 防止竞态条件
async function fetchUser() {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (!cancelled) {
setUser(data);
}
} catch (err) {
if (!cancelled) {
setError(err);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
fetchUser();
return () => { cancelled = true; }; // 清理
}, [userId]); // userId 变化时重新获取
if (loading) return <div>加载中...</div>;
if (error) return <div>出错了</div>;
return <div>{user?.name}</div>;
}
useReducer:复杂状态管理
当状态逻辑复杂或涉及多个子值时,useReducer 比 useState 更合适。
function TodoApp() { const initialState = { todos: [], filter: 'all' }; function reducer(state, action) { switch (action.type) { case 'add': return { ...state, todos: [...state.todos, { id: Date.now(), text: action.text, done: false }] }; case 'toggle': return { ...state, todos: state.todos.map(todo => todo.id === action.id ? { ...todo, done: !todo.done } : todo ) }; case 'delete': return { ...state, todos: state.todos.filter(todo => todo.id !== action.id) }; case 'setFilter': return { ...state, filter: action.filter }; default: return state; } } const [state, dispatch] = useReducer(reducer, initialState); const [text, setText] = useState(''); const filteredTodos = state.todos.filter(todo => { if (state.filter === 'active') return !todo.done; if (state.filter === 'completed') return todo.done; return true; }); return ( <div style={{ padding: '10px' }}> <div> <input value={text} onChange={e => setText(e.target.value)} placeholder="添加待办..." /> <button onClick={() => { if (text.trim()) { dispatch({ type: 'add', text }); setText(''); } }}> 添加 </button> </div> <div> <button onClick={() => dispatch({ type: 'setFilter', filter: 'all' })}>全部</button> <button onClick={() => dispatch({ type: 'setFilter', filter: 'active' })}>未完成</button> <button onClick={() => dispatch({ type: 'setFilter', filter: 'completed' })}>已完成</button> </div> <ul> {filteredTodos.map(todo => ( <li key={todo.id} style={{ textDecoration: todo.done ? 'line-through' : 'none' }}> <span onClick={() => dispatch({ type: 'toggle', id: todo.id })}> {todo.text} </span> <button onClick={() => dispatch({ type: 'delete', id: todo.id })}>删除</button> </li> ))} </ul> </div> ); }
useReducer 适合以下场景:
- 状态包含多个子值
- 下一个状态依赖上一个状态
- 状态逻辑需要在组件外测试
useContext:跨组件传递数据
useContext 允许你订阅 React context 而不引入嵌套组件。它解决了"属性钻取"(prop drilling)问题。
// 1. 创建 Context const ThemeContext = React.createContext('light'); function App() { const [theme, setTheme] = useState('light'); return ( <ThemeContext.Provider value={theme}> <div style={{ padding: '20px', background: theme === 'dark' ? '#1e293b' : '#f8fafc', color: theme === 'dark' ? 'white' : 'black' }}> <Header /> <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}> 切换主题 </button> </div> </ThemeContext.Provider> ); } function Header() { // 2. 使用 Context const theme = useContext(ThemeContext); return <h4>当前主题: {theme}</h4>; }
React 19:简化的 Provider 语法
在 React 19 中,可以直接使用 <Context> 代替 <Context.Provider>:
// React 19 新语法
const ThemeContext = createContext('');
function App({ children }) {
return (
// 新语法:直接使用 Context 组件
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
// 旧语法仍然支持,但未来会被弃用
// <ThemeContext.Provider value="dark">
// {children}
// </ThemeContext.Provider>
迁移建议:React 官方提供了 codemod 工具来自动更新:
npx codemod@latest react/19/context-provider
配合 useReducer 实现全局状态
const StateContext = React.createContext();
const DispatchContext = React.createContext();
function AppProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
// 在子组件中使用
function TodoList() {
const state = useContext(StateContext);
const dispatch = useContext(DispatchContext);
// ...
}
useRef:引用值与 DOM
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。它有两个主要用途:
- 获取 DOM 元素。
- 存储不需要触发渲染的变量(类似类组件的实例属性)。
获取 DOM 元素
function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { // `current` 指向已挂载到 DOM 上的文本输入元素 inputEl.current.focus(); }; return ( <div style={{ padding: '10px' }}> <input ref={inputEl} type="text" placeholder="点击按钮聚焦" /> <button onClick={onButtonClick} style={{ marginLeft: '10px' }}> 聚焦输入框 </button> </div> ); }
存储可变值
useRef 可以存储任何可变值,且修改不会触发重新渲染:
function StopWatch() {
const [time, setTime] = useState(0);
const intervalRef = useRef(null); // 存储 interval ID
const start = () => {
if (intervalRef.current) return; // 防止重复启动
intervalRef.current = setInterval(() => {
setTime(t => t + 10);
}, 10);
};
const stop = () => {
clearInterval(intervalRef.current);
intervalRef.current = null;
};
const reset = () => {
stop();
setTime(0);
};
return (
<div>
<p>{(time / 1000).toFixed(2)} 秒</p>
<button onClick={start}>开始</button>
<button onClick={stop}>停止</button>
<button onClick={reset}>重置</button>
</div>
);
}
保存上一次的值
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<div>
<p>现在: {count}, 之前: {prevCount}</p>
<button onClick={() => setCount(c => c + 1)}>增加</button>
</div>
);
}
useLayoutEffect
useLayoutEffect 与 useEffect 类似,但它在浏览器绘制之前同步执行。适合需要测量 DOM 或同步更新布局的场景。
function Tooltip({ text, children }) {
const [position, setPosition] = useState({ top: 0, left: 0 });
const tooltipRef = useRef();
useLayoutEffect(() => {
// 在绘制前测量和设置位置,避免闪烁
const rect = tooltipRef.current.getBoundingClientRect();
setPosition({
top: window.innerHeight / 2 - rect.height / 2,
left: window.innerWidth / 2 - rect.width / 2
});
}, [text]);
return (
<div ref={tooltipRef} style={{ position: 'absolute', ...position }}>
{text}
</div>
);
}
大多数情况下应使用 useEffect,除非需要同步更新 DOM。
性能优化 Hooks
useMemo
缓存计算结果,只有在依赖项改变时才重新计算。适用于计算成本高的场景。
function ExpensiveList({ items, filter }) {
// 只有 items 或 filter 变化时才重新过滤
const filteredItems = useMemo(() => {
console.log('过滤计算中...');
return items.filter(item => item.includes(filter));
}, [items, filter]);
return (
<ul>
{filteredItems.map(item => <li key={item}>{item}</li>)}
</ul>
);
}
不要过度使用 useMemo,简单计算的缓存可能比重新计算更慢。
useCallback
缓存函数定义,当函数作为依赖或传递给子组件时很有用。
function Parent() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([]);
// ✅ 使用 useCallback 缓存,避免子组件不必要的重新渲染
const addItem = useCallback((item) => {
setItems(prev => [...prev, item]);
}, []); // 不依赖任何状态
return (
<div>
<button onClick={() => setCount(c => c + 1)}>计数: {count}</button>
<ItemList onAdd={addItem} />
</div>
);
}
// 使用 React.memo 配合 useCallback
const ItemList = React.memo(function ItemList({ onAdd }) {
console.log('ItemList 渲染');
return <button onClick={() => onAdd('item')}>添加</button>;
});
React 18/19 新 Hooks
useId (React 18)
生成唯一的 ID,通常用于无障碍辅助功能中的 aria 属性,确保服务端渲染和客户端渲染一致。
function PasswordField() { const id = useId(); return ( <div style={{ padding: '10px' }}> <label htmlFor={id + '-password'}>密码:</label> <input id={id + '-password'} type="password" /> </div> ); }
useTransition (React 18)
允许你将某些状态更新标记为"过渡",不会阻塞 UI 响应。适合处理可能导致界面卡顿的重渲染。
function SearchResults() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); const [isPending, startTransition] = useTransition(); const handleSearch = (e) => { setQuery(e.target.value); // 紧急更新:立即更新输入框 startTransition(() => { // 过渡更新:可以被中断 const filtered = heavyFilter(e.target.value); setResults(filtered); }); }; return ( <div> <input value={query} onChange={handleSearch} /> {isPending && <span>搜索中...</span>} <ul> {results.map(r => <li key={r}>{r}</li>)} </ul> </div> ); }
useDeferredValue (React 18)
延迟更新某个值,让 UI 能先响应用户输入。
function SearchPage({ query }) {
const deferredQuery = useDeferredValue(query);
// query 立即更新,deferredQuery 延迟更新
return <Results query={deferredQuery} />;
}
useActionState (React 19)
useActionState 是 React 19 新增的 Hook,用于简化表单处理和数据变更操作。它可以自动管理 pending 状态、错误处理和返回值,特别适合配合 Server Actions 使用。
基本语法:
const [state, formAction, isPending] = useActionState(fn, initialState, permalink?)
参数说明:
fn:点击提交按钮时要调用的函数。函数接收两个参数:上一次的 state 和表单数据initialState:state 的初始值permalink(可选):包含此表单唯一 URL 的字符串,用于渐进增强
返回值:
state:当前 state 值,初始为 initialState,之后是 action 返回的值formAction:传递给 form 的 action 属性的函数isPending:boolean,表示 action 是否正在执行
基础示例:
function UpdateNameForm() {
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const name = formData.get("name");
// 验证
if (!name || name.trim().length < 2) {
return "名字至少需要2个字符";
}
// 调用 API
const error = await updateName(name);
if (error) {
return error;
}
// 成功后跳转
redirect("/profile");
return null; // 成功时返回 null
},
null // 初始 state
);
return (
<form action={submitAction}>
<input type="text" name="name" required />
<button type="submit" disabled={isPending}>
{isPending ? "更新中..." : "更新名字"}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}
配合 Server Actions 使用:
// actions.js (Server Component)
"use server";
export async function updateUser(prevState, formData) {
const name = formData.get("name");
// 服务端验证
if (!name) {
return { error: "名字不能为空", success: false };
}
try {
await db.users.update({ name });
return { error: null, success: true };
} catch (err) {
return { error: "更新失败", success: false };
}
}
// UserForm.jsx (Client Component)
"use client";
import { useActionState } from "react";
import { updateUser } from "./actions";
export function UserForm() {
const [state, formAction, isPending] = useActionState(updateUser, {
error: null,
success: false
});
return (
<form action={formAction}>
<input name="name" type="text" />
<button disabled={isPending}>
{isPending ? "保存中..." : "保存"}
</button>
{state.error && <p className="error">{state.error}</p>}
{state.success && <p className="success">更新成功!</p>}
</form>
);
}
useFormStatus (React 19)
useFormStatus 是 React 19 新增的 Hook,用于在表单提交过程中获取状态信息。它必须在 <form> 内部的组件中使用,返回父级 <form> 的状态。
基本语法:
const { pending, data, method, action } = useFormStatus();
返回值:
pending:boolean,表单是否正在提交data:FormData 对象,包含表单数据(提交时才有值)method:字符串,表单提交方法("get" 或 "post")action:表单的 action 属性值
使用场景:useFormStatus 特别适合在设计系统中使用,可以让按钮组件感知表单状态而不需要 props 传递。
示例:禁用提交按钮:
import { useFormStatus } from "react-dom";
// 设计系统按钮组件
function SubmitButton({ children, className }) {
const { pending } = useFormStatus();
return (
<button
type="submit"
disabled={pending}
className={className}
>
{pending ? "提交中..." : children}
</button>
);
}
// 使用
function ContactForm() {
async function handleSubmit(formData) {
await sendMessage(formData.get("message"));
}
return (
<form action={handleSubmit}>
<textarea name="message" required />
<SubmitButton>发送消息</SubmitButton>
</form>
);
}
示例:显示提交数据:
function FormStatusDisplay() {
const { pending, data, method } = useFormStatus();
if (!pending) return null;
return (
<div className="status-bar">
正在{method === "post" ? "提交" : "获取"}数据...
{data && (
<span>({data.get("name")})</span>
)}
</div>
);
}
重要说明:
useFormStatus必须在<form>内部的组件中调用,否则返回的 pending 始终为 false- 它实际上是读取父级
<form>提供的 Context,所以需要作为子组件使用
useOptimistic (React 19)
useOptimistic 用于实现乐观更新——在异步操作完成前就显示预期结果,提升用户体验。当操作失败时,会自动回滚到原始状态。
基本语法:
const [optimisticState, addOptimistic] = useOptimistic(actualState, updateFn);
参数说明:
actualState:真实的状态值updateFn:计算乐观状态的函数,接收 (currentState, optimisticValue) 并返回新的乐观状态
返回值:
optimisticState:乐观状态(操作进行中时显示乐观值,否则显示真实值)addOptimistic:触发乐观更新的函数
示例:消息发送:
import { useOptimistic } from "react";
function MessageThread({ messages, sendMessage }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [
...state,
{
id: "temp-" + Date.now(),
text: newMessage,
sending: true // 标记为发送中
}
]
);
async function handleSubmit(formData) {
const text = formData.get("message");
// 立即显示乐观消息
addOptimisticMessage(text);
// 发送到服务器
await sendMessage(text);
// 成功后,messages 会更新,乐观消息被替换
}
return (
<div>
<div className="messages">
{optimisticMessages.map((msg) => (
<div
key={msg.id}
className={msg.sending ? "message sending" : "message"}
>
{msg.text}
{msg.sending && <span className="spinner">...</span>}
</div>
))}
</div>
<form action={handleSubmit}>
<input name="message" placeholder="输入消息..." required />
<button type="submit">发送</button>
</form>
</div>
);
}
示例:点赞功能:
function LikeButton({ postId, initialLikes, isLiked }) {
const [likes, setLikes] = useState(initialLikes);
const [liked, setLiked] = useState(isLiked);
const [optimisticLikes, addLike] = useOptimistic(
likes,
(currentLikes, increment) => currentLikes + increment
);
async function handleLike() {
// 乐观更新:立即显示点赞效果
const increment = liked ? -1 : 1;
addLike(increment);
try {
await toggleLike(postId);
setLiked(!liked);
setLikes(prev => prev + increment);
} catch (error) {
// 失败时自动回滚到 optimisticLikes 之前的值
console.error("点赞失败", error);
}
}
return (
<button onClick={handleLike} className={liked ? "liked" : ""}>
❤️ {optimisticLikes}
</button>
);
}
use API (React 19)
use 是 React 19 引入的新 API,用于在渲染过程中读取 Promise 或 Context 的值。与 Hooks 不同,use 可以在条件语句和循环中调用。
基本语法:
import { use } from "react";
const value = use(resource);
参数:
resource:要读取的资源,可以是 Promise 或 Context
返回值:
- Promise 解析后的值,或 Context 的当前值
读取 Promise:
use 可以直接读取 Promise,并与 Suspense 配合使用:
import { use, Suspense } from "react";
// 获取数据的函数返回 Promise
function fetchUser(id) {
return fetch(`/api/users/${id}`).then(res => res.json());
}
function UserProfile({ userPromise }) {
// use 会"挂起"组件直到 Promise 解析完成
const user = use(userPromise);
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
function UserPage({ userId }) {
const userPromise = fetchUser(userId); // 在父组件创建 Promise
return (
<Suspense fallback={<div>加载中...</div>}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}
错误处理:
当 Promise 被 reject 时,可以使用 Error Boundary 捕获:
import { ErrorBoundary } from "react-error-boundary";
function UserPage({ userId }) {
return (
<ErrorBoundary fallback={<div>加载失败</div>}>
<Suspense fallback={<div>加载中...</div>}>
<UserProfile userPromise={fetchUser(userId)} />
</Suspense>
</ErrorBoundary>
);
}
读取 Context:
use 也可以读取 Context,与 useContext 的区别是 use 可以在条件语句中调用:
import { use, createContext } from "react";
const ThemeContext = createContext("light");
function ThemedButton({ show }) {
// useContext 不允许在条件语句中调用
// 但 use 可以!
if (show) {
const theme = use(ThemeContext);
return <button className={theme}>主题按钮</button>;
}
return null;
}
与 useContext 的对比:
| 特性 | useContext | use |
|---|---|---|
| 调用位置 | 必须在顶层 | 可以在条件语句、循环中 |
| 类型 | Hook | API(不是 Hook) |
| 推荐使用 | - | ✅ 官方推荐优先使用 use |
服务器组件与客户端组件协作:
use 特别适合在服务端获取数据、客户端消费的场景:
// page.jsx (Server Component)
import { fetchComments } from "./api";
import { Comments } from "./Comments";
export default function Page() {
// 服务端创建 Promise
const commentsPromise = fetchComments();
// 传递给客户端组件
return <Comments commentsPromise={commentsPromise} />;
}
// Comments.jsx (Client Component)
"use client";
import { use, Suspense } from "react";
export function Comments({ commentsPromise }) {
// 客户端使用 use 读取
const comments = use(commentsPromise);
return (
<ul>
{comments.map(c => (
<li key={c.id}>{c.text}</li>
))}
</ul>
);
}
注意事项:
use必须在组件或 Hook 中调用- 服务器组件中数据获取应优先使用
async/await - 客户端组件中创建的 Promise 每次渲染都会重新创建,应从服务端传递
自定义 Hook
自定义 Hook 是将逻辑提取到可重用函数中的方式。必须以 use 开头,这样 React 能检查它是否遵循 Hook 规则。
窗口大小 Hook
function useWindowSize() { const [size, setSize] = useState({ width: 0, height: 0 }); useEffect(() => { const handleResize = () => setSize({ width: window.innerWidth, height: window.innerHeight }); window.addEventListener('resize', handleResize); handleResize(); return () => window.removeEventListener('resize', handleResize); }, []); return size; } function DisplaySize() { const { width, height } = useWindowSize(); return ( <div style={{ padding: '10px', background: '#f0fdf4', borderRadius: '4px' }}> <p style={{ margin: 0 }}>窗口大小: <strong>{width} x {height}</strong></p> <small style={{ color: '#666' }}>尝试调整浏览器窗口大小看看</small> </div> ); } render(<DisplaySize />);
本地存储 Hook
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// 使用
function App() {
const [name, setName] = useLocalStorage('name', '张三');
return <input value={name} onChange={e => setName(e.target.value)} />;
}
防抖 Hook
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
// 使用:搜索框防抖
function SearchInput() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearchTerm) {
// 执行搜索
fetchResults(debouncedSearchTerm);
}
}, [debouncedSearchTerm]);
return <input value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />;
}
Hooks 速查表
| Hook | 用途 | 常见场景 |
|---|---|---|
useState | 管理状态 | 表单输入、切换开关、计数器 |
useEffect | 副作用 | 数据获取、订阅、DOM 操作 |
useReducer | 复杂状态 | 表单状态、复杂状态转换 |
useContext | 全局数据 | 主题、用户信息、语言设置 |
useRef | DOM 引用/可变值 | 焦点管理、定时器 ID、前值 |
useMemo | 缓存计算 | 列表过滤、复杂计算 |
useCallback | 缓存函数 | 传递给优化的子组件 |
useLayoutEffect | 同步副作用 | DOM 测量、布局更新 |
useId | 唯一 ID | 表单关联、无障碍属性 |
useTransition | 非紧急更新 | 搜索结果、列表过滤 |
useDeferredValue | 延迟值 | 搜索防抖 |
useActionState (19) | 表单动作状态 | 表单提交、数据变更 |
useFormStatus (19) | 表单提交状态 | 提交按钮禁用、加载指示 |
useOptimistic (19) | 乐观更新 | 点赞、消息发送 |
use (19) | 读取资源 | Promise、Context |
小结
- useState:管理本地状态,支持函数式更新和惰性初始化。
- useEffect:处理副作用,通过依赖数组控制执行时机。
- useReducer:管理复杂状态逻辑,适合状态有多个子值的场景。
- useContext:避免属性钻取,共享全局数据。
- useRef:获取 DOM 元素,存储不触发渲染的可变值。
- useMemo/useCallback:性能优化,缓存计算结果/函数。
- useId/useTransition:React 18 新增功能。
- useActionState/useOptimistic:React 19 新增功能。
练习
- 使用
useRef实现一个"点击外部关闭弹窗"的功能。 - 使用
useMemo优化一个经过大量数据过滤的列表组件。 - 创建一个自定义 Hook
useLocalStorage来同步状态到本地存储。 - 使用
useReducer实现一个购物车功能(添加、删除、修改数量)。 - 创建一个
useFetch自定义 Hook,处理加载状态和错误。