更多 Hooks API
约 1639 字大约 5 分钟
布欧-Lewyon
2026-05-15
首页 › React › 进阶 Hooks › 更多 Hooks API
除了 useState、useEffect、useRef、useContext、useReducer、useMemo、useCallback 等主力 API,React 还提供了一批难度较高的内置 Hook,它们解决特定场景问题。下面逐一讲解。
useLayoutEffect
与 useEffect 的区别
useLayoutEffect 签名与 useEffect 完全一致,但执行时机不同:
useEffect | useLayoutEffect | |
|---|---|---|
| 执行时机 | 浏览器完成绘制后(异步) | DOM 变更后、浏览器绘制前(同步) |
| 阻塞渲染 | 不阻塞 | 会阻塞视觉更新 |
| 典型用途 | 数据请求、日志、非视觉副作用 | 测量 DOM、同步修改布局 |
function MeasureExample() {
const ref = useRef(null);
const [height, setHeight] = useState(0);
useLayoutEffect(() => {
// DOM 变化 → 执行 → 再让浏览器绘制
setHeight(ref.current.offsetHeight);
}, []); // 避免 useEffect 导致的"闪烁"
return (
<div>
<div ref={ref}>Hello</div>
<p>高度: {height}px</p>
</div>
);
}经验法则:能用
useEffect就用useEffect;只有遇到闪烁或需要同步读取布局时才用useLayoutEffect。SSR 中应避免使用,可以用useEffect兜底或用useIsomorphicLayoutEffect封装。
useEffect 场景的"闪烁"示例
如果直接量 DOM 宽高后用 useEffect 更新 state:
// ❌ 用 useEffect: 用户先看到旧布局 → React 计算 → 再次渲染 → 闪烁
useEffect(() => {
setWidth(el.offsetWidth);
}, []);
// ✅ 用 useLayoutEffect: DOM 变更 → 同步设置 state → 统一绘制
useLayoutEffect(() => {
setWidth(el.offsetWidth);
}, []);useInsertionEffect
React 18 新增,专门给 CSS-in-JS 库作者使用。执行时机比 useLayoutEffect 还早——在 DOM 变更之前。
// 普通项目几乎不用,styled-components / emotion 内部使用
useInsertionEffect(() => {
// 此时可以操作 <style> 标签或 inject CSS
const style = document.createElement('style');
style.textContent = '.dynamic { color: red }';
document.head.appendChild(style);
return () => style.remove();
}, []);顺序:
useInsertionEffect→ DOM 变更 →useLayoutEffect→ 浏览器绘制 →useEffect
useImperativeHandle
配合 forwardRef 使用,向父组件暴露自定义的实例方法,而非直接暴露整个 DOM 节点。
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
clear: () => { inputRef.current.value = ''; },
getValue: () => inputRef.current.value,
}), []);
return <input ref={inputRef} {...props} />;
});
// 父组件
function Parent() {
const ref = useRef(null);
return (
<>
<FancyInput ref={ref} />
<button onClick={() => ref.current.focus()}>聚焦</button>
<button onClick={() => alert(ref.current.getValue())}>取值</button>
</>
);
}适用场景:
- 封装第三方组件(Select、DatePicker)暴露统一 API
- 限制父组件只能调用特定方法,不暴露整颗 DOM 树
- 单元测试中 Mock
useDebugValue
用于在 React DevTools 中为自定义 Hook 显示标签,方便调试。
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useDebugValue(isOnline ? '🟢 在线' : '🔴 离线');
useEffect(() => {
const on = () => setIsOnline(true);
const off = () => setIsOnline(false);
window.addEventListener('online', on);
window.addEventListener('offline', off);
return () => {
window.removeEventListener('online', on);
window.removeEventListener('offline', off);
};
}, []);
return isOnline;
}打开 React DevTools → Components 面板,选中使用该 Hook 的组件,即可看到 useOnlineStatus: "🟢 在线" 的标签。
注意:仅在开发环境下生效。可以用
useDebugValue(value, fn)第二个参数做格式化延迟计算。
useId
React 18 引入,生成唯一且稳定的 ID,兼容服务端渲染(SSR)。
function FormField({ label }) {
const id = useId(); // 生成类似 ":R1ac:" 的 stable ID
return (
<>
<label htmlFor={id}>{label}</label>
<input id={id} {...props} />
</>
);
}为什么要用 useId 而非 Math.random()?
- SSR 中客户端和服务端必须生成一致 ID,否则会导致 hydration 不匹配
useId基于组件的 Fiber 树路径生成,不依赖自增计数器,避免并发模式下 ID 重叠
useSyncExternalStore
React 18 引入,帮助第三方状态库(非 React 生态)安全地订阅外部 store。React 内部推荐 Zustand、Redux 等库用此 API 替代手动 subscribe。
import { useSyncExternalStore } from 'react';
// 外部 store(可以是任何 observable)
const store = {
state: { count: 0 },
listeners: new Set(),
subscribe(callback) {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
},
getSnapshot() {
return this.state;
},
};function Counter() {
const { count } = useSyncExternalStore(
store.subscribe.bind(store), // subscribe
store.getSnapshot.bind(store) // getSnapshot
);
return <p>{count}</p>;
}核心目标:防止 tearing(撕裂)——在并发模式下,外部 store 更新与 React 渲染不同步导致 UI 不一致。
useTransition
React 18 并发模式 Hook,用于标记非紧急更新,让 UI 保持响应。
function SearchPage() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
function handleChange(e) {
// 紧急:更新输入框
setQuery(e.target.value);
// 非紧急:根据输入过滤大量列表
startTransition(() => {
setFilteredList(expensiveFilter(e.target.value));
});
}
return (
<>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<ResultList items={filteredList} />
</>
);
}isPending:过渡中为true,可显示加载提示startTransition:包裹的 state 更新被标记为"可中断",优先处理紧急更新
useDeferredValue
接受一个值,返回一个延迟版的值,在紧急更新之后才更新。与 useTransition 类似,但由值驱动而非函数。
function SearchPage({ query }) {
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
const list = useMemo(() => {
return expensiveFilter(deferredQuery);
}, [deferredQuery]);
return (
<>
<input value={query} onChange={...} />
<div style={{ opacity: isStale ? 0.5 : 1 }}>
<ResultList items={list} />
</div>
</>
);
}对比:
useTransition | useDeferredValue | |
|---|---|---|
| 控制对象 | state setter 函数 | 值本身 |
| 使用场景 | 有明确的 state 更新操作 | 想延迟"派生值"(props、从自定义 Hook 得到的值) |
| 语法 | startTransition(() => setState(...)) | const deferred = useDeferredValue(value) |
useOptimistic
React 19 新增,用于实现乐观更新(Optimistic Update)——先假设操作成功并直接更新 UI,后台请求失败再回滚。
import { useOptimistic } from 'react';
function MessageList({ messages, sendMessage }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages, // 原始状态
(state, newMessage) => [...state, { text: newMessage, sending: true }]
);
async function handleSend(formData) {
const text = formData.get('message');
addOptimisticMessage(text); // 立即显示,不用等网络
await sendMessage(text); // 真正的异步请求
}
return (
<form action={handleSend}>
<input name="message" />
<button type="submit">发送</button>
<ul>
{optimisticMessages.map((msg, i) => (
<li key={i}>
{msg.text}
{msg.sending && <em>(发送中...)</em>}
</li>
))}
</ul>
</form>
);
}useActionState
React 19 新增,将表单 state 和 form action 绑定,自动处理 pending 状态。
import { useActionState } from 'react';
async function submitForm(prevState, formData) {
const res = await fetch('/api/submit', { method: 'POST', body: formData });
if (!res.ok) return { error: '提交失败' };
return { success: true };
}
function MyForm() {
const [state, formAction, isPending] = useActionState(submitForm, { error: null });
return (
<form action={formAction}>
<input name="title" />
<button type="submit" disabled={isPending}>
{isPending ? '提交中...' : '提交'}
</button>
{state.error && <p style={{ color: 'red' }}>{state.error}</p>}
</form>
);
}use() — React 19 新资源读取 API
use() 可以在渲染期间直接读取 Promise 或 Context,打破 Hooks 规则的限制(可以在条件/循环中使用)。
import { use } from 'react';
function Comments({ commentsPromise }) {
// 直接在组件体中 await,触发 Suspense
const comments = use(commentsPromise);
return <ul>{comments.map(c => <li key={c.id}>{c.text}</li>)}</ul>;
}
// 父组件
function PostPage() {
const commentsPromise = fetchComments(postId);
return (
<Suspense fallback={<p>加载评论中...</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
);
}与传统方式对比:
useEffect + useState | use() | |
|---|---|---|
| 加载状态 | 手动管理 loading / error state | 自动触发 Suspense / ErrorBoundary |
| 竞态问题 | 需清理 Effect | 不存在 |
| 可嵌套条件 | 否(违反 Hooks 规则) | 是 |
小结
| Hook | 版本 | 主要用途 |
|---|---|---|
useLayoutEffect | 16.8 | DOM 测量、同步布局(防止闪烁) |
useInsertionEffect | 18 | CSS-in-JS 注入样式 |
useImperativeHandle | 16.8 | 自定义 ref 暴露方法 |
useDebugValue | 16.8 | DevTools 调试标签 |
useId | 18 | SSR 安全的唯一 ID |
useSyncExternalStore | 18 | 订阅外部 store(防撕裂) |
useTransition | 18 | 标记非紧急状态更新 |
useDeferredValue | 18 | 延迟派生值更新 |
useOptimistic | 19 | 乐观更新 |
useActionState | 19 | 表单状态 + Action |
use() | 19 | 渲染时读取 Promise / Context |
