开发随笔:React使用IntersectionObserver实现无限下拉

17. 无限下拉的例子

参考博文

1. 工具介绍

  • IntersectionObserver: 一个元素是否在视窗中可见, 通过该对象实现对于DOM元素的异步监听功能的实现。

基本使用方法:

  • 创建一个IntersectionObserver的类函数,在类函数创建时,传入需要监听事件的回调处理函数
  • 主要用用于当元素和视窗发生交互时候,调用回调函数
  • 回调中的entities是所有监听对象组成的数组,通过监听单个entity的isIntersecting方法可以知道哪个元素与视窗发生了何种交互
  • 通过entity,target能够获得元素的DOM实例
  • 使用observer.unobserve可以取消监听某个DOM元素,利用observer.disconnect可以取消所有监听DOM

2. 使用该方法实现一个无限下拉

常规思路:只要滑到底部,就加载外部数据,之后延长列表即可

这种方式,用intersectionObserver实现非常简单,仅仅需要创建IntersectionObserver监听最后一个元素,然后滑动到底部后更换监听的元素即可。

3. 使用固定元素进行下拉思路

  • 监听底部的元素,当元素到达底部(底部元素显示在页面中的时候)
    • 从数据列表中加载需要加载的元素(比如一页5个元素,第二页应加载index为5-9的元素)
    • 重新监听对应新的元素的状态(利用disconnect解除原有的监听,使用observer方法实现监听)
  • 当滑动到顶部的时候:
    • 查询需要在列表中显示的元素的index
    • 重新监听对应元素的状态
  • 通过为顶部top元素提供margin的方法来模拟下拉过程中scroll的滑动距离
代码实例

详见注释

import React, {
      useState, useEffect, useRef } from "react";

const ITEMS_NUM = 20;

export const SlidingWindowScrolHook = props => {
     
  const {
      data, height } = props;
  // 目前的起始点和结束点的位置index
  const [start, setStart] = useState(0);
  const [end, setEnd] = useState(ITEMS_NUM);
  const topRef = useRef();
  const bottomRef = useRef();
  /*
  	这里使用observer的原因说一下,因为在new IntersectionObserver调用的时候是传入一个回调函数,而容易因为作用域闭包的问题,使值没有被更新,而使用useRef能够获得对应的值。
  */
  const observer = useRef();
 // 使用disconnect将保存的Observer实例中监听的函数全部解散
  const resetObserver = () => {
     
    observer.current.disconnect();
  };

  const initObserver = () => {
     
    const Observer = new IntersectionObserver(entries => {
     
      entries.forEach(elem => {
     
        if (elem.isIntersecting) {
     
          // 在创建元素的时候,定位了元素的第一个和最后一个子元素的位置
          // 通过id来实现对于第一和最后一个子元素的监听过程
          if (elem.target.id === "bottom") {
     
            // 确定新的加载的元素的index
            const newStart =
              end - 10 <= data.length - ITEMS_NUM
                ? end - 10
                : data.length - ITEMS_NUM;
            const newEnd = end + 10 <= data.length ? end + 10 : data.length;

            if (newStart !== start || newEnd !== end) {
     
              resetObserver();

              setStart(newStart);
              setEnd(newEnd);
            }
          } else if (elem.target.id === "top") {
     
            const newStart = start - 10 >= 0 ? start - 10 : 0;
            const newEnd =
              end === ITEMS_NUM
                ? ITEMS_NUM
                : end - 10 > ITEMS_NUM
                ? end - 10
                : ITEMS_NUM;
            /* 
              这里的判断条件一定要有
              因为当滑到底部的时候,因为我们是绑定第一个元素并且监听第一个元素是否出现,利用margin模拟滚动条的
              因此一定会出发top的方法,这里就是要判断如果start和newStart相同,说明是重叠的情况,就是上述的场景,因此需要禁用掉更新
            */
            if (newStart !== start || newEnd !== end) {
     
              resetObserver();

              setStart(newStart);
              setEnd(newEnd);
            }
          }
        }
      });
    });

    bottomRef.current && Observer.observe(bottomRef.current);
    bottomRef.current && Observer.observe(topRef.current);
    observer.current = Observer;
  };

  useEffect(() => {
     
    initObserver();
    return () => {
     
      resetObserver();
    };
  }, [end]);

  return (
    <ul style={
     {
      position: "relative" }}>
      {
     data.slice(start, end).map((elem, index) => {
     
        const id =
          index === 0 ? "top" : index === end - start - 1 ? "bottom" : null;
        const selectRef =
          index === 0 ? topRef : index === end - start - 1 ? bottomRef : null;

        const top = (index + start) * height;

        return (
          <li
            style={
     {
      top, height, position: "absolute" }}
            id={
     id}
            ref={
     selectRef}
            key={
     `items-${
       index}`}
          >
            {
     elem}
          </li>
        );
      })}
    </ul>
  );
};

4. 实验效果

你可能感兴趣的:(日常开发,前端学习,React学习笔记,react,无限下拉,javascript)