尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

为什么回调要存进useRef?react-custom-hooks中“最新回调“模式源码剖析

为什么回调要存进useRef?react-custom-hooks中“最新回调“模式源码剖析 为什么回调要存进useRefreact-custom-hooks中最新回调模式源码剖析【免费下载链接】react-custom-hooksReact Custom Hooks S.Leschev: useArray useAsync useClickOutside useCookie useCopyToClipboard useDarkMode useDebounce useDebugInformation useDeepCompareEffect useEffectOnce useEventListener useFetch useGeolocation useHover useLongPress useMediaQuery useOnlineStatus useOnScreen usePrevious useRenderCount useScript etc.项目地址: https://gitcode.com/gh_mirrors/re/react-custom-hooks如果你写过 React 自定义 Hook一定遇到过这个经典难题useEffect 里闭包捕获的回调总是过期的。react-custom-hooks 是一个汇集了 20 实用 React 自定义 Hooks 的开源项目其中的useEventListener、useTimeout等 Hook 就用一个极简的套路彻底解决了这个问题——把回调存进 useRef。今天我们就通过真实源码把这个被称为最新回调latest callback的 React Hook 模式一次讲透。 先看问题为什么直接监听回调会翻车以 src/hooks/useEventListener/useEventListener.js 的使用场景为例你可能想这样监听键盘useEventListener(keydown, e setKey(e.key))如果实现时把callback直接放进 effect 的依赖数组会出现两个麻烦组件每次渲染callback都是全新的函数引用依赖判断必然变化effect 因此每次渲染都执行清理和重注册addEventListener/removeEventListener被反复调用——对于昂贵的监听器如IntersectionObserver、matchMedia这是明显的性能浪费。反过来如果依赖数组干脆不写callback监听器虽然绑定了但闭包里锁死的却是第一次渲染时那个旧回调stale closure读到的永远是过期的 state。两难react-custom-hooks 的答案是两边都不要。 源码剖析最新回调三步走useEventListener的完整实现不到 20 行src/hooks/useEventListener/useEventListener.jsexport default function useEventListener(eventType, callback, element window) { const callbackRef useRef(callback) // ① 用 ref 存回调 useEffect(() { callbackRef.current callback // ② 每次渲染后刷新为最新回调 }, [callback]) useEffect(() { const handler e callbackRef.current(e) // ③ 触发时读取最新回调 element.addEventListener(eventType, handler) return () element.removeEventListener(eventType, handler) }, [eventType, element]) }拆解一下这个模式的精髓步骤做什么为什么①useRef(callback)在组件生命周期内保持同一块存储空间ref 不触发渲染引用本身稳定② 依赖[callback]的 effect每次渲染后把最新函数写进ref.current让 ref 永远追平当前渲染③ 依赖只含[eventType, element]监听器只注册一次handler 是固定不变的转发器触发时才去读ref.current关键在第三步注册进 DOM 的handler本身从不变化它只是一个取号窗口——事件真正触发的那一刻才从callbackRef.current取出当下最新的回调去执行。于是我们同时拿到了两样东西⚡ 监听器绑定一次不随渲染反复增删✨ 每次触发都运行最新逻辑彻底告别 stale closure。 顺藤摸瓜项目里还有谁在用这个模式这个套路在 react-custom-hooks 里被反复复用是典型的一次抽象、处处受益1. useTimeoutsrc/hooks/useTimeout/useTimeout.jssetTimeout的回调同样会过期所以它用了完全一样的 ref 结构const callbackRef useRef(callback) useEffect(() { callbackRef.current callback }, [callback]) timeoutRef.current setTimeout(() callbackRef.current(), delay)2. useDebouncesrc/hooks/useDebounce/useDebounce.js防抖 Hook 直接基于useTimeout组合而成仅 8 行代码。回调的保鲜能力被它无缝继承——这正是自定义 Hooks 组合的威力。3. 事件家族的全家桶useClickOutside、useHover、useMediaQuery、useOnlineStatus、useLongPress等全部构建在useEventListener之上间接都享受到了最新回调的红利src/hooks/useClickOutside/useClickOutside.js点击外部关闭弹窗/下拉框src/hooks/useHover/useHover.js一行代码搞定悬停状态src/hooks/useMediaQuery/useMediaQuery.js响应式断点匹配src/hooks/useOnlineStatus/useOnlineStatus.js在线/离线状态src/hooks/useLongPress.js/useLongPress.js长按手势。 新手常见疑问Q为什么不干脆每次渲染都 remove 再 add能跑但属于用性能换简单。对于高频事件或重量级监听器反复绑定是实打实的浪费最新回调模式则让绑定成本归零。Q为什么在 effect 里更新 ref而不是渲染期间在 effect 中写入更符合 React 的执行时序渲染后、交互事件前完成也避免了在渲染阶段修改外部可观察状态的副作用行为上更稳妥。QuseRef 和 useState 存回调的区别useState更新会触发重渲染形成无意义的循环useRef只是安静的一块可变盒子更新它零成本、不渲染——这正是它胜出的地方。 一句话总结最新回调模式的本质是把回调是谁和回调何时被调用解耦ref 负责永远持有最新版本稳定的 handler 负责在事件触发时现取现用。掌握了它你就能看懂 react-custom-hooks 中useEventListener、useTimeout以及几乎所有基于事件的 Hook 背后的统一心法写自己的 React 自定义 Hook 时也不会再踩过期闭包的坑。【免费下载链接】react-custom-hooksReact Custom Hooks S.Leschev: useArray useAsync useClickOutside useCookie useCopyToClipboard useDarkMode useDebounce useDebugInformation useDeepCompareEffect useEffectOnce useEventListener useFetch useGeolocation useHover useLongPress useMediaQuery useOnlineStatus useOnScreen usePrevious useRenderCount useScript etc.项目地址: https://gitcode.com/gh_mirrors/re/react-custom-hooks创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表