dont-use-use-effect
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDon't Use useEffect
不要使用useEffect
useEffectuseEffectThe golden rule: Effects are for synchronizing with external systems (DOM APIs, timers, websockets, third-party widgets). If the work you're doing is a response to a user event, or can be calculated from existing state/props, you don't need an Effect.
useEffectuseEffect黄金准则: Effects仅用于与外部系统同步(DOM API、定时器、WebSocket、第三方组件)。如果你要处理的是用户事件的响应,或者可以通过现有state/props计算得到的内容,那么你不需要Effect。
1. Derived State — Just Calculate It
1. 派生状态——直接计算即可
The most common anti-pattern: storing a value in state that can be computed from other state or props.
useEffecttsx
// 🔴 BAD: Redundant state + unnecessary Effect
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
return <span>{fullName}</span>;
}This causes two renders every time or changes: one with the stale , and another after the Effect updates it.
firstNamelastNamefullNametsx
// ✅ GOOD: Calculate during render
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// Derived — no state, no Effect, one render
const fullName = firstName + ' ' + lastName;
return <span>{fullName}</span>;
}For expensive calculations, wrap in instead of storing in state:
useMemotsx
// ✅ GOOD: Memoize expensive derivations
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
// Only recomputes when todos or filter change, not when newTodo changes
const visibleTodos = useMemo(
() => getFilteredTodos(todos, filter),
[todos, filter]
);
return <ul>{visibleTodos.map(todo => <li key={todo.id}>{todo.text}</li>)}</ul>;
}最常见的反模式:将可以通过其他state或props计算得到的值存储为状态。
useEffecttsx
// 🔴 错误:冗余状态 + 不必要的Effect
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
return <span>{fullName}</span>;
}这会导致每次或变化时触发两次渲染:一次使用过时的,另一次在Effect更新它之后。
firstNamelastNamefullNametsx
// ✅ 正确:在渲染时直接计算
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// 派生值——无需状态,无需Effect,仅一次渲染
const fullName = firstName + ' ' + lastName;
return <span>{fullName}</span>;
}对于计算成本较高的场景,使用包裹而非存储为状态:
useMemotsx
// ✅ 正确:对开销大的派生值进行记忆化
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
// 仅在todos或filter变化时重新计算,newTodo变化时不会触发
const visibleTodos = useMemo(
() => getFilteredTodos(todos, filter),
[todos, filter]
);
return <ul>{visibleTodos.map(todo => <li key={todo.id}>{todo.text}</li>)}</ul>;
}2. Event-Driven Logic — Use Event Handlers
2. 事件驱动逻辑——使用事件处理函数
If code should run because the user did something (clicked, submitted, typed), it belongs in an event handler — not an Effect. Effects run because a component rendered, not because the user took an action.
tsx
// 🔴 BAD: Event-specific logic triggered by state change in an Effect
function ProductPage({ product, addToCart }) {
useEffect(() => {
if (product.isInCart) {
showNotification(`Added ${product.name} to the cart!`);
}
}, [product]);
function handleBuyClick() {
addToCart(product);
}
// ...
}This is buggy: the notification fires on page reload if the product is already in the cart.
tsx
// ✅ GOOD: Side effects belong in the event handler that caused them
function ProductPage({ product, addToCart }) {
function buyProduct() {
addToCart(product);
showNotification(`Added ${product.name} to the cart!`);
}
function handleBuyClick() {
buyProduct();
}
function handleCheckoutClick() {
buyProduct();
navigateTo('/checkout');
}
// ...
}Rule of thumb: If you can point to the exact user interaction that should trigger the code, put it in that interaction's event handler.
如果代码是响应用户操作(点击、提交、输入)而执行的,那么它应该放在事件处理函数中——而不是Effect里。Effects是因为组件完成渲染而执行,而非用户触发了某个操作。
tsx
// 🔴 错误:通过Effect监听state变化触发事件专属逻辑
function ProductPage({ product, addToCart }) {
useEffect(() => {
if (product.isInCart) {
showNotification(`Added ${product.name} to the cart!`);
}
}, [product]);
function handleBuyClick() {
addToCart(product);
}
// ...
}这种写法存在bug:如果页面加载时商品已经在购物车中,通知会错误触发。
tsx
// ✅ 正确:副作用应放在触发它的事件处理函数中
function ProductPage({ product, addToCart }) {
function buyProduct() {
addToCart(product);
showNotification(`Added ${product.name} to the cart!`);
}
function handleBuyClick() {
buyProduct();
}
function handleCheckoutClick() {
buyProduct();
navigateTo('/checkout');
}
// ...
}经验法则: 如果你能明确指出触发代码执行的用户交互动作,就把代码放在该交互的事件处理函数中。
3. Effect Chains — Consolidate Into Event Handlers
3. Effect链式调用——合并到事件处理函数中
Chaining Effects that each set state based on other state creates a cascade of unnecessary re-renders and rigid, fragile code.
tsx
// 🔴 BAD: Chain of Effects triggering each other (4 render passes!)
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
const [isGameOver, setIsGameOver] = useState(false);
useEffect(() => {
if (card !== null && card.gold) {
setGoldCardCount(c => c + 1);
}
}, [card]);
useEffect(() => {
if (goldCardCount > 3) {
setRound(r => r + 1);
setGoldCardCount(0);
}
}, [goldCardCount]);
useEffect(() => {
if (round > 5) setIsGameOver(true);
}, [round]);
// ...
}tsx
// ✅ GOOD: Derive what you can, compute the rest in the event handler
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
// Derived — no state needed
const isGameOver = round > 5;
function handlePlaceCard(nextCard) {
if (isGameOver) throw Error('Game already ended.');
setCard(nextCard);
if (nextCard.gold) {
if (goldCardCount < 3) {
setGoldCardCount(goldCardCount + 1);
} else {
setGoldCardCount(0);
setRound(round + 1);
if (round === 5) alert('Good game!');
}
}
}
// ...
}多个Effects通过设置state互相触发,会导致不必要的渲染级联,同时让代码变得僵化且脆弱。
tsx
// 🔴 错误:Effects互相触发的链式调用(多达4次渲染!)
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
const [isGameOver, setIsGameOver] = useState(false);
useEffect(() => {
if (card !== null && card.gold) {
setGoldCardCount(c => c + 1);
}
}, [card]);
useEffect(() => {
if (goldCardCount > 3) {
setRound(r => r + 1);
setGoldCardCount(0);
}
}, [goldCardCount]);
useEffect(() => {
if (round > 5) setIsGameOver(true);
}, [round]);
// ...
}tsx
// ✅ 正确:尽可能派生值,其余逻辑合并到事件处理函数中
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
// 派生值——无需状态
const isGameOver = round > 5;
function handlePlaceCard(nextCard) {
if (isGameOver) throw Error('Game already ended.');
setCard(nextCard);
if (nextCard.gold) {
if (goldCardCount < 3) {
setGoldCardCount(goldCardCount + 1);
} else {
setGoldCardCount(0);
setRound(round + 1);
if (round === 5) alert('Good game!');
}
}
}
// ...
}4. Data Fetching — Use a Data-Fetching Library
4. 数据获取——使用数据获取库
Fetching data in is fragile. You must manually handle race conditions, loading states, caching, error handling, and cleanup. Raw fetching has no caching, no deduplication, and no SSR support.
useEffectuseEffecttsx
// 🔴 BAD: Manual fetch in useEffect — race conditions, no caching, no SSR
function SearchResults({ query }) {
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
let ignore = false;
setIsLoading(true);
fetchResults(query).then(json => {
if (!ignore) {
setResults(json);
setIsLoading(false);
}
});
return () => { ignore = true; };
}, [query]);
// ...
}tsx
// ✅ GOOD: Use TanStack Query (or your framework's data primitive)
import { useQuery } from '@tanstack/react-query';
function SearchResults({ query }) {
const { data: results = [], isLoading } = useQuery({
queryKey: ['search', query],
queryFn: () => fetchResults(query),
enabled: !!query,
});
// Automatic caching, deduplication, race-condition handling,
// background refetching, and SSR support — for free.
}Other good alternatives:
- Next.js / Remix: functions, Server Components,
loader+ Suspenseuse() - SWR: — similar to TanStack Query
useSWR(key, fetcher) - tRPC: End-to-end type-safe fetching with built-in React Query integration
If you must use for fetching (no framework, no library), at minimum:
useEffect- Add the cleanup flag to prevent race conditions
ignore - Extract the logic into a reusable custom hook (e.g. )
useData(url) - Handle loading, error, and empty states explicitly
在中手动获取数据是脆弱的。你必须手动处理竞态条件、加载状态、缓存、错误处理和清理逻辑。原生获取数据没有缓存、没有请求去重、不支持SSR。
useEffectuseEffecttsx
// 🔴 错误:在useEffect中手动获取数据——存在竞态条件、无缓存、不支持SSR
function SearchResults({ query }) {
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
let ignore = false;
setIsLoading(true);
fetchResults(query).then(json => {
if (!ignore) {
setResults(json);
setIsLoading(false);
}
});
return () => { ignore = true; };
}, [query]);
// ...
}tsx
// ✅ 正确:使用TanStack Query(或框架自带的数据原语)
import { useQuery } from '@tanstack/react-query';
function SearchResults({ query }) {
const { data: results = [], isLoading } = useQuery({
queryKey: ['search', query],
queryFn: () => fetchResults(query),
enabled: !!query,
});
// 自动缓存、请求去重、竞态条件处理、
// 后台重新获取、SSR支持——全部免费获得。
}其他优秀替代方案:
- Next.js / Remix: 函数、Server Components、
loader+ Suspenseuse() - SWR: ——与TanStack Query类似
useSWR(key, fetcher) - tRPC: 端到端类型安全的获取方案,内置React Query集成
如果你必须使用获取数据(无框架、无库),至少要做到:
useEffect- 添加清理标志以防止竞态条件
ignore - 将逻辑提取为可复用的自定义Hook(如)
useData(url) - 显式处理加载、错误和空状态
5. External Store Subscriptions — Use useSyncExternalStore
useSyncExternalStore5. 外部存储订阅——使用useSyncExternalStore
useSyncExternalStoreSubscribing to an external data source (browser API, third-party store, observable) using + is both boilerplate-heavy and can cause tearing in concurrent mode.
useEffectsetStatetsx
// 🔴 BAD: Manual subscription with useEffect
function OnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return <span>{isOnline ? '✅ Online' : '❌ Offline'}</span>;
}tsx
// ✅ GOOD: useSyncExternalStore — concurrent-safe, less code
import { useSyncExternalStore } from 'react';
function subscribe(callback: () => void) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
function getSnapshot() {
return navigator.onLine;
}
function OnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return <span>{isOnline ? '✅ Online' : '❌ Offline'}</span>;
}For state management libraries, use their built-in React hooks instead of manual subscriptions. See the react-render-performance skill for selector-based patterns with XState, Zustand, Redux, and Nanostores.
使用 + 订阅外部数据源(浏览器API、第三方状态库、可观察对象)不仅样板代码多,还可能在并发模式下导致渲染不一致。
useEffectsetStatetsx
// 🔴 错误:使用useEffect手动订阅
function OnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return <span>{isOnline ? '✅ Online' : '❌ Offline'}</span>;
}tsx
// ✅ 正确:useSyncExternalStore——并发安全、代码更少
import { useSyncExternalStore } from 'react';
function subscribe(callback: () => void) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
function getSnapshot() {
return navigator.onLine;
}
function OnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return <span>{isOnline ? '✅ 在线' : '❌ 离线'}</span>;
}对于状态管理库,使用其内置的React Hook而非手动订阅。可参考react-render-performance技巧中关于XState、Zustand、Redux和Nanostores的选择器模式。
6. Resetting State on Prop Changes — Use a key
key6. Props变化时重置状态——使用key
属性
keyDon't use to reset state when a prop changes. React has a built-in mechanism: the prop.
useEffectkeytsx
// 🔴 BAD: Resetting state in an Effect
function EditProfile({ userId }) {
const [comment, setComment] = useState('');
useEffect(() => {
setComment('');
}, [userId]);
return <textarea value={comment} onChange={e => setComment(e.target.value)} />;
}tsx
// ✅ GOOD: Use key to reset component state
function ProfilePage({ userId }) {
// When userId changes, React unmounts the old EditProfile and mounts a new one
return <EditProfile userId={userId} key={userId} />;
}
function EditProfile({ userId }) {
const [comment, setComment] = useState('');
return <textarea value={comment} onChange={e => setComment(e.target.value)} />;
}不要使用在props变化时重置状态。React有内置的机制:属性。
useEffectkeytsx
// 🔴 错误:在Effect中重置状态
function EditProfile({ userId }) {
const [comment, setComment] = useState('');
useEffect(() => {
setComment('');
}, [userId]);
return <textarea value={comment} onChange={e => setComment(e.target.value)} />;
}tsx
// ✅ 正确:使用key重置组件状态
function ProfilePage({ userId }) {
// 当userId变化时,React会卸载旧的EditProfile并挂载新的实例
return <EditProfile userId={userId} key={userId} />;
}
function EditProfile({ userId }) {
const [comment, setComment] = useState('');
return <textarea value={comment} onChange={e => setComment(e.target.value)} />;
}When useEffect
IS Correct
useEffect什么时候useEffect
才是正确的选择
useEffectEffects are appropriate for synchronizing your component with something outside of React:
| Use Case | Why It's Correct |
|---|---|
| Integrating a non-React widget (map, chart, video player) | Syncing React state → imperative DOM API |
| Setting up a WebSocket or EventSource connection | External system lifecycle tied to component |
Measuring DOM layout ( | Reading post-render DOM information |
| Managing focus or scroll position imperatively | Imperative DOM interaction |
| Connecting to hardware APIs (camera, geolocation) | External system with subscribe/cleanup |
| Syncing React state → browser API |
Even for these, consider whether a library already handles it (e.g., , ).
react-intersection-observerframer-motionEffects适用于将组件与React外部的事物同步:
| 使用场景 | 合理性说明 |
|---|---|
| 集成非React组件(地图、图表、视频播放器) | 同步React状态 → 命令式DOM API |
| 建立WebSocket或EventSource连接 | 外部系统生命周期与组件绑定 |
测量DOM布局( | 读取渲染后的DOM信息 |
| 命令式管理焦点或滚动位置 | 命令式DOM交互 |
| 连接硬件API(摄像头、地理位置) | 带有订阅/清理逻辑的外部系统 |
| 同步React状态 → 浏览器API |
即使是这些场景,也可以考虑是否已有成熟库处理(如、)。
react-intersection-observerframer-motionDecision Flowchart
决策流程图
Ask yourself these questions before writing :
useEffect- Can I calculate this from existing props/state? → Derive it during render (or ).
useMemo - Is this a response to a user action? → Put it in the event handler.
- Am I fetching data? → Use TanStack Query, SWR, or your framework's data primitive.
- Am I subscribing to an external store? → Use or the library's hook.
useSyncExternalStore - Am I resetting state when a prop changes? → Use the prop.
key - Am I syncing with an external system that React doesn't control? → ✅ This is a valid .
useEffect
在编写之前,先问自己这些问题:
useEffect- 能否通过现有props/state计算得到? → 在渲染时直接派生(或使用)。
useMemo - 这是否是对用户操作的响应? → 放在事件处理函数中。
- 我是否在获取数据? → 使用TanStack Query、SWR或框架自带的数据原语。
- 我是否在订阅外部存储? → 使用或对应库的Hook。
useSyncExternalStore - 我是否要在props变化时重置状态? → 使用属性。
key - 我是否在与React无法控制的外部系统同步? → ✅ 这是的合理使用场景。
useEffect
Summary Checklist
检查清单
- No that sets state derived from other state/props — calculate during render instead
useEffect - No that runs logic in response to user events — use event handlers instead
useEffect - No chains of s that trigger each other via state updates — consolidate into event handlers or derived values
useEffect - No raw for data fetching — use TanStack Query, SWR, or framework data primitives
useEffect - No +
useEffectfor external store subscriptions — usesetStateor library hooksuseSyncExternalStore - No to reset state when props change — use the
useEffectpropkey - Every remaining is syncing with a genuine external system (DOM API, WebSocket, third-party widget)
useEffect
- 没有通过设置可从其他state/props派生的状态——改为在渲染时计算
useEffect - 没有通过响应用户事件——改为使用事件处理函数
useEffect - 没有通过state更新互相触发的Effect链式调用——合并到事件处理函数或派生值中
- 没有使用原生进行数据获取——使用TanStack Query、SWR或框架数据原语
useEffect - 没有使用+
useEffect订阅外部存储——使用setState或库的HookuseSyncExternalStore - 没有使用在props变化时重置状态——使用
useEffect属性key - 剩余的均用于与真正的外部系统同步(DOM API、WebSocket、第三方组件)
useEffect