
React 学习笔记:useEffectEvent
发布于: 2026-06-08 14:10:46 更新于: 2026-06-08 14:10:46
概述
useEffectEvent 是 React 19 引入的一个新 Hook,用于将 Effect 中的"事件处理逻辑"与"副作用逻辑"分离。它解决了一个常见问题:如何在 Effect 中读取最新的 props 或 state,而不导致 Effect 重新运行。
核心问题
在 Effect 中,我们经常需要使用组件的 props 或 state。但根据 React 的规则,Effect 的依赖数组必须包含所有使用的响应式值。这会导致一个问题:
// ❌ 问题:theme 变化会导致重新连接
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.on("connected", () => {
showNotification("Connected!", theme); // theme 在这里使用
});
connection.connect();
return () => connection.disconnect();
}, [roomId, theme]); // 必须包含 theme,导致 theme 变化时重新连接
}
但实际上,我们只希望在 roomId 变化时重新连接,theme 只是通知时需要用到。
解决方案:useEffectEvent
// ✅ 正确:theme 变化不会导致重新连接
function ChatRoom({ roomId, theme }) {
// 将使用 theme 的逻辑封装为 Effect Event
const onConnected = useEffectEvent(() => {
showNotification("Connected!", theme);
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on("connected", () => {
onConnected(); // 调用 Effect Event
});
connection.connect();
return () => connection.disconnect();
}, [roomId]); // 只依赖 roomId,theme 不需要加入
}
基本语法
import { useEffectEvent } from "react";
function MyComponent({ someProp }) {
// 创建 Effect Event
const onSomething = useEffectEvent((param) => {
// 这里可以安全地读取 someProp
console.log(someProp, param);
});
useEffect(() => {
// 在 Effect 中调用
onSomething("data");
}, []); // 不需要把 onSomething 加入依赖
}
核心特性
1. 总是读取最新值
Effect Event 内部的代码总是在"最新的渲染"上下文中执行,因此能读取到最新的 props 和 state。
2. 不是响应式的
Effect Event 本身不会触发 Effect 重新运行。把它从依赖数组中移除是安全的。
3. 只在 Effect 中调用
Effect Event 应该只在 Effect 内部调用,不应在事件处理器或渲染期间调用。
常见使用场景
场景 1:Effect 中需要读取非响应式数据
function Page({ url }) {
const [data, setData] = useState(null);
const onFetchComplete = useEffectEvent((response) => {
// 读取最新的 url,但不想 url 变化时重新 fetch
trackAnalytics(url, response);
});
useEffect(() => {
fetch(url).then((res) => {
setData(res);
onFetchComplete(res);
});
}, [url]); // 只在 url 变化时重新 fetch
}
场景 2:定时器需要最新值
function Counter({ incrementBy }) {
const [count, setCount] = useState(0);
// 自定义 Hook:useInterval
const onTick = useEffectEvent(() => {
setCount((c) => c + incrementBy);
});
useEffect(() => {
const id = setInterval(onTick, 1000);
return () => clearInterval(id);
}, []); // interval 不会因为 incrementBy 变化而重建
}
场景 3:事件监听器需要最新状态
function MouseTracker({ canMove }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const onMove = useEffectEvent((e) => {
if (canMove) {
setPosition({ x: e.clientX, y: e.clientY });
}
});
useEffect(() => {
window.addEventListener("pointermove", onMove);
return () => window.removeEventListener("pointermove", onMove);
}, []); // 事件监听器只注册一次
}
对比:useEffectEvent vs useCallback
| 特性 | useEffectEvent | useCallback |
|---|---|---|
| 主要用途 | 在 Effect 中读取最新值 | 缓存函数引用 |
| 是否响应式 | 否(不触发 Effect 重运行) | 是(依赖变化返回新函数) |
| 调用时机 | 只在 Effect 中调用 | 任何地方 |
| React 版本 | React 19+ | React 16.8+ |
注意事项
实验性 API:
useEffectEvent在 React 19 中正式引入,早期版本需要使用实验性频道(experimental channel)不要在渲染期间调用:Effect Event 只应在 Effect 回调中调用
命名约定:通常以
on开头命名,如onConnected、onTick、onMove不是替代 useCallback:两者解决不同问题,不要混用
总结
useEffectEvent 是一个强大的工具,用于解决 Effect 中"需要最新值但不想触发重运行"的矛盾。它让你能够:
- 将非响应式逻辑从 Effect 中分离
- 保持 Effect 的依赖数组干净
- 总是读取到最新的 props/state
标签分类
# React# 前端