React/Gatsby/Chakra UI项目千条MDX列表渲染DOM过多性能优化问询
你遇到的是大量DOM节点渲染导致的性能问题,用**虚拟滚动(窗口化渲染)**就能解决,实现逻辑是仅保留视口内的条目DOM,滚动时动态替换内容,DOM数量会直接降到30以内,Lighthouse性能评分可以升到90+。
具体实现步骤
- 安装虚拟滚动依赖
npm install react-window # 或者用yarn yarn add react-window
- 替换原有首页代码如下:
import * as React from "react"; import { ChakraProvider, chakra, Box, HStack, Button, VStack, Wrap, WrapItem, Badge, useMediaQuery } from "@chakra-ui/react"; import { graphql, useStaticQuery } from 'gatsby'; import { FixedSizeGrid as Grid } from 'react-window'; // 抽离单独的条目组件 const ItemCard = ({ node }) => { return ( <Box margin="2em" padding="1em"> <HStack padding="0.4em" align="center" alignItems="stretch" justifyContent="space-between"> <Button size="sm" shadow="md" colorScheme="blue" onClick={(e) => { e.preventDefault(); window.location.href=`/objects/${node.frontmatter.field1.toLowerCase()}`; }} >Detail</Button> <Box align="center"/> <VStack alignItems="end" justifyContent="right"> <Wrap columns={2} spacing={1} direction={"row-reverse"} isInline="true" shouldWrapChildren="true"> <WrapItem> {node.frontmatter.field5 === true && <Badge colorScheme="green">Field5</Badge> } </WrapItem> <WrapItem> {node.frontmatter.field6 === true && <Badge colorScheme="blue">Field6</Badge> } </WrapItem> <WrapItem> {node.frontmatter.field7 === true && <Badge colorScheme="orange">field7</Badge> } </WrapItem> <WrapItem> {node.frontmatter.field4 === true && <Badge colorScheme="red">Field8</Badge> } </WrapItem> </Wrap> </VStack> </HStack> <Box bg="gray.300" borderRadius="0.5em" margin="0em" padding="0em"> <chakra.h2 id={node.frontmatter.field1.toLowerCase()}> Field1: {node.frontmatter.field1} </chakra.h2> <chakra.p>Field2: {node.frontmatter.field2}</chakra.p> <chakra.p>{node.frontmatter.field3}</chakra.p> </Box> </Box> ) } const IndexPage = () => { const query = useStaticQuery(graphql` query AllObjects { allMdx(sort: {fields: frontmatter___field1}) { nodes { frontmatter { field1 field2 field3 field4 field5 field6 field7 uniqueId } } } } `) const allNodes = query.allMdx.nodes // 响应式判断列数,和原有SimpleGrid规则一致 const [isLg, isMd] = useMediaQuery(['(min-width: 1024px)', '(min-width: 768px)']) const columns = isLg ? 3 : isMd ? 2 : 1 // 计算总行数 const rows = Math.ceil(allNodes.length / columns) // 按你实际卡片高度调整,单位px const itemHeight = 300 // 容器宽度默认取视口宽度,也可以自己调整 const containerWidth = typeof window !== 'undefined' ? window.innerWidth : 1200 const itemWidth = containerWidth / columns return ( <ChakraProvider> <body> <main> <Grid columnCount={columns} columnWidth={itemWidth} height={typeof window !== 'undefined' ? window.innerHeight : 800} rowCount={rows} rowHeight={itemHeight} width={containerWidth} style={{ overflowX: 'hidden' }} > {({ columnIndex, rowIndex, style }) => { const index = rowIndex * columns + columnIndex const node = allNodes[index] if (!node) return null return ( <div style={style}> <ItemCard node={node} /> </div> ) }} </Grid> </main> </body> </ChakraProvider> ) }; export default IndexPage;
可选优化
如果你的卡片高度不固定,可以把FixedSizeGrid换成VariableSizeGrid,给不同高度的卡片设置对应行高即可;如果不想手动计算容器尺寸,可以搭配react-virtualized-auto-sizer自动适配父容器宽高。
内容的提问来源于stack exchange,提问作者Maximo.Wiki
相关产品推荐
相关产品推荐

