响应式Effect的生命周期
🌟 核心概念
💡 组件与Effect生命周期的区别
- 🏗️ 组件生命周期:挂载 → 更新 → 卸载
- 🔄 Effect生命周期:开始同步 → 停止同步 → (可能重复多次)
🎯 Effect的本质
- 📊 Effect是同步的过程:将组件与外部系统同步
- 🔁 独立于组件生命周期:同步可能在组件更新时发生多次
- 🧩 描述了一个完整过程:从开始到结束的完整同步流程
jsx
function ChatRoom({ roomId }) {
useEffect(() => {
// 开始同步 - 连接到聊天室
const connection = createConnection(serverUrl, roomId);
connection.connect();
// 停止同步 - 断开连接
return () => {
connection.disconnect();
};
}, [roomId]);
}
📚 Effect重新同步的过程
🔄 重新同步的时机
- 🏗️ 首次渲染后:组件挂载时开始初始同步
- 🔄 依赖项变化后:停止旧同步 → 开始新同步
- 🗑️ 组件卸载时:停止同步
🔍 重新同步的例子
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会通过代码检查工具验证依赖项的正确性
- 🛠️ 修复依赖问题应该通过重构代码,而不是忽略检查规则