Skip to content

你可能不需要Effect

🎯 核心观点

🧐 Effect的本质

  • 🔮 Effect是脱围机制:用于连接React与外部系统
  • 🌐 外部系统包括:非React组件、网络API、浏览器DOM等
  • ⚠️ 滥用警告:如果没有涉及外部系统,通常不需要Effect

🚫 不需要Effect的常见场景

📊 场景1:转换渲染所需的数据

  • 错误用法:使用Effect来转换props或state并更新另一个state
  • 正确方法:直接在渲染期间计算派生值
jsx
// ❌ 不必要的Effect
function Form() {
  const [firstName, setFirstName] = useState('Taylor');
  const [lastName, setLastName] = useState('Swift');
  
  // 多余的state和Effect
  const [fullName, setFullName] = useState('');
  useEffect(() => {
    setFullName(firstName + ' ' + lastName);
  }, [firstName, lastName]);
}

// ✅ 直接在渲染期间计算
function Form() {
  const [firstName, setFirstName] = useState('Taylor');
  const [lastName, setLastName] = useState('Swift');
  
  // 在渲染期间直接计算
  const fullName = firstName + ' ' + lastName;
}

🧮 场景2:缓存昂贵的计算

  • 错误用法:使用Effect缓存计算结果
  • 正确方法:使用useMemo进行性能优化
jsx
// ❌ 不必要的Effect
function TodoList({ todos, filter }) {
  const [visibleTodos, setVisibleTodos] = useState([]);
  useEffect(() => {
    setVisibleTodos(getFilteredTodos(todos, filter));
  }, [todos, filter]);
}

// ✅ 使用useMemo缓存计算结果
function TodoList({ todos, filter }) {
  const visibleTodos = useMemo(() => {
    return getFilteredTodos(todos, filter);
  }, [todos, filter]);
}

🔄 场景3:当props变化时重置state

  • 错误用法:使用Effect监听props变化并重置state
  • 正确方法一:通过key属性重置整个组件
  • 正确方法二:在渲染期间有条件地更新state
jsx
// ✅ 方法一:使用key重置整个组件
<ProfilePage key={userId} userId={userId} />

// ✅ 方法二:在渲染期间直接更新
function List({ items }) {
  const [selection, setSelection] = useState(null);
  
  // 当items没有选中项时,重置selection
  const hasSelection = selection != null && items.includes(selection);
  if (!hasSelection && items.length > 0) {
    setSelection(items[0]);
  }
}

📝 场景4:处理用户事件

  • 错误用法:使用Effect响应用户交互
  • 正确方法:将逻辑放在事件处理函数中
jsx
// ❌ 不必要的Effect
function Form() {
  const [submitted, setSubmitted] = useState(false);
  
  useEffect(() => {
    if (submitted) {
      // 🔴 避免:在Effect中处理事件逻辑
      post('/api/register');
      showNotification('注册成功!');
    }
  }, [submitted]);
}

// ✅ 将逻辑放在事件处理函数中
function Form() {
  function handleSubmit() {
    // ✅ 事件处理函数是处理交互的好地方
    post('/api/register');
    showNotification('注册成功!');
  }
}

🌟 何时需要使用Effect

🔄 与外部系统同步

  • 🌐 浏览器API:管理文档标题、媒体播放等
  • 🔌 第三方小部件:集成非React UI库
  • 📡 网络:订阅服务器事件
jsx
// ✅ 适合使用Effect:与外部系统同步
useEffect(() => {
  const connection = createConnection(serverUrl, roomId);
  connection.connect();
  return () => connection.disconnect();
}, [roomId]);

🔍 数据获取(有替代方案)

  • ⚠️ 注意:虽然可以用Effect获取数据,但有局限性
  • 🛠️ 更好的选择:使用框架提供的数据获取机制或自定义Hook
jsx
// 使用自定义Hook简化数据获取
function useData(url) {
  const [data, setData] = useState(null);
  useEffect(() => {
    let ignore = false;
    fetch(url)
      .then(response => response.json())
      .then(json => {
        if (!ignore) {
          setData(json);
        }
      });
    return () => {
      ignore = true;
    };
  }, [url]);
  return data;
}

🛠️ 重构技巧总结

💊 移除不必要的Effect的方法

  • 🧮 渲染期间计算:直接在组件函数体内计算派生值
  • 💾 使用useMemo:缓存昂贵计算结果
  • 🔑 使用key属性:通过改变key完全重置组件
  • 🎮 使用事件处理函数:在用户交互时执行逻辑
  • 🏗️ 状态提升:将共享状态提升到父组件中

📝 记住的规则

  1. 🧠 如果可以在渲染期间计算,不需要Effect
  2. 👆 处理用户事件时,把逻辑放在事件处理函数中
  3. 🌐 只在需要与外部系统同步时使用Effect
  4. 🧩 在数据获取时,考虑使用专门的Hook或框架

参考:React官方文档-你可能不需要Effect