react-useeffect-audit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact useEffect Audit
React useEffect 审计
Effects are an escape hatch for synchronizing with external systems (DOM, network, third-party widgets). If no external system is involved, you likely don't need an Effect.
Effects是用于与外部系统(DOM、网络、第三方组件)同步的“逃生舱”。如果没有涉及外部系统,你很可能不需要使用Effect。
Quick Decision Guide
快速决策指南
| Scenario | Use Effect? | Alternative |
|---|---|---|
| Transform data for rendering | No | Calculate during render |
| Handle user events | No | Event handler |
| Cache expensive calculations | No | |
| Reset state when props change | No | |
| Adjust state based on props | No | Calculate during render |
| Chain of state updates | No | Single event handler |
| Notify parent of state change | No | Call parent in event handler |
| Initialize app (once) | No | Module-level code |
| Subscribe to external store | No | |
| Fetch data on mount/change | Yes | With cleanup; prefer framework |
| 场景 | 是否使用Effect? | 替代方案 |
|---|---|---|
| 转换用于渲染的数据 | 否 | 在渲染期间计算 |
| 处理用户事件 | 否 | 事件处理函数 |
| 缓存昂贵的计算结果 | 否 | |
| 当props变化时重置状态 | 否 | |
| 根据props调整状态 | 否 | 在渲染期间计算 |
| 链式状态更新 | 否 | 单个事件处理函数 |
| 通知父组件状态变化 | 否 | 在事件处理函数中调用父组件方法 |
| 应用初始化(仅一次) | 否 | 模块级代码 |
| 订阅外部状态库 | 否 | |
| 在挂载/变化时获取数据 | 是 | 搭配清理逻辑;优先使用框架内置方案 |
Common Anti-Patterns
常见反模式
1. Derived State in Effect
1. Effect中的派生状态
jsx
// ❌ Bad: Extra render, unnecessary state
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// ✅ Good: Calculate during render
const fullName = firstName + ' ' + lastName;jsx
// ❌ 不良写法:额外渲染,不必要的状态
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// ✅ 推荐写法:在渲染期间计算
const fullName = firstName + ' ' + lastName;2. Event Logic in Effect
2. Effect中的事件逻辑
jsx
// ❌ Bad: Runs on mount if condition true, not just on click
useEffect(() => {
if (product.isInCart) {
showNotification('Added to cart!');
}
}, [product]);
// ✅ Good: In event handler
function handleBuyClick() {
addToCart(product);
showNotification('Added to cart!');
}jsx
// ❌ 不良写法:如果条件成立,会在挂载时执行,而非仅点击时
useEffect(() => {
if (product.isInCart) {
showNotification('Added to cart!');
}
}, [product]);
// ✅ 推荐写法:放在事件处理函数中
function handleBuyClick() {
addToCart(product);
showNotification('Added to cart!');
}3. Expensive Calculations
3. 昂贵的计算
jsx
// ❌ Bad: Unnecessary state and Effect
const [filtered, setFiltered] = useState([]);
useEffect(() => {
setFiltered(getFilteredTodos(todos, filter));
}, [todos, filter]);
// ✅ Good: useMemo for expensive calculations
const filtered = useMemo(
() => getFilteredTodos(todos, filter),
[todos, filter]
);jsx
// ❌ 不良写法:不必要的状态和Effect
const [filtered, setFiltered] = useState([]);
useEffect(() => {
setFiltered(getFilteredTodos(todos, filter));
}, [todos, filter]);
// ✅ 推荐写法:使用useMemo处理昂贵计算
const filtered = useMemo(
() => getFilteredTodos(todos, filter),
[todos, filter]
);4. Resetting State on Prop Change
4. Prop变化时重置状态
jsx
// ❌ Bad: Effect to reset state
function ProfilePage({ userId }) {
const [comment, setComment] = useState('');
useEffect(() => {
setComment('');
}, [userId]);
}
// ✅ Good: Use key to create fresh instance
<Profile userId={userId} key={userId} />jsx
// ❌ 不良写法:用Effect重置状态
function ProfilePage({ userId }) {
const [comment, setComment] = useState('');
useEffect(() => {
setComment('');
}, [userId]);
}
// ✅ 推荐写法:使用key属性创建全新组件实例
<Profile userId={userId} key={userId} />5. Effect Chains
5. Effect链式调用
jsx
// ❌ Bad: Multiple Effects triggering each other
useEffect(() => {
if (card?.gold) setGoldCount(c => c + 1);
}, [card]);
useEffect(() => {
if (goldCount > 3) { setRound(r => r + 1); setGoldCount(0); }
}, [goldCount]);
// ✅ Good: Calculate in event handler
function handlePlaceCard(nextCard) {
setCard(nextCard);
if (nextCard.gold) {
if (goldCount < 3) setGoldCount(goldCount + 1);
else { setGoldCount(0); setRound(round + 1); }
}
}jsx
// ❌ 不良写法:多个Effect互相触发
useEffect(() => {
if (card?.gold) setGoldCount(c => c + 1);
}, [card]);
useEffect(() => {
if (goldCount > 3) { setRound(r => r + 1); setGoldCount(0); }
}, [goldCount]);
// ✅ 推荐写法:在事件处理函数中统一计算
function handlePlaceCard(nextCard) {
setCard(nextCard);
if (nextCard.gold) {
if (goldCount < 3) setGoldCount(goldCount + 1);
else { setGoldCount(0); setRound(round + 1); }
}
}6. Notifying Parent
6. 通知父组件
jsx
// ❌ Bad: Effect to notify parent
useEffect(() => {
onChange(isOn);
}, [isOn, onChange]);
// ✅ Good: Notify in same event
function handleClick() {
setIsOn(!isOn);
onChange(!isOn);
}
// ✅ Better: Controlled component (parent owns state)
function Toggle({ isOn, onChange }) {
return <button onClick={() => onChange(!isOn)} />;
}jsx
// ❌ 不良写法:用Effect通知父组件
useEffect(() => {
onChange(isOn);
}, [isOn, onChange]);
// ✅ 推荐写法:在同一事件中通知
function handleClick() {
setIsOn(!isOn);
onChange(!isOn);
}
// ✅ 更优写法:受控组件(父组件管理状态)
function Toggle({ isOn, onChange }) {
return <button onClick={() => onChange(!isOn)} />;
}When Effects ARE Appropriate
适合使用Effects的场景
Data Fetching (with cleanup)
数据获取(搭配清理逻辑)
jsx
useEffect(() => {
let ignore = false;
fetchResults(query).then(json => {
if (!ignore) setResults(json); // Prevent race condition
});
return () => { ignore = true; };
}, [query]);jsx
useEffect(() => {
let ignore = false;
fetchResults(query).then(json => {
if (!ignore) setResults(json); // 防止竞态条件
});
return () => { ignore = true; };
}, [query]);External Store Subscription
外部状态库订阅
jsx
// ✅ Use dedicated hook
const isOnline = useSyncExternalStore(
callback => {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
},
() => navigator.onLine, // Client getter
() => true // Server getter
);jsx
// ✅ 使用专用hook
const isOnline = useSyncExternalStore(
callback => {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
},
() => navigator.onLine, // 客户端获取函数
() => true // 服务端获取函数
);App Initialization
应用初始化
jsx
// ✅ Module-level, not in Effect
if (typeof window !== 'undefined') {
checkAuthToken();
loadDataFromLocalStorage();
}jsx
// ✅ 模块级代码,而非Effect
if (typeof window !== 'undefined') {
checkAuthToken();
loadDataFromLocalStorage();
}Key Principles
核心原则
- Calculate during render - Derived values don't need state or Effects
- Events belong in handlers - User interactions go in event handlers, not Effects
- Single render pass - Update multiple state variables in one event handler
- Use specialized hooks - ,
useMemoover EffectsuseSyncExternalStore - Always cleanup async - Prevent race conditions with ignore flags
- Lift state when sharing - Parent component owns shared state
For detailed examples and edge cases, see references/patterns.md.
- 在渲染期间计算 - 派生值不需要状态或Effects
- 事件逻辑放在处理函数中 - 用户交互应写在事件处理函数中,而非Effects
- 单次渲染传递 - 在一个事件处理函数中更新多个状态变量
- 使用专用hooks - 优先使用、
useMemo而非EffectsuseSyncExternalStore - 异步操作务必清理 - 使用忽略标记防止竞态条件
- 共享状态提升 - 父组件管理共享状态
如需详细示例和边缘场景说明,请查看references/patterns.md。