外观
React Hooks 与副作用
- 状态:未准备
- 适用方向:前端 / Go 全栈
- 最近复习:待填写
- 下次复习:待安排
一句话回答
useState 保存影响视图的数据,useRef 保存跨渲染但不直接驱动视图的数据,useEffect 负责与外部系统同步,并在依赖改变或卸载时清理旧同步。
原理解释
Effect 什么时候执行?
Effect 在组件提交后运行;依赖通过 Object.is 比较。没有依赖数组时每次相关提交后运行,空数组不会因普通依赖变化重跑,但不代表整个应用生命周期绝对只执行一次。依赖变化时,先清理旧 Effect,再建立新 Effect;卸载时也清理。
jsx
useEffect(() => {
const controller = new AbortController()
let active = true
async function load() {
try {
const response = await fetch(`/api/users/${id}`, {
signal: controller.signal,
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const data = await response.json()
if (active) setUser(data)
} catch (error) {
if (active) setError(String(error))
}
}
load()
return () => {
active = false
controller.abort()
}
}, [id])示例重点是取消与过期结果隔离;实际页面还需在切换时重置 loading/error,并处理数据缓存和运行时校验。不能把 Effect 回调本身写成 async,因为它需要返回清理函数或 undefined。
陈旧闭包怎么处理?
先判断逻辑是否属于事件还是外部同步。需要同步变化的响应式值应进入依赖;基于前值更新可用函数式 setter;不驱动 UI 的连接句柄、计时器 ID 可放 ref。不能为了消除 lint 提示随意删依赖。
场景与边界
能在渲染中直接计算的派生值,通常不需要 Effect 再写一次 state。useLayoutEffect 用于必须在绘制前测量或调整布局的场景,会阻塞绘制,不能当默认替代。修改 ref.current 不触发重新渲染。
项目中的应用
结合 SimulSpeak 媒体与窗口清理 和 Blog 动画,检查监听、rAF、连接、媒体轨道是否有对应释放,异步晚到资源是否再次检查会话是否有效。
常见追问
- Hooks 为什么不能随意放条件里? useState/useEffect 等依靠稳定调用顺序关联状态;应在组件或自定义 Hook 顶层调用。特殊 API use 有单独规则,不能泛化。
- 自定义 Hook 会共享状态吗? 默认共享逻辑,每次调用各有状态;真正共享需外部 store、Context 等机制。
- 空数组加 ref 是万能修复吗? 不是,可能使外部系统不随真正依赖变化重建,应先明确生命周期。