React真曝光Hook设计思路


需求背景

在前端开发中,我们经常需要统计页面元素的曝光数据,比如广告展示、商品曝光等。一个完善的曝光统计需要考虑以下场景:

  1. 元素是否在视口内可见
  2. 资源是否加载完成
  3. 页面切换状态时的重新曝光
  4. 防止重复曝光

核心设计思路

曝光判定机制

new IntersectionObserver((entries) => {
  for (const entry of entries) {
    exposedRef.current = entry.intersectionRatio >= 0.9;
    // ...
  }
}, {
  threshold: 0.9
});

设计要点:

  • 使用 IntersectionObserver API 替代传统的 scroll+getBoundingClientRect 方案
  • 设置 90%曝光阈值,确保元素充分展示
  • 使用 ref 记录曝光状态,避免重复触发

资源加载控制


const resourceLoaded = useRef(false);
const onResourceLoad = useCallback(() => {
  resourceLoaded.current = true;
  setTimeout(() => {
    if (!document.hidden && exposedRef.current) {
      invokeExposeCallback(onExposeRef.current);
    }
  }, 1);
}, []);

设计要点: 提供资源加载状态控制 使用 setTimeout 确保 DOM 布局稳定 同时判断页面可见性和曝光状态

页面可见性管理


const callbacks: Array<() => void> = [];
const onPageVisible = (cb: () => void) => {
  callbacks.push(cb);
};

document.addEventListener('visibilitychange', () => {
  if (!document.hidden) {
    triggerExpose();
  }
});

设计要点: 实现简单的发布订阅模式 统一管理页面可见性回调 支持多个曝光实例同时工作 4. 重渲染处理

useEffect(() => {
  if (elRef.current === preElRef.current) {
    return;
  }

  if (preElRef.current) {
    exposedRef.current = false;
    intersectionObserverRef.current!.unobserve(preElRef.current);
  }

  // ... 重新观察新元素
  preElRef.current = elRef.current;
});

设计要点: 比较新旧 ref 避免重复处理 清理旧元素的观察者 重置曝光状态

生命周期管理

useEffect(() => {
  onPageVisible(cb);
  return () => {
    exposedRef.current = false;
    intersectionObserverRef.current?.disconnect();
    offPageVisible(cb);
  };
}, []);

设计要点: 组件卸载时清理所有监听器 重置曝光状态 移除页面可见性回调

性能优化考虑

防抖处理:


setTimeout(() => {
  // 延迟处理资源加载后的曝光
}, 1);

缓存优化:


const onExposeRef = useRef(onExpose);
onExposeRef.current = onExpose;

条件判断优化:


if (elRef.current === preElRef.current) {
  return;
}

使用示例

function Banner() {
  const { ref, onResourceLoad } = useExpose({
    needLoadResource: true,
    onExpose: () => {
      // 上报曝光数据
      trackEvent('banner_expose');
    }
  });

  return (
    <div ref={ref}>
      <img
        src="banner.jpg"
        onLoad={onResourceLoad}
      />
    </div>
  );
}

扩展性考虑

  • 支持自定义曝光阈值
  • 支持多个资源加载完成的场景
  • 支持自定义曝光条件判断

总结

这个曝光 Hook 的设计充分考虑了:

  • 准确性:通过 IntersectionObserver 保证曝光判定准确
  • 性能:避免频繁 DOM 操作和重复计算
  • 可靠性:完善的生命周期管理和清理机制
  • 易用性:简单的 API 设计,开箱即用
  • 扩展性:预留配置项,支持自定义场景 这样的设计使得埋点代码与业务逻辑解耦,提高了代码的可维护性和复用性。