使用 js 原生 API IntersectionObserver实现内容触底加载

页面初始化以及搜索参数变化时,假设调用fetchData方法请求数据;

(假设 filterValA、filterValB 是搜索条件)

const DEFAULT_CURRENT_PAGE = 1

const [total, setTotal] = useState(0);    // 数据总数
const [itemList, setItemList] = useState([]);    // 列表数据
const [currentPage, setCurrentPage] = useState(DEFAULT_CURRENT_PAGE);    // 当前页数

useEffect(() => {
    fetchData({ current: DEFAULT_CURRENT_PAGE, reset: true });
  }, [filterValA, filterValB]);

fetchData方法 :包含触底滚动逻辑

const fetchData = useRefCallback(
    async (params: { current?: number; reset?: boolean }) => {
      
      // 发起xhr请求
      const res = await getSomeData(someParams)
      
      // 存储数据总条目
      setTotal(res.total);

      // 处理数据
      // 如果是触发滚动加载,则在现有数据后接上新数据;
      // 如果是搜索后页面更新,则用新数据替换现有数据
      const data = res.data_list || [];
      const newData = params.reset ? data : itemList.concat(data);
      setItemList(newData);
      
      // 获取这批数据最后一个项目的id
      const lastItemId = newData.concat([]).pop()?.id;

      // 重点:无限滚动 observer 逻辑
      setTimeout(() => {    // 为了防止渲染未完成dom上没有新节点,延时500ms执行
        if (!scrollObserver) {
          // 首次初始化逻辑: 新建一个observer,定义好callback(在下方代码块)
          const observer = new IntersectionObserver(
            entries => callback(entries, observer),
            { threshold: [0, 1] },
          );

          // 将item id作为dom节点的ID,监听最后一个item
          const newTarget = document.querySelector(`#${lastItemId}`);
          newTarget && observer.observe(newTarget);

          // 保存observer对象
          setScrollObserver(observer);
        } else {
          // 二次及以后逻辑
          // 将item id作为dom节点的ID,监听最后一个item
          const newTarget = document.querySelector(`#${lastItemId}`);
          newTarget && scrollObserver.observe(newTarget);
        }
      }, 500);
    },
    [],
  );

observer监听对象视野进入阈值事件回调 


const callback = useRefCallback(
    (entries: IntersectionObserverEntry[], observer) => {
      const entry = entries[0];    // 一次只监听一个,所以可以直接取[0]
      if (entry?.intersectionRatio > 0) { 
        // 元素出现后,获取dom ID,解除对这个元素的监听
        const target = document.querySelector(`#${entry?.target.id}`);
        target && observer.unobserve(target);
        // 判断是否最后一页,否则调用fetchList拉取下一页数据并进行新元素的监听
        const isFinish =
          spoCount && currentPage >= Math.ceil(spoCount / DEFAULT_PAGE_SIZE);
        if (isFinish) return;
        fetchList({ current: currentPage + 1 });
        setCurrentPage(currentPage => currentPage + 1);
      }
    },
    [],
  );

使用自定义的useRefCallback公用方法,保证函数里取到最新的变量。

(useRefCallback原理另起文章详解)

import { useCallback, useRef } from 'react';

export const useRefCallback =  any>(
  fn: T,
  _?: React.DependencyList,
) => {
  const ref = useRef(fn);

  ref.current = fn;
  return useCallback((...args: any[]) => {
    const fn = ref.current;
    return fn(...args);
  }, []) as T;
};

你可能感兴趣的:(JavaScript,js,react.js,javascript,前端)