ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Motion 仓库 issue-2509 复盘:useInView 的 ref 必传签名与 root/margin 正确用法全解析

Motion 仓库 issue-2509 复盘:useInView 的 ref 必传签名与 root/margin 正确用法全解析 前端UI组件【免费下载链接】motionA modern animation library for React and JavaScript项目地址https://gitcode.com/GitHub_Trending/mo/motion点击查看免费下载本文以 motion 仓库的 issue-2509 处理计划为主线深入解析 React 版useInViewhook 的正确调用方式ref是必需的第一个参数root、margin、amount、once、initial各选项的真实语义与默认值。结合 use-in-view.ts 源码、底层 IntersectionObserver 封装viewport/index.ts以及 use-in-view.test.tsx 测试用例读者将掌握该 API 的正确写法、参数边界与排查文档示例错误的方法。一、背景一个由文档示例引发的 API 用法澄清2024 年有人向 motion原 framer-motion仓库提交了 issue-2509当时的useInView文档页面上root与margin两个章节的代码示例均以useInView(options)形式调用省略了必需的ref第一个参数。由于文档内容迁移至 motion.dev 后两个示例均已被修正本仓库为它制定了验证 关闭的 P3 计划记录在 plans/issues/issue-2509.md 中。这个 issue 看似只是修一处文档笔误但它暴露的是useInViewAPI 的一个关键事实ref是必需参数不是可选参数。任何跳过ref直接传 options 的调用都是错误的。本文接下来的全部内容都围绕这一事实展开。二、正确签名useInView(ref, options?)ref 必须第一个传仓库中 hook 的真实签名定义在 use-in-view.tsexport function useInView( ref: RefObjectElement | null, { root, margin, amount, once false, initial false, }: UseInViewOptions {} ) { const [isInView, setInView] useState(initial) // ... }从签名可以看出两条硬性约定第一个参数ref是RefObjectElement | null必传。它指向你要观察的目标 DOM 元素例如div ref{ref} /。useInView内部通过ref.current读取目标元素并交给inView()建立观察。第二个参数options是可选对象支持root、margin、amount、once、initial五个字段且once、initial有默认值见 use-in-view.ts。因此issue-2509 中被标记为错误的调用方式是// ❌ 错误把 options 当成了第一个参数缺少必需的 ref const isInView useInView({ root: container })修正后的正确写法是// ✅ 正确ref 必须作为第一个参数传入 const targetRef useRefHTMLDivElement(null) const containerRef useRefHTMLDivElement(null) const isInView useInView(targetRef, { root: containerRef })仓库中useInView由 index.ts 对外导出export { useInView, UseInViewOptions }说明它是公开 API 面的一部分文档示例的错误因此影响面较大。三、选项参数详解五个字段的语义与默认值UseInViewOptions接口定义于 use-in-view.ts其继承自底层InViewOptions剔除root、amount后重新以 React ref 形式声明export interface UseInViewOptions extends OmitInViewOptions, root | amount { root?: RefObjectElement | null once?: boolean amount?: some | all | number initial?: boolean }各字段逐一说明选项类型默认值作用rootRefObjectElement \| null视口undefined指定作为相交判定基准的容器元素相当于 IntersectionObserver 的root传 ref 对象而非 DOM 元素margin形如0px 100px -50px 0px的字符串无围绕root的偏移量会扩大或缩小判定区域见 InViewOptions 的类型约束支持 1~4 段px或%值amountsome \| all \| numbersome元素需要可见的比例阈值some即 0任意可见即触发all即 1全部可见才触发数字为 0~1 的自定义阈值oncebooleanfalse为true时只在首次进入视口时置true之后不再复位initialbooleanfalse初始状态值为true时 hook 一挂载isInView即为true其中margin选项在 issue-2509 中被纠正的示例写法为// ✅ 文档修正后的 margin 示例 const isInView useInView(ref, { margin: 0px 100px -50px 0px })这条 margin 的含义是右侧把判定区域向外扩大 100px底部向内收缩 50px常用于元素即将进入视口前提前触发的预加载场景。四、底层原理inView 与 IntersectionObserver 的实现细节useInView并不是自己实现观察逻辑而是把脏活交给inView函数。该函数位于 viewport/index.ts内部直接构造IntersectionObserverexport function inView( elementOrSelector: ElementOrSelector, onStart: ( element: Element, entry: IntersectionObserverEntry ) void | ViewChangeHandler, { root, margin: rootMargin, amount some }: InViewOptions {} ): VoidFunction { const elements resolveElements(elementOrSelector) const activeIntersections new WeakMapElement, ViewChangeHandler() const onIntersectionChange: IntersectionObserverCallback (entries) { entries.forEach((entry) { const onEnd activeIntersections.get(entry.target) if (entry.isIntersecting Boolean(onEnd)) return if (entry.isIntersecting) { const newOnEnd onStart(entry.target, entry) if (typeof newOnEnd function) { activeIntersections.set(entry.target, newOnEnd) } else { observer.unobserve(entry.target) } } else if (typeof onEnd function) { onEnd(entry) activeIntersections.delete(entry.target) } }) } const observer new IntersectionObserver(onIntersectionChange, { root, rootMargin, threshold: typeof amount number ? amount : thresholds[amount], }) elements.forEach((element) observer.observe(element)) return () observer.disconnect() }几个值得注意的实现事实阈值映射amount的字符串值与数值被映射到threshold映射表定义在同文件 viewport/index.tssome → 0、all → 1数字直接透传。一次性观察优化若onStart返回的不是函数即没有离开回调观察器会立即unobserve该元素——这正是once语义的底层支撑之一。返回值是清理函数inView返回() observer.disconnect()useInView在useEffect的 cleanup 中调用它确保组件卸载时观察器被正确回收。在 hook 侧use-in-view.ts 的useEffect把rootref 的.current、margin、amount组装成InViewOptions再调用inView依赖数组为[root, ref, margin, once, amount]意味着这些值变化时观察会重建。此外当ref.current尚未挂载为null或once已生效时useEffect会提前返回不建立观察use-in-view.ts——这也是 issue-2579ref.current首渲染为 null 时需重新注册被单列为独立 feature 的原因可见在 README 分类中它被标记为real bug shaped as feature见 plans/issues/README.md。五、测试验证单元测试如何锁定 API 行为仓库用 Jest mock IntersectionObserver 覆盖了useInView的关键行为测试文件为 use-in-view.test.tsx共五组用例挂载时返回falseuse-in-view.test.tsx不触发任何回调时isInView保持false。initial: true可改变初始值use-in-view.test.tsx初始即true。进入视口置trueuse-in-view.test.tsx观察器回调isIntersecting: true时状态翻转。离开视口复位falseuse-in-view.test.tsx反复进出时状态序列为[false, true, false, true, false]。once: true只触发一次use-in-view.test.tsx多次进出后结果只记录[false, true]。测试通过getActiveObserver()来自同目录的 mock-intersection-observer手动驱动回调不依赖真实浏览器视口因此行为可被 CI 稳定复现。这些用例直接印证了本文第二节、第三节所述的签名与默认值语义——尤其是once与initial的行为完全由测试锁定。六、仓库内自查方法如何验证 hook 源码没有文档示例错误issue-2509 计划还给出了一套可复用的自查手段用于确认错误示例只存在于外部文档、不残留于本仓库源码。计划的命令表见 plans/issues/issue-2509.md其中与本仓库相关的检查是grep -n example\|useInView({ packages/framer-motion/src/utils/use-in-view.ts期望结果为无匹配即 hook 源文件既没有携带 JSDocexample块也没有内联的useInView({ ... })调用形式。我已在当前仓库实测该命令退出码为 1无匹配与计划中的expected on success一致——证明错误示例从未进入仓库源码仅存在于当时的外部文档页面。七、经验与正确用法速查issue-2509 的完整处理路径重新验证两条事实 → 回复 reporter → 在plans/issues/README.md状态行标记APPROVED后才执行 gated close记录于 plans/issues/issue-2509.md。它对开发者最有价值的启示有三点文档示例错误 ≠ 源码 bug核实 API 用法时应以源码签名为准。本仓库中useInView的签名与测试均正确错误仅存在于仓库之外的文档页面因此该 issue 按docs-only finding处理见 plans/issues/README.md 中docs-only findings are out of scope的约定。ref必传是硬约束所有useInView调用都必须形如useInView(ref, options)options可以省略ref不可以。可复制的最小正确用法import { useRef } from react import { useInView } from framer-motion function LazySection() { const ref useRefHTMLDivElement(null) // 基础用法元素任意部分进入视口即触发一次 const isInView useInView(ref, { once: true }) // 带 root 与 margin 的用法对应 issue 中被纠正的两个示例 // const isInView useInView(ref, { root: containerRef }) // const isInView useInView(ref, { margin: 0px 100px -50px 0px }) return div ref{ref}{isInView Content /}/div }深入阅读可继续参考use-in-view.ts、viewport/index.ts、use-in-view.test.tsx、issue-2509 计划。赞分享前端UI组件【免费下载链接】motionA modern animation library for React and JavaScript项目地址https://gitcode.com/GitHub_Trending/mo/motion点击查看免费下载相关推荐Motion 序列动画中 ref 元素目标完全受支持剖析 issue 2260 的「ref.current 读取过早」误报与正确用法Motion 序列动画中 ref 元素目标完全受支持剖析 issue 2260 的「ref.current 读取过早」误报与正确用法 本指南以 plans/i前端UI组件motion 仓库 issue-2444 复盘useDragControls React Portal 内存泄漏调查的 NEEDS-REPRO 方法论motion 仓库 issue 2444 复盘useDragControls React Portal 内存泄漏调查的 NEEDS REPRO 方法论 导前端UI组件Framer Motion 外部 ref 切换时重新水合修复解析以 issue-2263 看 motion 组件 ref 契约的实现与回归防护Framer Motion 外部 ref 切换时重新水合修复解析以 issue 2263 看 motion 组件 ref 契约的实现与回归防护 在 Frame前端UI组件上一篇如何把 kkFileView 接入 KingbaseES一份国产化文件预览与数据库备份落地指南下一篇如何快速完成一次完整的文件整理Files 文件管理器上手指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表