Effect 依赖和清理函数什么时候执行?

先理解原理和示例,再练习完整口述与追问。

依赖变化的提交后,先清理旧同步,再执行新同步;卸载时也清理。依赖逐项按 Object.is 比较,应包含 Effect 使用的相关响应式值。无依赖数组与空数组含义不同。

Effect 首次提交后建立同步;依赖变化时通常先清理旧一轮,再建立新一轮;卸载时清理最后一轮。依赖按 Object.is 比较,所以每次新建的对象和函数可能导致重复运行。没有依赖数组、空数组与有依赖数组代表不同同步关系,不能仅用生命周期钩子的名字机械替换。

jsx
useEffect(() => {
  const handle = event => receive(roomId, event)
  socket.on('message', handle)
  return () => socket.off('message', handle)
}, [socket, roomId])

roomId 从 A 变 B,清理的是与 A 对应的旧 handle,然后注册 B 的新 handle。清理函数闭包保留旧值,正好用于撤销旧工作。

不要把整个 async 函数直接作为 Effect 回调返回 Promise;应在内部定义异步函数,并同步返回清理。异常、取消与旧响应覆盖仍需单独处理。

Effect 在提交后运行;依赖变化时,先用旧闭包执行上次 cleanup,再用新闭包 setup,卸载也 cleanup。依赖逐项用 Object.is 比较,应列出 Effect 实际使用的响应式输入。省略数组通常每次提交后同步,空数组则不随普通输入变化重新同步,但仍受挂载和开发检查影响。

面试官可能继续问追问与完整解答展开收起

对象每次渲染都新建为何会重复触发?为什么 Effect 函数不直接写成 async?旧请求怎么取消或忽略?React 官方 useEffect

每次创建的新对象引用不同会让依赖变化,可把对象构造放进 Effect 或稳定真正必要输入。Effect 回调不能直接 async,因为返回值应是 cleanup 或空,而不是 Promise;在内部调用异步函数,并通过 AbortController 或失效标记防止旧结果写回。