React 高级 Hooks
本章将介绍 React 中的高级 Hooks,包括 useReducer、useLayoutEffect、useId、useTransition、useDeferredValue 和 useImperativeHandle。
1. useReducer:复杂状态管理
它是 useState 的替代方案,特别适合处理多个子状态或下一个状态依赖前一个状态的场景。
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 }; default: return state; } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <div style={{ textAlign: 'center', padding: '20px', border: '1px solid #ddd' }}> <h1>{state.count}</h1> <button onClick={() => dispatch({ type: 'decrement' })} style={{ marginRight: '5px' }}>-1</button> <button onClick={() => dispatch({ type: 'increment' })}>+1</button> </div> ); } render(<Counter />);
2. useTransition:非阻塞渲染 (React 18+)
允许将某些更新标记为非紧急,让用户界面在重负载下仍能保持响应。
function App() { const [isPending, startTransition] = useTransition(); const [input, setInput] = useState(""); const [list, setList] = useState([]); function handleChange(e) { const value = e.target.value; setInput(value); // 紧急更新 // 将非紧急更新包裹在 startTransition 中 startTransition(() => { const slowList = Array.from({ length: 5000 }, (_, i) => `${value} - Item ${i}`); setList(slowList); }); } return ( <div> <input value={input} onChange={handleChange} placeholder="输入内容感受差异..." /> {isPending ? ( <p>正在计算列表...</p> ) : ( <ul style={{ height: '150px', overflow: 'auto', opacity: 1 }}> {list.slice(0, 100).map((item, i) => <li key={i}>{item}</li>)} </ul> )} </div> ); }
3. useLayoutEffect:执行 DOM 测量
它在浏览器绘制屏幕之前同步执行,通常用于测量布局并同步重新渲染,以避免闪烁。
function LayoutDemo() { const [value, setValue] = useState(0); const ref = useRef(null); useLayoutEffect(() => { if (ref.current) { console.log("测量高度: ", ref.current.getBoundingClientRect().height); } }, [value]); return ( <div> <div ref={ref} style={{ height: `${value}px`, background: 'orange' }}> 高度: {value}px </div> <button onClick={() => setValue(v => v + 50)}>增加高度</button> </div> ); }
4. useDeferredValue:延迟更新
与 useTransition 类似,但它直接返回一个“延迟版”的值。
const List = React.memo(({ text }) => { // 模拟慢速渲染 const items = []; for (let i = 0; i < 2000; i++) { items.push(<li key={i}>{text} - 第 {i} 个内容</li>); } return <ul style={{ height: '100px', overflow: 'auto' }}>{items}</ul>; }); function App() { const [text, setText] = useState(""); const deferredText = useDeferredValue(text); return ( <div> <p>输入框能立即响应,因为列表渲染被推迟了:</p> <input value={text} onChange={e => setText(e.target.value)} /> <List text={deferredText} /> </div> ); } render(<App />);
5. React 19: useActionState (预览)
用于简化表单状态管理,自动处理 pending 和反馈。
// 在 React 19 环境中:
const [error, submitAction, isPending] = useActionState(
async (prevState, formData) => {
// 处理逻辑
return null;
},
null
);
6. React 19: use API
use 可以在渲染中读取资源(如 Promise 或 Context)。它是唯一可以在条件/循环语句中使用的 Hook。
function MessageContent({ messagePromise }) {
const message = use(messagePromise);
return <p>{message}</p>;
}
��据变更操作。它会自动处理待定状态和错误状态。
import { useActionState } from 'react';
function UpdateNameForm() {
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const name = formData.get('name');
// 调用 API 更新名字
const error = await updateName(name);
if (error) {
return error; // 返回错误信息
}
// 成功后跳转
redirect('/profile');
return null;
},
null // 初始状态
);
return (
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>
{isPending ? '更新中...' : '更新名字'}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}
参数说明:
- 第一个参数:Action 函数,接收
(previousState, payload)参数 - 第二个参数:初始状态值
- 第三个参数(可选):用于设置 action 的 permalink
返回值:
state:Action 返回的最后结果dispatch:用于触发 action 的函数isPending:是否处于待定状态
useOptimistic
useOptimistic 用于实现乐观更新,即在异步操作完成前就显示预期结果。
import { useOptimistic } from 'react';
function TodoList({ todos, onUpdateTodo }) {
const [optimisticTodos, setOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => {
// 乐观更新:立即显示新状态
return state.map(todo =>
todo.id === newTodo.id ? { ...todo, ...newTodo } : todo
);
}
);
async function updateTodo(id, completed) {
// 立即显示乐观更新
setOptimisticTodo({ id, completed });
// 发送请求
await updateTodoAPI(id, completed);
// 成功后更新实际数据
onUpdateTodo(id, completed);
}
return (
<ul>
{optimisticTodos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={(e) => updateTodo(todo.id, e.target.checked)}
/>
{todo.title}
</li>
))}
</ul>
);
}
使用场景:
- 点赞/收藏操作
- 待办事项状态切换
- 评论发送
- 任何需要即时反馈的用户操作
use API
use 是 React 19 新增的 API,用于在渲染过程中读取资源(Promise 或 Context)。
读取 Promise
import { use, Suspense } from 'react';
function Comments({ commentsPromise }) {
// use 会暂停直到 Promise 解析完成
const comments = use(commentsPromise);
return (
<ul>
{comments.map(comment => (
<li key={comment.id}>{comment.text}</li>
))}
</ul>
);
}
function Post({ postPromise }) {
return (
<Suspense fallback={<div>加载评论中...</div>}>
<Comments commentsPromise={postPromise.comments} />
</Suspense>
);
}
读取 Context
import { use } from 'react';
import ThemeContext from './ThemeContext';
function Heading({ children }) {
if (children == null) {
return null;
}
// use 可以在条件语句中调用(与 useContext 不同)
const theme = use(ThemeContext);
return (
<h1 style={{ color: theme.color }}>
{children}
</h1>
);
}
use 与 useContext 的区别:
useContext必须在组件顶层调用use可以在条件语句和循环中调用use可以读取 Promise,而useContext只能读取 Context
useFormStatus
useFormStatus 用于获取父级 <form> 的状态,常用于设计系统中的按钮组件。
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending, data, method, action } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? '提交中...' : '提交'}
</button>
);
}
function ContactForm() {
async function handleSubmit(formData) {
await sendEmail(formData);
}
return (
<form action={handleSubmit}>
<input name="email" type="email" required />
<textarea name="message" required />
<SubmitButton />
</form>
);
}
返回值:
pending:表单是否正在提交data:FormData 对象method:表单方法(get/post)action:表单的 action 属性值
React 19 其他改进
ref 作为 prop
React 19 中,ref 可以直接作为 prop 传递,不再需要 forwardRef:
// React 19 新写法
function Input({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />;
}
// 使用
<Input ref={inputRef} placeholder="输入内容" />
Context 简化
React 19 中可以直接使用 <Context> 作为提供者:
const ThemeContext = createContext('light');
// React 19 新写法
function App({ children }) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
ref 清理函数
React 19 支持在 ref 回调中返回清理函数:
<input
ref={(ref) => {
// ref 创建时
if (ref) {
ref.addEventListener('focus', handleFocus);
}
// 新特性:返回清理函数
return () => {
if (ref) {
ref.removeEventListener('focus', handleFocus);
}
};
}}
/>
文档元数据支持
React 19 支持在组件中渲染 <title>、<meta>、<link> 标签:
function BlogPost({ post }) {
return (
<article>
<title>{post.title}</title>
<meta name="description" content={post.excerpt} />
<meta name="keywords" content={post.keywords} />
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
完整示例:使用 React 19 新特性的表单
import { useActionState, useOptimistic } from 'react';
import { useFormStatus } from 'react-dom';
// 提交按钮组件
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? '保存中...' : '保存'}
</button>
);
}
// 用户资料表单
function UserProfile({ user }) {
const [optimisticName, setOptimisticName] = useOptimistic(user.name);
const [error, formAction] = useActionState(
async (prevState, formData) => {
const newName = formData.get('name');
// 乐观更新
setOptimisticName(newName);
try {
await updateUser(user.id, { name: newName });
return { success: true };
} catch (err) {
return { error: err.message };
}
},
null
);
return (
<form action={formAction}>
<div>
<label>用户名:</label>
<input
name="name"
defaultValue={optimisticName}
required
/>
</div>
<div>
<label>邮箱:</label>
<input
name="email"
type="email"
defaultValue={user.email}
required
/>
</div>
<SubmitButton />
{error?.error && <p className="error">{error.error}</p>}
{error?.success && <p className="success">保存成功!</p>}
</form>
);
}
Hooks 使用规则
在使用所有 Hooks 时,必须遵循以下规则:
1. 只在顶层调用
// ❌ 错误:在条件语句中调用
if (condition) {
const [value, setValue] = useState(0);
}
// ❌ 错误:在循环中调用
for (let i = 0; i < items.length; i++) {
const [value, setValue] = useState(items[i]);
}
// ✅ 正确:在组件顶层调用
function Component() {
const [value, setValue] = useState(0);
// ...
}
2. 只在 React 函数中调用
// ❌ 错误:在普通函数中调用
function handleClick() {
const [value, setValue] = useState(0); // 错误!
}
// ✅ 正确:在组件或自定义 Hook 中调用
function useCustomHook() {
const [value, setValue] = useState(0);
return value;
}
function Component() {
const value = useCustomHook();
// ...
}
3. use API 的特殊规则
use API 是例外,它可以在条件语句中调用:
function Component({ condition, promise, context }) {
if (condition) {
// ✅ use 可以在条件中调用
const value = use(promise);
const theme = use(context);
}
}
小结
本章我们学习了 React 中的高级 Hooks:
- useReducer:适合管理复杂状态逻辑,通过 action 描述状态更新
- useLayoutEffect:在浏览器绘制前执行,用于 DOM 测量
- useId:生成唯一 ID,用于无障碍属性
- useTransition:将状态更新标记为非阻塞,保持 UI 响应
- useDeferredValue:延迟值的更新,优化性能
- useImperativeHandle:自定义暴露给父组件的 ref 方法
练习
- 使用 useReducer 实现一个完整的待办事项列表(增删改查)
- 使用 useLayoutEffect 实现一个自适应位置的弹出菜单
- 使用 useId 创建一个包含多个表单字段的组件
- 使用 useTransition 或 useDeferredValue 优化搜索列表的输入体验
- 使用 useImperativeHandle 实现一个可由父组件控制的多步骤表单