React:如何避免组件逻辑切换时的双渲染卡顿问题
问题描述
我通过state控制图片未加载时渲染占位组件,但图片加载完成瞬间,图片与占位组件会同时渲染,导致容器高度短暂翻倍后才移除占位组件。
若将图片改为条件渲染{loaded && <img onLoad={() => setLoaded(true)} src={props.imageURL} alt=""/>},则仅占位组件渲染,图片因无法触发onLoad始终无法加载。
另外需注意:占位组件并非图片,无法使用fallback源。请问有什么优化方案可以避免这种双渲染/卡顿问题?
我的代码如下:
export const StyledCard = (props: CardProps) => { const [loaded, setLoaded] = React.useState<boolean>(false); return ( <StyledContainer> <StyledImage onLoad={() => setLoaded(true)} src={props.imageURL} alt=""/> {!loaded && <ComponentWhichIsNotAnImageAndCannotBeUsedAsFallbackSource />} </StyledContainer> ) }
优化方案
方案1:初始隐藏图片,加载完成后显示
给图片设置初始隐藏样式,保证它能触发onLoad但不占据布局空间,加载完成后再显示并移除占位组件,避免高度跳动。
修改后的代码:
export const StyledCard = (props: CardProps) => { const [loaded, setLoaded] = React.useState<boolean>(false); return ( <StyledContainer> <StyledImage style={{ opacity: loaded ? 1 : 0, position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} onLoad={() => setLoaded(true)} src={props.imageURL} alt="" /> {!loaded && <ComponentWhichIsNotAnImageAndCannotBeUsedAsFallbackSource />} </StyledContainer> ) }
补充:如果容器需要自适应高度,可以给占位组件设置和图片最终一致的高度,或者提前通过接口获取图片尺寸并设置容器高度。
方案2:预加载图片后再渲染
在组件挂载时创建Image对象预加载图片,加载完成后再将图片渲染到DOM中,这样占位组件会直接被替换,不会出现两者同时存在的情况。
修改后的代码:
export const StyledCard = (props: CardProps) => { const [loaded, setLoaded] = React.useState<boolean>(false); React.useEffect(() => { const img = new Image(); img.src = props.imageURL; img.onload = () => setLoaded(true); // 可选:处理图片加载失败的情况 img.onerror = () => { console.error('图片加载失败'); }; }, [props.imageURL]); return ( <StyledContainer> {loaded && <StyledImage src={props.imageURL} alt="" />} {!loaded && <ComponentWhichIsNotAnImageAndCannotBeUsedAsFallbackSource />} </StyledContainer> ) }
方案3:用绝对定位让两者重叠
给容器设置相对定位,让图片和占位组件绝对定位在同一位置,这样即使两者短暂共存,也不会改变容器高度,视觉上无卡顿。
修改后的代码(含样式):
import styled from 'styled-components'; const StyledContainer = styled.div` position: relative; width: 100%; min-height: 200px; `; const StyledImage = styled.img` position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; `; export const StyledCard = (props: CardProps) => { const [loaded, setLoaded] = React.useState<boolean>(false); return ( <StyledContainer> <StyledImage onLoad={() => setLoaded(true)} src={props.imageURL} alt="" /> {!loaded && ( <ComponentWhichIsNotAnImageAndCannotBeUsedAsFallbackSource style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} /> )} </StyledContainer> ) }
内容的提问来源于stack exchange,提问作者Nick
相关产品推荐
相关产品推荐

