十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

@ice/appear 可见性事件组件深度解析:基于 React 的 appear/disappear 方案与版本演进

@ice/appear 可见性事件组件深度解析:基于 React 的 appear/disappear 方案与版本演进 ice/appear 可见性事件组件深度解析基于 React 的 appear/disappear 方案与版本演进【免费下载链接】ice ice.js: The Progressive App Framework Based On React基于 React 的渐进式应用框架项目地址: https://gitcode.com/gh_mirrors/ice1/iceice/appear是 ice.js 渐进式应用框架体系中负责元素进出可视区事件能力的轻量级 React 组件它为子元素注入appear进入可视区与disappear离开可视区两类事件并同时支持 Web 与 Weex 双端运行。本文以 packages/appear/CHANGELOG.md 记录的版本演进为骨架结合包内 README、源码与测试用例完整讲解该组件的使用方式、底层实现原理以及每个版本修复的问题帮助你理解并复用这一可见性检测方案。一、版本演进脉络从 VisibilityChange 到双端 Appearice/appear的 CHANGELOG 记录了从首个功能版本到当前 0.2.2 的完整演进过程短短几条记录恰好勾勒出该组件的能力边界与关键 bug 修复史版本类型变更内容v0.1.0feat支持VisibilityChange组件v0.1.4fix修复VisibilityChange子组件中 ref 无法找到的问题v0.1.5chore为 pegasus 组件升级版本0.2.0minor patch为 ice appear 增加 Weex 支持修复 ref 回调上的 listener 挂载问题0.2.1patch修复产物文件中的 sourceMap url 但未随产物发布 sourceMap 文件的问题0.2.2patch修复 WeexAppear 组件中 appear 事件处理器回调 ref 的问题从这条时间线可以清晰看到组件的能力成长先是 Web 端基础的可见性检测v0.1.0随后逐步打磨 ref 传递与事件监听的生命周期v0.1.4、0.2.0 patch再到跨端扩展 Weex0.2.0 minor最终补完发布产物与回调 ref 的细节0.2.1、0.2.2。当前包版本为 0.2.2对应 package.json 中的version字段。二、快速上手VisibilityChange 组件用法2.1 安装与最小示例安装命令与 README 中的示例用法如下npm i ice/appear -Simport VisibilityChange from ice/appear; export default function Home() { return ( VisibilityChange onAppear{() { console.log(Something has been shown.) }} onDisappear{() { console.log(Something has disappeard.) }} Anything you want to show. /VisibilityChange ); }组件的使用形态非常简洁将任意需要检测可见性的内容作为唯一的子元素传入通过Children.only约束并分别通过onAppear与onDisappear两个可选回调注册事件。当子元素进入可视区域时触发onAppear完全离开可视区域时触发onDisappear。2.2 Props 类型定义从 typings.ts 的AppearProps接口可以看到完整的类型契约export interface AppearProps { children: React.ReactElement; /** * Triggered when the element enters the visible area. * param {CustomEvent} e * returns {void} */ onAppear?: (e: CustomEvent) void; /** * Triggered when the element leaves the visible area. * param {CustomEvent} e * returns {void} */ onDisappear?: (e: CustomEvent) void; }两个关键约定值得注意children必须是单个 React 元素React.ReactElement多子元素会在运行时被Children.only抛错回调参数是CustomEvent事件对象上携带detail数据详见下文实现原理部分因此回调可以读取到触发事件的方向信息。三、双端分发入口如何选择 Web 与 Weex 实现包入口 src/index.tsx 负责按构建目标分发实现let Appear: React.ForwardRefExoticComponentAppearProps React.RefAttributesany; if (import.meta.target weex) { Appear WeexAppear; } else { Appear WebAppear as any; } export default Appear;这里的import.meta.target是构建期宏其类型在 src/runtime.d.ts 中声明为weex | web。这意味着面向 Web 的构建会打包./web目录下的WebAppear实现面向 Weex 的构建会打包./weex目录下的WeexAppear实现同一份业务代码无需改动即可适配两种渲染环境。这正是 0.2.0 weex support for ice appear 这一 minor 版本所引入的架构能力通过import.meta.target条件分发让组件同时服务 Web 与 Weex。四、Web 端实现原理IntersectionObserver 驱动的事件系统4.1 组件层ref 穿透与事件挂载Web 端组件 src/web/index.tsx 的核心逻辑分为三步第一步解析 ref。组件会优先复用子元素自带的 ref兼容对象形式 ref否则创建内部defaultRef对函数形式的 ref在useEffect中将其回填到当前节点以解决子组件内 ref 找不到的问题对应 v0.1.4 的修复const ref: RefObjectNode children.ref ? typeof children.ref object ? children.ref : defaultRef : defaultRef; useEffect(() { if (typeof children.ref function) { children.ref(ref.current); } }, [ref, children]);第二步挂载监听。listen回调通过addEventListener为子元素注册appear/disappear事件并返回清理函数用于组件卸载或回调变更时解绑对应 0.2.0 patch fixup listener attach for ref callback 关注的监听生命周期问题const listen useCallback( (eventName: string, handler: EventListenerOrEventListenerObject) { const { current } ref; // Rax components will set custom ref by useImperativeHandle. // So We should get eventTarget by _nativeNode. if (current isFunction(handler)) { const eventTarget current._nativeNode || current; observerElement(eventTarget as Element); eventTarget.addEventListener(eventName, handler); } return () { /* removeEventListener */ }; }, [ref], );这里有一个兼容细节注释与_nativeNode回退逻辑表明Rax 组件通过useImperativeHandle设置自定义 ref因此需要先尝试取current._nativeNode作为真实的事件目标节点该判断在 src/web/type.ts 的isFunction辅助下完成。第三步渲染。Children.only({ ...children, ref })将解析出的 ref 注入唯一子元素实现 ref 的透明穿透。4.2 事件层observerElement 与状态机判定可见性检测的核心在 src/web/visibility.ts。该文件fork 自raxjs/appear-polyfill维护了一个模块级共享的 IntersectionObserver 单例并优先使用浏览器原生能力const IntersectionObserver (function () { if (typeof window ! undefined IntersectionObserver in window IntersectionObserverEntry in window intersectionRatio in window.IntersectionObserverEntry.prototype) { return window.IntersectionObserver; } else { return PolyfilledIntersectionObserver; } })();默认观测配置为root: null视口、rootMargin: 0px、threshold: generateThreshold(10)——即生成[0, 0.1, 0.2, ..., 1]共 11 个阈值实现对进入/退出过程中 10% 粒度变化的监听。每次交叉回调handleIntersect中通过intersectionRatio与元素上的状态属性data-appeared/data-has-appeared/data-has-disappeared驱动一个简单状态机当intersectionRatio 0.01且此前未触发过 appear 时派发appear事件当intersectionRatio 0且此前处于已出现状态时派发disappear事件。每个事件都是new CustomEvent(eventName, { bubbles: false, cancelable: true, detail: data })其中detail携带direction: up | down——通过比较元素当前 Y 坐标与上次记录的data-before-current-y判断滚动方向。同时支持isonce/data-once一次性模式appearOnce函数检查该标记若设置了 once 则同一元素只触发一次 appear/disappear。4.3 Polyfill 层无原生支持环境的降级在IntersectionObserver不可用的旧浏览器中src/web/intersection-observer.ts 提供了完整的 polyfill 实现关键设计包括节流检测THROTTLE_TIMEOUT 100毫秒节流_checkForIntersections避免滚动/缩放期间高频计算多通道监控监听window resize、document scroll捕获阶段并在支持MutationObserver时对document进行全量子树变更观测以捕捉 DOM 增删导致的交叉变化shadow DOM 与 slot 支持getParentNode对 shadow root 返回 host、对 distributed slot 返回 slot 父节点保证嵌套场景下的相交矩形计算正确rect 计算_computeTargetAndRootIntersection沿祖先链裁剪相交矩形跳过display: none的元素并对百分比/像素rootMargin做统一解析。整个 polyfill 还维护了全局registry数组持有对 Observer 实例的强引用防止实例被垃圾回收导致观察失效。这正是observerElement在首次调用时懒创建单例、并提供destroyIntersectionObserver供销毁的原因。五、Weex 端实现原生事件桥接0.2.0 引入的 Weex 支持位于 src/weex/index.tsx采用与 Web 端完全不同的模型不再依赖 IntersectionObserver而是直接监听 Weex 环境派发的原生appear/disappear事件const WeexAppear forwardRefany, AppearProps((props, ref) { const internalRef useRefHTMLDivElement(null); const childrenRef: ForwardedRefHTMLDivElement ref ?? internalRef; useEffect(() { // Use copy of childrenRef to avoid ref value changed in cleanup phase. const nodeRef typeof childrenRef object ? childrenRef.current : null; const appearHandler (e: CustomEvent) { onAppear?.(e); }; // Return early if onAppear callback not specified. onAppear nodeRef?.addEventListener(appear, appearHandler); return () { onAppear nodeRef?.removeEventListener(appear, appearHandler); }; }, [childrenRef, onAppear]); // disappear 的 useEffect 结构与之对称 });这段实现有几个要点使用forwardRef暴露组件引用且子元素的 ref 优先使用外部传入的ref否则回退到内部internalRef两个useEffect分别管理appear与disappear监听并在清理阶段移除监听依赖数组包含childrenRef与对应回调保证 ref 或回调变化时重新挂载通过cloneElement(Children.only(children), { ref: childrenRef })将 ref 注入唯一子元素。v0.2.2 的修复点就落在这里此前appear事件处理器回调 ref在 WeexAppear 组件中的绑定存在缺陷0.2.2 的 commit451839c5修复了 appear 事件处理回调 ref 的问题同时 0.2.2 的代码中Use copy of childrenRef to avoid ref value changed in cleanup phase的注释也印证了组件对 ref 在卸载清理阶段可能变化这一边界情况的处理。六、测试验证测试用例如何覆盖核心行为packages/appear/tests/visibilityChange.test.tsx 使用 vitest jsdom testing-library/react 对 Web 端组件做了行为级验证两个用例分别覆盖appear 触发渲染带onAppear的组件后进入可视区的子元素应触发回调以 Promise resolve 作为断言子元素 ref 可用验证 VisibilityChange 包裹的子组件可以正常取得 ref——这正是 v0.1.4 the ref cant be find in child of VisibilityChange component 修复后回归保障的用例。从测试可以反推出组件的两条核心契约事件回调必须在元素可见后触发且 ref 必须穿透到子元素。这两个契约也分别对应 CHANGELOG 中 v0.1.0 的 feat 与 v0.1.4 的 fix。七、工程发布细节0.2.1 与 sourceMap 问题0.2.1 的变更内容sourceMap url in prod files but not publish with sourceMap file是一个典型的发布工程问题产物esm/index.js等中保留了//# sourceMappingURL注释但发布到 npm 的包里并未包含对应的.map文件。从 package.json 可见其构建脚本为build: tsc、prepublishOnly: npm run build入口为./esm/index.js因此该修复涉及的是 TypeScript 编译产物与 npm 发布内容的匹配性问题——这类细节虽然不影响运行时功能却直接影响开发者本地调试的源码映射体验。八、总结一套方案两种环境纵观 CHANGELOG.md 与仓库源码ice/appear的技术画像可以归纳为API 极简一个VisibilityChange组件、两个回调onAppear/onDisappear、一个唯一子元素约束即可完成可见性检测Web 端能力完备原生 IntersectionObserver 优先 完整 polyfill 降级支持方向判断up/down、一次性触发isonce/data-once与 Rax 自定义 ref 兼容Weex 端桥接干净通过import.meta.target构建期分流直接消费 Weex 原生事件与 Web 端共享同一套 Props 类型演进脉络清晰从单端基础功能v0.1.0到 ref 修复v0.1.4、双端支持0.2.0再到发布与回调细节的打磨0.2.1、0.2.2每一步都有对应的源码与测试佐证。如果你正在 ice.js 项目中需要滚动进入可视区再加载/曝光埋点之类的能力或希望在 Rax/Weex 场景复用同一套可见性事件抽象ice/appear的源码web 实现、weex 实现、visibility 状态机都是可以直接阅读和复用的参考实现。【免费下载链接】ice ice.js: The Progressive App Framework Based On React基于 React 的渐进式应用框架项目地址: https://gitcode.com/gh_mirrors/ice1/ice创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表