react-useeffect-audit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React 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

快速决策指南

ScenarioUse Effect?Alternative
Transform data for renderingNoCalculate during render
Handle user eventsNoEvent handler
Cache expensive calculationsNo
useMemo
Reset state when props changeNo
key
prop
Adjust state based on propsNoCalculate during render
Chain of state updatesNoSingle event handler
Notify parent of state changeNoCall parent in event handler
Initialize app (once)NoModule-level code
Subscribe to external storeNo
useSyncExternalStore
Fetch data on mount/changeYesWith cleanup; prefer framework
场景是否使用Effect?替代方案
转换用于渲染的数据在渲染期间计算
处理用户事件事件处理函数
缓存昂贵的计算结果
useMemo
当props变化时重置状态
key
属性
根据props调整状态在渲染期间计算
链式状态更新单个事件处理函数
通知父组件状态变化在事件处理函数中调用父组件方法
应用初始化(仅一次)模块级代码
订阅外部状态库
useSyncExternalStore
在挂载/变化时获取数据搭配清理逻辑;优先使用框架内置方案

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

核心原则

  1. Calculate during render - Derived values don't need state or Effects
  2. Events belong in handlers - User interactions go in event handlers, not Effects
  3. Single render pass - Update multiple state variables in one event handler
  4. Use specialized hooks -
    useMemo
    ,
    useSyncExternalStore
    over Effects
  5. Always cleanup async - Prevent race conditions with ignore flags
  6. Lift state when sharing - Parent component owns shared state
For detailed examples and edge cases, see references/patterns.md.
  1. 在渲染期间计算 - 派生值不需要状态或Effects
  2. 事件逻辑放在处理函数中 - 用户交互应写在事件处理函数中,而非Effects
  3. 单次渲染传递 - 在一个事件处理函数中更新多个状态变量
  4. 使用专用hooks - 优先使用
    useMemo
    useSyncExternalStore
    而非Effects
  5. 异步操作务必清理 - 使用忽略标记防止竞态条件
  6. 共享状态提升 - 父组件管理共享状态
如需详细示例和边缘场景说明,请查看references/patterns.md