关于 no-unnecessary-effects
面向 React 开发的实践指南,帮助判断何时使用 useEffect,并为常见场景提供替代方案。重点是将效果限制在与外部系统同步的场合。
主要功能
- 01判断 useEffect 是否必要
- 02派生数据直接在渲染中计算
- 03将用户操作逻辑放入事件处理器
- 04提供外部订阅的清理建议
适用场景
- 01避免用 useEffect 保存派生状态
- 02处理点击或表单提交逻辑
- 03连接 WebSocket 或第三方组件
- 04订阅浏览器 API 或测量 DOM
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ cst2989/ react-tips-skill --skill no-unnecessary-effects安装后在 Claude Code 中输入/no-unnecessary-effects确认已加载。
详细介绍
编写 useEffect 前
每次准备编写 useEffect 时,都先停下来回答这个问题:
这是否是在与外部系统同步?
外部系统:WebSocket、浏览器 API(IntersectionObserver、navigator.onLine)、第三方库(地图 SDK、图表组件)、DOM 测量、setInterval 定时器。
非外部系统:props、state、根据 props 或 state 派生的值、用户事件(点击、表单提交)。
如果答案是否定的,就不要编写 effect。使用下面的决策树找到合适的替代方案。
决策树
编写 effect 前,按顺序检查以下情况:
1. 我是在转换或派生数据吗?
直接在代码中计算。不用 state,也不用 effect。
// NEVER do this
const [filtered, setFiltered] = useState([]);
useEffect(() => {
setFiltered(data.filter(item => item.active));
}, [data]);
// DO this
const filtered = data.filter(item => item.active);
// If genuinely expensive and not using React Compiler:
const filtered = useMemo(() => data.filter(item => item.active), [data]);
2. 我是在响应用户事件吗?
把逻辑放在事件处理函数中。Effect 响应的是渲染,而不是用户操作。
// NEVER do this
useEffect(() => {
if (submitted) {
performSearch(query);
setSubmitted(false);
}
}, [submitted, query]);
// DO this
function handleSubmit(e: FormEvent) {
e.preventDefault();
performSearch(query);
}
3. 我是在 prop 变化时重置 state 吗?
使用 key prop,让 React 卸载并重新挂载组件,从而获得新的 state。
// NEVER do this
useEffect(() => {
setComment('');
}, [userId]);
// DO this — in the parent
<UserProfile key={userId} userId={userId} />
4. 我是在获取数据吗?
使用 TanStack Query(React Query)或类似的库。如果必须使用 useEffect,务必添加清理逻辑,以忽略过时的响应。
// Preferred
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
// If useEffect is unavoidable, always handle race conditions
useEffect(() => {
let ignore = false;
fetchUser(userId).then(data => {
if (!ignore) setUser(data);
});
return () => { ignore = true; };
}, [userId]);
5. 我是在通知父组件吗?
在事件处理函数中直接调用父组件的回调,同时调用 setState。React 会将两次更新批处理为一次渲染。
// NEVER do this
useEffect(() => {
onChange(isOn);
}, [isOn, onChange]);
// DO this
function handleClick() {
const next = !isOn;
setIsOn(next);
onChange(next);
}
6. 我是在串联多个 effect 吗?
将这一连串操作移到单个事件处理函数中。能在渲染期间派生的数据就直接派生。
// NEVER do this — three effects, three render passes
useEffect(() => { setCity(''); }, [country]);
useEffect(() => { setDistrict(''); }, [city]);
useEffect(() => { setShippingCost(calculate(country, city, district)); }, [country, city, district]);
// DO this
function handleCountryChange(newCountry: string) {
setCountry(newCountry);
setCity('');
setDistrict('');
}
const shippingCost = country && city && district
? calculateShipping(country, city, district)
: 0;
7. 我是在订阅外部 store 吗?
使用 useSyncExternalStore,而不是通过 useEffect 手动设置监听器。
// NEVER do this
useEffect(() => {
const handler = () => setIsOnline(navigator.onLine);
window.addEventListener('online', handler);
window.addEventListener('offline', handler);
return () => {
window.removeEventListener('online', handler);
window.removeEventListener('offline', handler);
};
}, []);
// DO this
function subscribe(callback: () => void) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
const isOnline = useSyncExternalStore(subscribe, () => navigator.onLine, () => true);
适合使用 useEffect 的情况
如果以上情况都不适用,而且对于“这是否是外部系统?”的回答确实是肯定的,那么 useEffect 就是合适的工具。例如:
- WebSocket 连接(挂载时打开,卸载时关闭)
- 初始化第三方组件(地图 SDK、富文本编辑器)
- DOM 测量(绘制前使用
useLayoutEffect,绘制后使用useEffect) - 订阅浏览器 API,并添加清理逻辑(
IntersectionObserver、ResizeObserver)
编写有效的 effect 时:
- 为函数命名,提高可读性:
useEffect(function connectToChat() { ... }) - 订阅或建立连接时,务必返回清理函数
- 列出 effect 读取的所有依赖项
- 测量 DOM 时使用
useLayoutEffect,以避免视觉闪烁
规则
- 绝不要编写
useEffect(() => setSomething(derivedValue), [dep])——直接在代码中计算 - 绝不要使用
useEffect响应点击、提交或更改事件 - 绝不要使用
useEffect在 prop 变化时重置 state,而不先考虑keyprop - 绝不要串联多个 effect,让一个 effect 设置的 state 触发另一个 effect
- 订阅外部系统时,务必添加清理函数
- 务必为 effect 函数命名,以提高可读性