Intersection Observer API 进入无限渲染循环
Intersection Observer API going into infinite rendering loop
我正在尝试使用交集观察器 API 在用户开始滚动时有条件地在 CSS 网格中显示项目,但它似乎进入了无限渲染循环。这是我的代码。
这是我在 StackBlitz 上的实时代码link
另外,我想要实现的是在我可以避免的情况下不在屏幕上渲染太多项目。我不确定 display: none
是否真的让浏览器工作得更少。如果这不是正确的方法,请告诉我。
感谢阅读我的问题。非常感谢任何帮助。
问题:1000 个元素上的相同引用
您有 1000 个 GridItem
组件,它们都获得相同的回调引用 setRefs
。他们都收到相同的 inView
值,即使我们知道在任何给定时间,有些在视野中,有些不在视野中。最终发生的是每个项目都会覆盖先前设置的 ref,这样所有 1000 个项目都会收到一个布尔值 inView
,表示列表中的最后一个项目是否在视图中——而不是它本身是否在视图中。
解决方案:每个元素 useInView
为了知道每个单独的组件是否在视图中,我们需要对列表中的每个元素单独使用 useInView
挂钩。我们可以将每个项目的代码移动到它自己的组件中。我们需要向该组件传递其编号 ix
和 useInView
挂钩的选项(我们也可以只传递根引用并在此处创建 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,
rootMargin: '0px',
root: parRef?.current,
}}/>
))}
</Grid>
</Box>
);
};
该组件未按您预期的方式工作,因为您对所有项目使用了相同的引用。您可以使用 ref
存储引用数组或使用列表项逻辑创建组件。
如果不想同时渲染所有item,可以渲染一部分(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>
);
};
我正在尝试使用交集观察器 API 在用户开始滚动时有条件地在 CSS 网格中显示项目,但它似乎进入了无限渲染循环。这是我的代码。
这是我在 StackBlitz 上的实时代码link
另外,我想要实现的是在我可以避免的情况下不在屏幕上渲染太多项目。我不确定 display: none
是否真的让浏览器工作得更少。如果这不是正确的方法,请告诉我。
感谢阅读我的问题。非常感谢任何帮助。
问题:1000 个元素上的相同引用
您有 1000 个 GridItem
组件,它们都获得相同的回调引用 setRefs
。他们都收到相同的 inView
值,即使我们知道在任何给定时间,有些在视野中,有些不在视野中。最终发生的是每个项目都会覆盖先前设置的 ref,这样所有 1000 个项目都会收到一个布尔值 inView
,表示列表中的最后一个项目是否在视图中——而不是它本身是否在视图中。
解决方案:每个元素 useInView
为了知道每个单独的组件是否在视图中,我们需要对列表中的每个元素单独使用 useInView
挂钩。我们可以将每个项目的代码移动到它自己的组件中。我们需要向该组件传递其编号 ix
和 useInView
挂钩的选项(我们也可以只传递根引用并在此处创建 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,
rootMargin: '0px',
root: parRef?.current,
}}/>
))}
</Grid>
</Box>
);
};
该组件未按您预期的方式工作,因为您对所有项目使用了相同的引用。您可以使用 ref
存储引用数组或使用列表项逻辑创建组件。
如果不想同时渲染所有item,可以渲染一部分(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>
);
};