Intersection Observer API 进入无限渲染循环 问题:1000 个元素的相同引用解决方案:useInView 每个元素

问题描述

用户开始滚动时,我尝试使用相交观察器 API 有条件地显示 CSS 网格中的项目,但它似乎进入了无限渲染循环。这是我的代码

这是我在 StackBlitz 上的实时代码link

此外,我想要实现的目标是在可以避免的情况下在屏幕上渲染太多项目。我不确定 display: none 是否真的减少了浏览器的工作量。如果这不是正确的方法,请告诉我。

感谢您阅读我的问题。非常感谢任何帮助。

解决方法

该组件未按您预期的方式工作,因为您对所有项目使用相同的引用。您可以使用 ref 来存储引用数组或使用列表项逻辑创建组件。

如果不想同时渲染所有项目,可以渲染一部分 (100),每次 scroll 到达末尾时,再渲染 100,以此类推。我建议 您可以使用 React.memo 来避免每次状态更新时都呈现项目:

PortfolioItem.js

const PortfolioItem = React.memo(({ ix }) => {
  const ref = useRef();
    const [inViewRef,inView] = useInView({
    threshold: 1,rootMargin: '0px',});
    const setRefs = useCallback(
    (node) => {
      ref.current = node;
      inViewRef(node);
    },[],//--> empty dependencies
  );

  return ( <GridItem bg={inView?"red.100":"blue.100"} ref={setRefs} _last={{ mb: 4 }} >
              <Center  border={1} borderColor="gray.100" borderStyle="solid" h={16} w="100%">
                Item {ix}
              </Center>
            </GridItem>)
});

PortfolioList.js

export const PortfolioList = ({ 
  title,count = 100
}: PortfolioListProps) => {
  const ref = useRef(null);
  const items = [...Array(1000)];
  const [index,setIndex] = useState(count);
  
  useEffect(()=> {
    const grid = ref.current;
    function onScroll(){
      if(grid.offsetHeight + grid.scrollTop >= grid.scrollHeight) {
           setIndex(prev => prev+count);
      }
    }
    grid.addEventListener("scroll",onScroll);
    return ()=> {
       grid.removeEventListener("scroll",onScroll);
    }
  },[]);

  return (
    <Box
      w="100%"
      mx="auto"
      rounded={{ md: `lg` }}
      bg={mode(`white`,`gray.700`)}
      shadow="md"
      overflow="hidden"
    >
      <Flex align="center" justify="space-between" px="6" py="4">
        <Text as="h3" fontWeight="bold" fontSize="xl">
          {title}
        </Text>
      </Flex>
      <Divider />
      <Grid
        p={4}
        gap={4}
        templateColumns="1fr 1fr 1fr 1fr"
        templateRows="min-content"
        maxH="500px"
        minH="500px"
        overflowY="auto"
        id="list"
        ref={ref}
      >
        {items.slice(0,index).map((pt,ix) => (
            <PortfolioItem ix={ix} key={`Postfolio__item-${ix}`}/>
          ))
         }
      </Grid>
    </Box>
  );
};

Working example

,

问题:1000 个元素的相同引用

您有 1000 个 GridItem 组件,它们都获得相同的回调引用 setRefs。尽管我们知道在任何给定时间有些人在视野中而其他人不在视野中,但它们都收到相同的 inView 值。最终会发生的是,每个项目都会覆盖之前设置的 ref,这样所有 1000 个项目都会收到一个布尔值 inView,表示列表中的最后一个项目是否在视图中——而不是它本身是否在视图中。>

解决方案:useInView 每个元素

为了知道每个单独的组件是否在视图中,我们需要为列表中的每个元素分别使用 useInView 钩子。我们可以将每个项目的代码移动到它自己的组件中。我们需要传递这个组件的编号 ixuseInView 钩子的选项(我们也可以只传递根引用并在此处创建 options 对象)。

import { Box,Flex,Text,useColorModeValue as mode,Divider,Grid,GridItem,Center } from '@chakra-ui/react';
import { useInView,IntersectionOptions } from 'react-intersection-observer';
import React,{ useRef } from 'react';

interface ItemProps {
  ix: number;
  inViewOptions: IntersectionOptions;
}

export const ListItem = ({ix,inViewOptions}: ItemProps) => {
  const {ref,inView}= useInView(inViewOptions);

  return (
    <GridItem bg={inView?"red.100":"blue.100"} ref={ref} _last={{ mb: 4 }} key={ix}>
      <Center  border={1} borderColor="gray.100" borderStyle="solid" h={16} w="100%">
        Item {ix}
      </Center>
    </GridItem>
  )
}


export type PortfolioListProps = {
  title: string;
};

export const PortfolioList = ({ 
  title,}: PortfolioListProps) => {
  const parRef = useRef(null);

  return (
    <Box
      w="100%"
      mx="auto"
      rounded={{ md: `lg` }}
      bg={mode(`white`,`gray.700`)}
      shadow="md"
      overflow="hidden"
    >
      <Flex align="center" justify="space-between" px="6" py="4">
        <Text as="h3" fontWeight="bold" fontSize="xl">
          {title}
        </Text>
      </Flex>
      <Divider />
      <Grid
        p={4}
        gap={4}
        templateColumns="1fr 1fr 1fr 1fr"
        templateRows="min-content"
        maxH="500px"
        minH="500px"
        overflowY="auto"
        id="list"
        ref={parRef}
      >
        {[...Array(1000)].map((pt,ix) => (
          <ListItem ix={ix} key={ix} inViewOptions={{
            threshold: 1,root: parRef?.current,}}/>
        ))}
      </Grid>
    </Box>
  );
};

StackBlitz Link

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...