react-useeffect-avoid
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact: When Not to Use useEffect
React:何时不应使用useEffect
Core Principle
核心原则
useEffectModern React patterns prioritize one-way data flow and event-driven updates over effect-based synchronization to avoid performance penalties and complex synchronization bugs.
useEffect现代React模式优先采用单向数据流和事件驱动更新,而非基于effect的同步方式,以此避免性能损耗和复杂的同步bug。
When NOT to Use useEffect
何时不应使用useEffect
1. ❌ Calculating Derived State
1. ❌ 计算派生状态
Problem: Computing one state value from another using causes unnecessary double-renders and complexity.
useEffectjsx
// ❌ BAD: Double render cycle
function FilteredList({ items }) {
const [query, setQuery] = useState('');
const [filtered, setFiltered] = useState([]);
useEffect(() => {
setFiltered(items.filter(item => item.name.includes(query)));
}, [items, query]); // Renders twice on every input
return <ul>{filtered.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}✅ Solution: Calculate derived state directly during render.
jsx
// ✅ GOOD: Single render, no effect needed
function FilteredList({ items }) {
const [query, setQuery] = useState('');
// Calculated every render - no state, no effect
const filtered = items.filter(item => item.name.includes(query));
return <ul>{filtered.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}Why it's better:
- Immediate feedback UI updates (no double render pause)
- Simpler mental model: what you see is what you get
- No dependency array to manage
- React can optimize calculations via memoization when needed
问题: 使用从另一个状态值计算新状态会导致不必要的双重渲染和复杂度提升。
useEffectjsx
// ❌ 不良写法:触发两次渲染周期
function FilteredList({ items }) {
const [query, setQuery] = useState('');
const [filtered, setFiltered] = useState([]);
useEffect(() => {
setFiltered(items.filter(item => item.name.includes(query)));
}, [items, query]); // 每次输入都会触发两次渲染
return <ul>{filtered.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}✅ 解决方案: 在渲染阶段直接计算派生状态。
jsx
// ✅ 推荐写法:单次渲染,无需effect
function FilteredList({ items }) {
const [query, setQuery] = useState('');
// 每次渲染时计算 - 无需额外状态,无需effect
const filtered = items.filter(item => item.name.includes(query));
return <ul>{filtered.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}优势:
- UI更新即时反馈(无双重渲染延迟)
- 心智模型更简单:所见即所得
- 无需管理依赖数组
- 必要时React可通过memoization优化计算
2. ❌ Resetting State When a Prop Changes
2. ❌ 当Props变化时重置状态
Problem: Watching a prop change and manually resetting state causes stale data display and extra renders.
jsx
// ❌ BAD: Shows old state briefly, then new
function UserForm({ userId }) {
const [userName, setUserName] = useState('');
const [email, setEmail] = useState('');
useEffect(() => {
setUserName(''); // First render shows old userName
setEmail(''); // Then this effect runs to reset
}, [userId]); // Double render every userId change
return (
<form>
<input value={userName} onChange={e => setUserName(e.target.value)} />
<input value={email} onChange={e => setEmail(e.target.value)} />
</form>
);
}✅ Solution A: Key Prop (Preferred)
jsx
// ✅ GOOD: React tears down and rebuilds component
function App() {
const [userId, setUserId] = useState(1);
return (
<div>
<button onClick={() => setUserId(prev => prev + 1)}>Next User</button>
{/* Different key = different component instance */}
<UserForm key={userId} userId={userId} />
</div>
);
}✅ Solution B: Derived State (When you only need partial reset)
jsx
// ✅ GOOD: Only reset specific values, keep others
function UserForm({ userId }) {
const [userName, setUserName] = useSyncExternalStore(
() => ({ onSet: setUserName }), // Reset when userId changes
{ value: '' }
);
const [email, setEmail] = useState('');
// Email persists, userName resets
return <form>...</form>;
}⚠️ Caveat: The prop approach remounts the entire component. Use when you need a complete state reset. For partial resets, consider derived state patterns.
key问题: 监听Props变化并手动重置状态会导致旧数据短暂显示和额外渲染。
jsx
// ❌ 不良写法:先显示旧状态,再更新为新状态
function UserForm({ userId }) {
const [userName, setUserName] = useState('');
const [email, setEmail] = useState('');
useEffect(() => {
setUserName(''); // 首次渲染显示旧userName
setEmail(''); // 随后effect执行重置操作
}, [userId]); // 每次userId变化都会触发两次渲染
return (
<form>
<input value={userName} onChange={e => setUserName(e.target.value)} />
<input value={email} onChange={e => setEmail(e.target.value)} />
</form>
);
}✅ 解决方案A:使用Key Prop(优先推荐)
jsx
// ✅ 推荐写法:React会卸载并重建组件
function App() {
const [userId, setUserId] = useState(1);
return (
<div>
<button onClick={() => setUserId(prev => prev + 1)}>下一个用户</button>
{/* 不同的key对应不同的组件实例 */}
<UserForm key={userId} userId={userId} />
</div>
);
}✅ 解决方案B:派生状态(仅需部分重置时)
jsx
// ✅ 推荐写法:仅重置特定值,保留其他状态
function UserForm({ userId }) {
const [userName, setUserName] = useSyncExternalStore(
() => ({ onSet: setUserName }), // 当userId变化时重置
{ value: '' }
);
const [email, setEmail] = useState('');
// Email状态保留,userName状态重置
return <form>...</form>;
}⚠️ 注意: 使用key prop的方式会重新挂载整个组件。当你需要完全重置状态时使用该方法。若仅需部分重置,可考虑派生状态模式。
3. ❌ Waterfall of State Updates
3. ❌ 状态更新瀑布流
Problem: Multiple effects triggering each other create cascading renders that are hard to debug.
jsx
// ❌ BAD: Effects trigger effects
function OrderForm() {
const [formData, setFormData] = useState({});
const [validated, setValidated] = useState(false);
const [error, setError] = useState(null);
// Effect 1: Validate when formData changes
useEffect(() => {
setValidated(validateData(formData));
}, [formData]);
// Effect 2: Set error when validation changes
useEffect(() => {
setError(validated ? null : 'Invalid');
}, [validated]);
// Effect 3: Submit when no error
useEffect(() => {
if (!error && validated) submitOrder(formData);
}, [error, validated, formData]);
}✅ Solution: Handle all logic in event handler.
jsx
// ✅ GOOD: One handler, one render, clear flow
function OrderForm() {
const [formData, setFormData] = useState({});
const handleSubmit = () => {
// All logic happens atomically
if (validateData(formData)) {
submitOrder(formData); // No intermediate states
} else {
setError('Invalid form'); // Set directly, no cascade
}
};
return <button onClick={handleSubmit}>Submit</button>;
}问题: 多个effect互相触发会导致级联渲染,难以调试。
jsx
// ❌ 不良写法:Effect互相触发
function OrderForm() {
const [formData, setFormData] = useState({});
const [validated, setValidated] = useState(false);
const [error, setError] = useState(null);
// Effect 1:当formData变化时验证
useEffect(() => {
setValidated(validateData(formData));
}, [formData]);
// Effect 2:当验证状态变化时设置错误信息
useEffect(() => {
setError(validated ? null : 'Invalid');
}, [validated]);
// Effect 3:无错误时提交表单
useEffect(() => {
if (!error && validated) submitOrder(formData);
}, [error, validated, formData]);
}✅ 解决方案: 在事件处理器中处理所有逻辑。
jsx
// ✅ 推荐写法:单个处理器,单次渲染,流程清晰
function OrderForm() {
const [formData, setFormData] = useState({});
const handleSubmit = () => {
// 所有逻辑原子性执行
if (validateData(formData)) {
submitOrder(formData); // 无中间状态
} else {
setError('Invalid form'); // 直接设置,无级联
}
};
return <button onClick={handleSubmit}>Submit</button>;
}4. ❌ Fetching Data on User Action
4. ❌ 用户操作触发的数据请求
Problem: Using a flag to trigger effects loses user intent and creates fragile code.
jsx
// ❌ BAD: Intent is lost, hard to follow
function LoginForm() {
const [username, setUsername] = useState('');
const [submitted, setSubmitted] = useState(false);
const handleSubmit = () => {
setSubmitted(true); // Just a flag, no actual logic
};
useEffect(() => {
if (submitted) {
login(username); // What triggered this? Which submit was it?
}
}, [submitted, username]);
}✅ Solution: Perform action directly in handler.
jsx
// ✅ GOOD: Clear intent, direct action
function LoginForm() {
const [username, setUsername] = useState('');
const handleSubmit = async (e) => {
e.preventDefault(); // User action context preserved
// Actual logic here, not delayed
try {
await login(username);
} catch (error) {
console.error('Login failed:', error);
}
};
return <form onSubmit={handleSubmit}>...</form>;
}问题: 使用标记位触发effect会丢失用户意图,代码脆弱。
jsx
// ❌ 不良写法:意图不清晰,难以追踪
function LoginForm() {
const [username, setUsername] = useState('');
const [submitted, setSubmitted] = useState(false);
const handleSubmit = () => {
setSubmitted(true); // 仅设置标记位,无实际逻辑
};
useEffect(() => {
if (submitted) {
login(username); // 是什么触发了这个操作?是哪次提交?
}
}, [submitted, username]);
}✅ 解决方案: 在处理器中直接执行操作。
jsx
// ✅ 推荐写法:意图清晰,直接执行
function LoginForm() {
const [username, setUsername] = useState('');
const handleSubmit = async (e) => {
e.preventDefault(); // 保留用户操作上下文
// 实际逻辑直接在此执行,无延迟
try {
await login(username);
} catch (error) {
console.error('Login failed:', error);
}
};
return <form onSubmit={handleSubmit}>...</form>;
}5. ❌ Filtering/Transforming Lists
5. ❌ 过滤/转换列表
Problem: Creating filtered lists via effects is a common mistake that causes extra renders.
jsx
// ❌ BAD: Unnecessary effect
function ProductList({ products }) {
const [visibleProducts, setVisibleProducts] = useState([]);
useEffect(() => {
setVisibleProducts(products.filter(p => p.inStock));
}, [products]); // Runs twice whenever products change
}✅ Solution: Filter directly during render.
jsx
// ✅ GOOD: Immediate, single render
function ProductList({ products }) {
const visibleProducts = products.filter(p => p.inStock);
}问题: 通过effect创建过滤列表是常见错误,会导致额外渲染。
jsx
// ❌ 不良写法:不必要的effect
function ProductList({ products }) {
const [visibleProducts, setVisibleProducts] = useState([]);
useEffect(() => {
setVisibleProducts(products.filter(p => p.inStock));
}, [products]); // 每次products变化时执行两次
}✅ 解决方案: 在渲染阶段直接过滤。
jsx
// ✅ 推荐写法:即时执行,单次渲染
function ProductList({ products }) {
const visibleProducts = products.filter(p => p.inStock);
}6. ❌ Subscribing to Browser APIs (Without useSyncExternalStore)
6. ❌ 订阅浏览器API(未使用useSyncExternalStore)
Problem: Using + for external subscriptions causes "tearing" in concurrent rendering.
useStateuseEffectjsx
// ❌ BAD: UI can show inconsistent state during concurrent updates
function WindowSize() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
}✅ Solution: Use for external state.
useSyncExternalStorejsx
// ✅ GOOD: Prevents tearing, single source of truth
function WindowSize() {
const width = useSyncExternalStore(
// Subscribe - return cleanup function
(callback) => {
window.addEventListener('resize', callback);
return () => window.removeEventListener('resize', callback);
},
// Get snapshot
() => window.innerWidth,
// Server fallback
() => 1200
);
}Why it's better:
- React can guarantee UI consistency even during concurrent rendering
- Single source of truth
- Built-in optimization - subscription fires only when needed
问题: 使用 + 处理外部订阅会在并发渲染中导致“撕裂”问题。
useStateuseEffectjsx
// ❌ 不良写法:并发更新期间UI可能显示不一致状态
function WindowSize() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
}✅ 解决方案: 使用处理外部状态。
useSyncExternalStorejsx
// ✅ 推荐写法:防止撕裂,单一数据源
function WindowSize() {
const width = useSyncExternalStore(
// 订阅 - 返回清理函数
(callback) => {
window.addEventListener('resize', callback);
return () => window.removeEventListener('resize', callback);
},
// 获取快照
() => window.innerWidth,
// 服务端回退值
() => 1200
);
}优势:
- 即使在并发渲染期间,React也能保证UI一致性
- 单一数据源
- 内置优化 - 仅在需要时触发订阅
7. ❌ Initial Data Fetch Without Dependencies (Stale Data)
7. ❌ 无依赖的初始数据请求(数据过期)
Problem: Empty dependency array with state inside effect causes stale bugs.
jsx
// ❌ BAD: Id never updates, always fetches user 1
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/user/1`).then(res => res.json()).then(setUser);
}, [ ]); // Missing userId dependency!
}✅ Solution: Include all dependencies or use proper patterns.
jsx
// ✅ GOOD: Correct dependencies
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/user/${userId}`).then(res => res.json()).then(setUser);
}, [userId]); // Properly tracks changes
}Better: Move to event-driven fetching or data libraries.
jsx
// ✅ EVEN BETTER: No effect, just loader
function UserProfile({ userId }) {
const user = use(userId); // React 19's use API
// Or use React Query, SWR, etc.
}问题: 依赖数组为空且effect内部使用状态会导致过期bug。
jsx
// ❌ 不良写法:Id从不更新,始终请求用户1的数据
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/user/1`).then(res => res.json()).then(setUser);
}, [ ]); // 缺少userId依赖!
}✅ 解决方案: 包含所有依赖或使用合适的模式。
jsx
// ✅ 推荐写法:正确的依赖项
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/user/${userId}`).then(res => res.json()).then(setUser);
}, [userId]); // 正确追踪变化
}更优方案: 转向事件驱动的请求方式或使用数据库。
jsx
// ✅ 更优写法:无effect,仅使用loader
function UserProfile({ userId }) {
const user = use(userId); // React 19的use API
// 或使用React Query、SWR等库
}Decision Tree: useEffect vs Alternatives
决策树:useEffect vs 替代方案
Need to sync with external system?
├─ Yes (browser APIs, websockets, timers)
│ └─ Use useEffect
│
└─ No (pure React application logic)
├─ Derived state calculation?
│ ├─ Yes → Calculate during render
│ └─ No → Continue...
│
├─ User action triggered?
│ ├─ Yes → Use event handler
│ └─ No → Continue...
│
├─ State reset needed?
│ ├─ Yes → Use key prop
│ └─ No → Continue...
│
└─ Really need effect after re-think?
└─ Yes → Use useState/useReducer/setState pattern是否需要与外部系统同步?
├─ 是(浏览器API、websocket、定时器)
│ └─ 使用useEffect
│
└─ 否(纯React应用逻辑)
├─ 是否为派生状态计算?
│ ├─ 是 → 在渲染阶段计算
│ └─ 否 → 继续...
│
├─ 是否由用户操作触发?
│ ├─ 是 → 使用事件处理器
│ └─ 否 → 继续...
│
├─ 是否需要重置状态?
│ ├─ 是 → 使用key prop
│ └─ 否 → 继续...
│
└─ 重新思考后是否真的需要effect?
└─ 是 → 使用useState/useReducer/setState模式Patterns to Always Avoid
需始终避免的模式
❌ 1. useEffect for DOM Manipulation (without refs)
❌ 1. 无refs的情况下使用useEffect操作DOM
jsx
// BAD
function Modal() {
useEffect(() => {
document.body.style.overflow = 'hidden';
return () => { document.body.style.overflow = ''; };
}, []);
}✅ Better: Use refs or layout effects when needed.
jsx
// 不良写法
function Modal() {
useEffect(() => {
document.body.style.overflow = 'hidden';
return () => { document.body.style.overflow = ''; };
}, []);
}✅ 更优方案: 必要时使用refs或layout effects。
❌ 2. useEffect for Logging
❌ 2. 使用useEffect进行日志记录
jsx
// BAD
function App({ data }) {
useEffect(() => {
console.log('Data changed:', data);
}, [data]);
}✅ Better: Log in event handler or use profiling.
React DevToolsjsx
// 不良写法
function App({ data }) {
useEffect(() => {
console.log('Data changed:', data);
}, [data]);
}✅ 更优方案: 在事件处理器中记录日志,或使用分析。
React DevTools❌ 3. useEffect for Component Initialization
❌ 3. 使用useEffect进行组件初始化
jsx
// BAD
function Widget() {
const [initialized, setInitialized] = useState(false);
useEffect(() => {
someLibrary.init();
setInitialized(true);
}, []);
}✅ Better: Initialize outside component effect:
jsx
// GOOD
let initialized = false;
function Widget() {
if (!initialized) {
someLibrary.init();
initialized = true;
}
}Or use library-specific initialization patterns.
jsx
// 不良写法
function Widget() {
const [initialized, setInitialized] = useState(false);
useEffect(() => {
someLibrary.init();
setInitialized(true);
}, []);
}✅ 更优方案: 在组件effect外部初始化:
jsx
// 推荐写法
let initialized = false;
function Widget() {
if (!initialized) {
someLibrary.init();
initialized = true;
}
}或使用库特定的初始化模式。
Quick Reference
快速参考
❌ Don't use useEffect for:
❌ 不应使用useEffect的场景:
| Scenario | Problem | Alternative |
|---|---|---|
| Derived state | Double render | Calculate during render |
| State resets | Stale data | Use |
| User actions | Lost intent | Event handlers |
| List filtering | Extra renders | Filter in render |
| Browser APIs | Tearing bugs (concurrent) | |
| Form submission | Fragile flag pattern | Direct async handler |
| Data fetching | Manual cache management | React Query, SWR, Suspense |
| 场景 | 问题 | 替代方案 |
|---|---|---|
| 派生状态 | 双重渲染 | 在渲染阶段计算 |
| 状态重置 | 数据过期 | 使用 |
| 用户操作 | 丢失意图 | 事件处理器 |
| 列表过滤 | 额外渲染 | 在渲染阶段过滤 |
| 浏览器API | 并发撕裂bug | |
| 表单提交 | 脆弱的标记位模式 | 直接异步处理器 |
| 数据请求 | 手动缓存管理 | React Query、SWR、Suspense |
✅ DO use useEffect for:
✅ 应使用useEffect的场景:
- Subscribing to external systems (websockets, browser APIs, etc.)
- Setting up timers with cleanup
- Managing third-party library integration
- Document title changes
- Analytics/telemetry when rendering completes
- 订阅外部系统(websocket、浏览器API等)
- 设置带清理操作的定时器
- 管理第三方库集成
- 修改文档标题
- 渲染完成后的分析/遥测
React 19: New Alternatives
React 19:新替代方案
React 19 introduces the API for reading resources in render:
usejsx
// React 19+ - Direct resource reading
function UserProfile({ userId }) {
const user = use(fetchUser(userId)); // Reads promise directly
return <div>{user.name}</div>;
}This eliminates many data-fetching useEffect patterns entirely.
React 19引入了 API,用于在渲染阶段读取资源:
usejsx
// React 19+ - 直接读取资源
function UserProfile({ userId }) {
const user = use(fetchUser(userId)); // 直接读取Promise
return <div>{user.name}</div>;
}这彻底消除了许多基于effect的数据请求模式。
References and Further Reading
参考资料与扩展阅读
Official Documentation
官方文档
Articles from Senior Engineers
资深工程师文章
- LogRocket: 15 Common useEffect Mistakes - Comprehensive anti-pattern catalog
- Epic React: Myths About useEffect - Kent Dodds on mental models
- Kent Dodds: useSyncExternalStore Demystified
- LogRocket: 15 Common useEffect Mistakes - 全面的反模式目录
- Epic React: Myths About useEffect - Kent Dodds关于心智模型的讲解
- Kent Dodds: useSyncExternalStore Demystified
Key Principles
核心原则
- Effects are escape hatches - use only when stepping outside React
- Event-driven > Effect-driven - prefer handlers for user actions
- Render-time > Effect-time - calculate values during render
- Single source of truth - avoid duplicating state in effects
- Concurrent-safe - use specialized hooks for external subscriptions
- Effects是逃生舱 - 仅在脱离React环境时使用
- 事件驱动 > Effect驱动 - 优先为用户操作使用处理器
- 渲染阶段 > Effect阶段 - 在渲染阶段计算值
- 单一数据源 - 避免在effects中复制状态
- 并发安全 - 对外部订阅使用专用hooks