React Infinite Loading 钩子,前一个触发器

React Infinite Loading hook, previous trigger

我正在尝试制作一个类似于 Waypoint 的挂钩。

我只想加载项目,然后当路径点超出屏幕时,如果到达路径点,允许它加载更多项目。

我似乎无法弄清楚让这项工作正常进行的逻辑。

目前它在屏幕上看到观察者状态。然后它快速获取数据。

我认为这是因为钩子每次都从 false 开始。我不确定如何让它成为现实,以便数据可以加载。再次到达时相反。

任何想法。

这里是钩子:

import { useEffect, useState, useRef, RefObject } from 'react';

export default function useOnScreen(ref: RefObject<HTMLElement>) {
  const observerRef = useRef<IntersectionObserver | null>(null);
  const [isOnScreen, setIsOnScreen] = useState(false);

  useEffect(() => {
    observerRef.current = new IntersectionObserver(([entry]) => {
      if (isOnScreen !== entry.isIntersecting) {
        setIsOnScreen(entry.isIntersecting);
      }
    });
  }, []);

  useEffect(() => {
    observerRef.current.observe(ref.current);

    return () => {
      observerRef.current.disconnect();
    };
  }, [ref]);

  return isOnScreen;
}

用法如下:

import React, { useRef } from 'react';
import { WithT } from 'i18next';

import useOnScreen from 'utils/useOnScreen';

interface IInboxListProps extends WithT {
  messages: any;
  fetchData: () => void;
  searchTerm: string;
  chatID: string | null;
}

const InboxList: React.FC<IInboxListProps> = ({ messages, fetchData, searchTerm, chatID}) => {
  const elementRef = useRef(null);
  const isOnScreen = useOnScreen(elementRef);

  if (isOnScreen) {
    fetchData();
  }

  
  const renderItem = () => {
    return (
      <div className='item unread' key={chatID}>
      Item
      </div>
    );
  };

  const renderMsgList = ({ messages }) => {
    return (
      <>
        {messages.map(() => {
          return renderItem();
        })}
      </>
    );
  };

  let messagesCopy = [...messages];

  //filter results
  if (searchTerm !== '') {
    messagesCopy = messages.filter(msg => msg.user.toLocaleLowerCase().startsWith(searchTerm.toLocaleLowerCase()));
  }

  return (
    <div className='conversations'>
      {renderMsgList({ messages: messagesCopy })}
      <div className='item' ref={elementRef} style={{ bottom: '10%', position: 'relative',backgroundColor:"blue",width:"5px",height:"5px" }} />
    </div>
  );
};

export default InboxList;

让我们检查一下这段代码

  const [isOnScreen, setIsOnScreen] = useState(false);

  useEffect(() => {
    observerRef.current = new IntersectionObserver(([entry]) => {
      if (isOnScreen !== entry.isIntersecting) {
        setIsOnScreen(entry.isIntersecting);
      }
    });
  }, []);

我们有以下含义:

  • .isIntersecting 为 TRUE --> 元素变得可见
  • .isIntersecting 为 FALSE --> 元素消失了

  • isOnScreen 为 TRUE --> 元素至少可见一次
  • isOnScreen 为 FALSE--> 元素从不可见

当使用异或 (!==) 时,您指定它:

  • 从不可见,只是变得可见
    • 在第一个十字路口后发生了 1 次
  • 曾经可见,现在消失了
    • 每次元素离开屏幕时都会发生 n 次

你想做的是每次元素相交时得到更多的项目

export default function useOnScreen(ref: RefObject<HTMLElement>, onIntersect: function) {
  const observerRef = useRef<IntersectionObserver | null>(null);
  const [isOnScreen, setIsOnScreen] = useState(false);

  useEffect(() => {
    observerRef.current = new IntersectionObserver(([entry]) => {
      setIsOnScreen(entry.isIntersecting);
    });
  }, []);
  
  useEffect(()=?{
    if(isOnScreen){
       onIntersect();
    }
  },[isOnScreen,onIntersect])

  ...
}

然后像这样使用它:

  const refetch= useCallback(()=>{
    fetchData();
  },[fetchData]);

  const isOnScreen = useOnScreen(elementRef, refetch);

或者简单地说:

  const isOnScreen = useOnScreen(elementRef, fetchData);

如果 fetchData 由于某种原因更改了引用,您可能需要改用以下内容:

  const refetch= useRef(fetchData);

  const isOnScreen = useOnScreen(elementRef, refetch);

记住 useOnScreen 必须像 onIntersect.current()

那样调用它

InboxList组件中,这段代码表达的意思

  if (isOnScreen) {
    fetchData();
  }

就是每次InboxList渲染时,如果屏幕上有航路点,则启动抓取,不管之前的抓取是否仍在进行中。

请注意,由于多种原因,InboxList 可能会在获取过程中重新呈现,可能会多次呈现,例如父组件重新渲染。只要航路点在屏幕上,每次重新渲染都会启动新的获取。

为了防止这种情况,我们需要跟踪正在进行的提取,类似于典型的 isLoading 状态变量。然后仅在 isLoading === false && isOnScreen.

时启动新的提取

或者,如果保证每次获取都会将路径点推离屏幕,那么我们可以仅在路径点即将出现在屏幕上时才启动获取,即​​isOnScreen 正在从 false 更改为 true :

useEffect(() => {
  if (isOnScreen) {
    fetchData();
  }    
}, [isOnScreen]);

但是,如果我们的假设(航点在每次获取时都离开屏幕)不成立,则这将无法正常运行。这可能发生,因为

  • pageSize的fetch small and display area可以容纳更多 元素
  • 由于以下原因,从提取中收到的数据被过滤掉 客户端过滤,例如searchTerm.

正如我的假设。你也可以这样试试

   const observeRef = useRef(null);
    const [isOnScreen, setIsOnScreen] = useState(false);
    const [prevY, setPrevY] = useState(0);
    
     
  useEffect(()=>{
    fetchData();

  var option = {
  root : null,
  rootmargin : "0px",
  threshold : 1.0 };

  const observer =  new IntersectionObserver(
      handleObserver(),
      option
    );
   
 const handleObserver = (entities, observer) => {
    const y = observeRef.current.boundingClientRect.y;
    if (prevY > y) { 
       fetchData();
    } 
    setPrevY(y);
  } 


},[prevY]);
   

在这种情况下我们不关注聊天消息。我们只关注 chat<div className="item 元素下方。当 div 元素被滚动条触发时,fetchData() 一次又一次地调用。 说明:

-首先在 hanlderObserver 中您可以看到 boundingClientRect.y. the boundingClientRect 方法读取元素位置。在这种情况下,我们只需要 y 轴,因为使用 y.

  • 当滚动条到达 div 元素时,y 值发生变化。然后再次触发 fetchData()。

  • root :这是用于交集的根。 rootMargin :就像边距 属性 一样,用于为根提供边距值,以像素或百分比 (%) 为单位。 threshold :当交叉路口的面积大于或等于我们在本例中提供的值时,用于触发回调的数字。 最后你可以添加加载数据的加载状态。

         return (
             <div className='conversations'>
               {renderMsgList({ messages: messagesCopy })}
               <div className='item' ref={observeRef} style={{ bottom: '10%', position: 'relative',backgroundColor:"blue",width:"5px",height:"5px" }} />
    
             </div>
           );
    

    };

我希望它是正确的,我不确定。可能对某人有帮助。谢谢..