Skip to content

响应式Effect的生命周期

🌟 核心概念

💡 组件与Effect生命周期的区别

  • 🏗️ 组件生命周期:挂载 → 更新 → 卸载
  • 🔄 Effect生命周期:开始同步 → 停止同步 → (可能重复多次)

🎯 Effect的本质

  • 📊 Effect是同步的过程:将组件与外部系统同步
  • 🔁 独立于组件生命周期:同步可能在组件更新时发生多次
  • 🧩 描述了一个完整过程:从开始到结束的完整同步流程
jsx
function ChatRoom({ roomId }) {
  useEffect(() => {
    // 开始同步 - 连接到聊天室
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    
    // 停止同步 - 断开连接
    return () => {
      connection.disconnect();
    };
  }, [roomId]);
}

📚 Effect重新同步的过程

🔄 重新同步的时机

  1. 🏗️ 首次渲染后:组件挂载时开始初始同步
  2. 🔄 依赖项变化后:停止旧同步 → 开始新同步
  3. 🗑️ 组件卸载时:停止同步

🔍 重新同步的例子

jsx
// roomId为"general"时的初始同步
// 1. 连接到"general"聊天室

// 用户将roomId切换为"travel"
// 1. 断开与"general"聊天室的连接
// 2. 连接到"travel"聊天室

// 用户导航离开页面
// 1. 断开与"travel"聊天室的连接

🧐 从Effect角度思考

🎭 每个Effect描述独立的同步过程

  • 🔁 不要从组件角度思考:不是"挂载时做什么,更新时做什么"
  • 应该从Effect角度思考:这个Effect需要如何开始和停止同步?

📝 正确的思考方式

jsx
function ChatRoom({ roomId }) {
  useEffect(() => {
    // Effect的思维模式: 实现完整的同步过程
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);
}

🧪 响应式值与依赖

🔍 什么是响应式值?

  • ⚛️ Props - 可能随渲染变化的值
  • 💾 State - 组件内部状态
  • 🧮 派生值 - 在组件体内基于props或state计算的变量
jsx
function ChatRoom({ roomId }) { // roomId是响应式的(props)
  const [message, setMessage] = useState(''); // message是响应式的(state)
  const connectionURL = serverUrl + '/' + roomId; // connectionURL是响应式的(派生值)
  
  useEffect(() => {
    const connection = createConnection(connectionURL);
    connection.connect();
    return () => connection.disconnect();
  }, [connectionURL]); // 必须声明依赖
}

📋 依赖项检查规则

  • 🔍 React会验证依赖项:所有响应式值必须在依赖数组中声明
  • 🚨 不能"选择"依赖项:依赖项由Effect内部使用的响应式值决定
  • 🧹 空依赖数组 []:表示Effect不依赖任何响应式值,仅在挂载和卸载时运行
jsx
// ❌ 错误:依赖项缺失
useEffect(() => {
  const connection = createConnection(serverUrl, roomId);
  connection.connect();
  return () => connection.disconnect();
}, []); // 缺少roomId依赖

// ✅ 正确:包含所有依赖
useEffect(() => {
  const connection = createConnection(serverUrl, roomId);
  connection.connect();
  return () => connection.disconnect();
}, [roomId]);

🛠️ 解决依赖项问题的方法

🧩 拆分多个Effect

  • 📊 每个Effect表示一个独立的同步过程
  • 🔀 不同的关注点放在不同的Effect中

🔍 提取非响应式逻辑

  • 🏷️ 使用Effect Event:可以让部分逻辑不触发重新同步
  • 📡 将事件处理从Effect中分离:区分响应式和非响应式代码

🛡️ 避免对象和函数依赖

  • 🏭 将创建移到Effect内部:避免每次渲染都创建新引用
  • 🧮 使用原始值作为依赖:使用对象/函数的各个原始字段

⚠️ 常见陷阱与解决方案

🚫 不要禁用依赖检查工具

jsx
// 🔴 错误做法
useEffect(() => {
  // ...
  // eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);

✅ 正确解决方法是重构代码

  • 🧩 拆分Effect
  • 🏷️ 使用useEffectEvent
  • 🧪 调整响应式设计

📝 总结

  • 🔄 Effect有独立于组件的生命周期:开始同步和停止同步
  • 🧩 每个Effect应描述一个独立的同步过程
  • 📊 响应式值(props、state及其派生值)的变化会触发Effect重新同步
  • 📋 依赖项必须包含Effect中使用的所有响应式值
  • 🧠 React会通过代码检查工具验证依赖项的正确性
  • 🛠️ 修复依赖问题应该通过重构代码,而不是忽略检查规则

参考:React官方文档-响应式Effect的生命周期