Gatsby/React:修改display:none元素背景图时为何仍出现重绘?
让我们一步步拆解你的问题,找出根源并给出解决方案:
1. useCallback依赖缺失引发的闭包问题
你的setNewWallpaper用了useCallback但依赖数组是空的,这会导致两个核心问题:
- 它捕获的
pool_of_images_fetched永远是组件第一次渲染时的空数组,get_new_wallpaper的逻辑会出现数据不一致; - 后续如果补全依赖数组,
setNewWallpaper的引用会随着pool_of_images_fetched更新而频繁变化,直接让PureFirst的onClickprops每次都改变,memo自然就失效了。
修复方式:重构get_new_wallpaper,摆脱对当前pool的依赖
修改get_new_wallpaper使用函数式更新来维护图片池,这样setNewWallpaper就不需要依赖pool_of_images_fetched了:
// 重写get_new_wallpaper,用函数式更新确保拿到最新池数据 const get_new_wallpaper = async (setPool) => { // 模拟获取新壁纸的逻辑 const newWallpaperUrl = await fetchRandomWallpaper(); // 函数式更新,避免依赖当前pool状态 setPool(prevPool => [...prevPool, newWallpaperUrl]); return newWallpaperUrl; }; // 重构setNewWallpaper,只保留稳定的依赖项 const setNewWallpaper = useCallback(async stillMounted => { const wallpaper = await get_new_wallpaper(setPool_of_images_fetched); stillMounted.value && setBackground(wallpaper); await wait(2000); const wallpaper2 = await get_new_wallpaper(setPool_of_images_fetched); stillMounted.value && setNextBackground(wallpaper2); }, [setPool_of_images_fetched, wait, get_new_wallpaper]);
2. onClick props传递错误
你直接把setNewWallpaper作为onClick传给组件,但setNewWallpaper需要接收stillMounted参数,点击时浏览器会把事件对象传进去,导致stillMounted.value是undefined,后续的状态更新根本不会执行。而且如果setNewWallpaper引用变化,会直接触发PureFirst重新渲染。
修复方式:包裹onClick回调
把onClick包裹成独立的回调,确保传递正确的stillMounted参数,同时避免因外部引用变化触发渲染:
// 在组件return中补充visible props,并修正onClick return ( <> <PureFirst visible={visible} background={background} onClick={() => setNewWallpaper({ value: true })} /> <PureSecond visible={visible} background={nextBackground} onClick={() => setNewWallpaper({ value: true })} /> </> );
3. 遗漏visible props导致样式异常
从你的代码看,PureFirst和PureSecond没接收visible props,但styled组件的display完全依赖这个prop。这会导致组件的display始终是none(因为props.visible是undefined),和你描述的"First处于显示状态"矛盾。遗漏这个props不仅会导致样式错误,还可能引发不必要的重绘。
修复方式:补全visible props传递
一定要把组件内的visible状态传给两个Pure组件,这样样式逻辑才能正常工作,也能避免因样式异常引发的重绘。
4. 浏览器重绘≠组件重新渲染
如果上述问题都修复了还是有闪屏,那可能不是组件重新渲染导致的,而是浏览器的重绘。即使组件没重新渲染,修改隐藏元素的background-image也可能触发浏览器的全局重绘,让你误以为是memo没生效。
优化方式:隔离重绘范围
用CSS的contain属性限制每个组件的重绘范围,避免影响其他元素:
const First = styled.div` display: ${props => (props.visible === 'first' ? 'unset' : 'none')}; background-image: url('${props => props.background}'); contain: layout style paint; /* 把布局、样式、绘制都隔离在组件内部 */ ` const Second = styled.div` display: ${props => (props.visible === 'second' ? 'unset' : 'none')}; background-image: url('${props => props.background}'); contain: layout style paint; `
验证memo是否真的生效
你可以给PureFirst加个日志,确认它有没有在nextBackground更新时重新渲染:
const PureFirst = memo(props => { console.log('PureFirst 重新渲染了'); return <First {...props} />; });
如果日志没在nextBackground更新时打印,说明memo已经生效,闪屏是浏览器重绘的问题,用上面的contain属性优化就好。
内容的提问来源于stack exchange,提问作者GWorking

