GatsbyJS v4.12.1直接访问URL时页面重复渲染问题求助
问题
使用GatsbyJS v4.12.1搭建的网站中,产品搜索页通过页面模板的page context传递产品数据。从菜单导航进入页面时运行正常,但直接通过URL访问该页面时,组件会出现两次渲染:先无产品显示,之后才加载出产品内容。
相关代码:
import * as React from 'react' import { useState } from 'react' import { Divider, Grid, Pagination, sliderClasses, Typography } from '@mui/material' import { makeStyles } from '@mui/styles' import FilterComponent from './sidebar-filter' import ProductCard from './product-card' import Accordion from '@mui/material/Accordion'; import AccordionSummary from '@mui/material/AccordionSummary'; import AccordionDetails from '@mui/material/AccordionDetails'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; const getFilterModel = (products) => { let brands = [] let types = [] let ages = [] let breeds = [] let features = [] let petTypes=[] products.forEach(product => { brands.push(product?.brand?.brandName) product.type?.forEach(type => types.push(type)) product.ageGroup?.forEach(age => ages.push(age)) product.breeds?.forEach(breed => breeds.push(breed)) product.special_care?.forEach(feature => features.push(feature.name)) petTypes.push(product.for_cats?'cats':'dogs') }); return { brands: [...new Set(brands)], types: [...new Set(types)], ages: [...new Set(ages)].sort(), breeds: [...new Set(breeds)], features: [...new Set(features)], petTypes: [...new Set(petTypes)] }; } const filterPrdcts = (products, filter) => { let petTypes = [] if (filter?.petTypes) { if (filter.petTypes.includes('cats')) { petTypes.push(true) } if (filter.petTypes.includes('dogs')) { petTypes.push(false) } } const filteredByBrands = (filter.brands.length > 0) ? products.filter(product => filter.brands.includes(product.brand.brandName)) : [...products] const filteredByTypes = (filter.types.length > 0) ? filteredByBrands.filter(product => filter.types.some(r => product.type.includes(r))) : [...filteredByBrands] const filteredByPetTypes = (petTypes.length > 0) ? filteredByTypes.filter(product => petTypes.includes(product.for_cats)) : [...filteredByTypes] const filteredByBreeds = (filter.breeds.length > 0) ? filteredByPetTypes.filter(product => filter.breeds.some(r => product.breeds?.includes(r))) :[...filteredByPetTypes] const filteredByAgeGroups = (filter.ages.length > 0) ? filteredByBreeds.filter(product => filter.ages.some(r => product.ageGroup.includes(r))) : [...filteredByBreeds] const filteredByFeature = (filter.features?.length > 0) ? filteredByAgeGroups.filter(product => filter.features.some(r => (product.special_care?.filter(feature => (feature.name === r)).length > 0))) : [...filteredByAgeGroups] return (filteredByFeature) } const useStyles = makeStyles(theme => ({ wrapper: { display: 'flex', flexDirection: 'row', }, productListWrapper: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: '2rem' }, hiddenOnMobile: { [theme.breakpoints.down('md')]: { display: 'none' } }, hiddenOnDesktop: { [theme.breakpoints.up('md')]: { display: 'none' } } })) const ProductList = ({ products, checked, handleChange, flipPage}) => { const [filteredProducts, setFilteredProducts] = useState(products) const classes = useStyles() const filterModel = getFilterModel(products) const itemsPerPage = 9 React.useEffect(() => { const filtered = filterPrdcts(products, checked) setFilteredProducts(filtered) }, [checked, products]) React.useEffect(()=> { return () => console.log('unmounting Productlist') },[]) return ( <Grid className={classes.wrapper} container > <Grid item xs={3} sx={{ paddingTop: '10px' }} className={classes.hiddenOnMobile}> <FilterComponent options={filterModel} handleChange={handleChange} checked={checked} /> </Grid> <Grid item xs={12} className={classes.hiddenOnDesktop}> <FilterAccordion options={filterModel} handleChange={handleChange} checked={checked} /> </Grid> <Grid item xs={12} md={9} sx={{ paddingTop: '10px' }}> <div className={classes.productListWrapper}> {filteredProducts?.slice((checked.page-1)*itemsPerPage,checked.page*itemsPerPage).map((product, index) => { return ( <React.Fragment key={index}> <ProductCard product={product} /> <Divider variant="fullWidth" className={classes.hiddenOnDesktop} sx={{width:'100%'}}/> </React.Fragment> ) })} </div> <Grid><Pagination count={(filteredProducts.length-filteredProducts.length%itemsPerPage)/itemsPerPage+1} onChange={flipPage} page={parseInt(checked.page)}/></Grid> </Grid> </Grid> ) } const FilterAccordion = ({ options, handleChange, checked }) => { return ( <Accordion > <AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls="panel1a-content" id="panel1a-header" > <Typography>Ürün Filtrele</Typography> </AccordionSummary> <AccordionDetails> <FilterComponent options={options} handleChange={handleChange} checked={checked} /> </AccordionDetails> </Accordion> ) } export default ProductList
解决方案
原因分析
直接URL访问时,Gatsby的SSR/SSG阶段可能未正确传递page context中的products数据,导致组件首次渲染时products为空;后续客户端hydration阶段拿到完整数据后再次渲染,从而出现“先空白后加载”的现象。另外,filteredProducts的初始化逻辑未考虑过滤规则,也会加剧这种渲染差异。
具体修复步骤
优化状态初始化逻辑
将filteredProducts的初始值直接设置为过滤后的结果,避免初始状态与后续过滤结果不一致:const [filteredProducts, setFilteredProducts] = useState(() => filterPrdcts(products || [], checked))使用函数式初始化可以避免每次渲染都执行过滤逻辑,提升性能。
添加加载状态兜底
在组件开头判断products是否存在或为空,显示加载提示避免空白:if (!products?.length) { return <div style={{padding: '2rem', textAlign: 'center'}}>Ürünler yükleniyor...</div> }确保page context数据在SSR/SSG时正确传递
检查页面模板的createPage逻辑,确保在构建阶段就获取到完整的产品数据并传入page context,而不是仅在客户端异步获取。例如:// gatsby-node.js中的createPage部分 createPage({ path: '/urunler', component: require.resolve('./src/templates/product-list.js'), context: { products: await fetchProductsData(), // 确保这里同步获取数据 }, })优化useEffect依赖与逻辑
确保useEffect在products或checked变化时正确更新过滤结果,同时避免不必要的重渲染:React.useEffect(() => { if (!products) return const filtered = filterPrdcts(products, checked) setFilteredProducts(filtered) }, [checked, products])
内容的提问来源于stack exchange,提问作者Ergun
相关产品推荐
相关产品推荐

