自定义React ImageGallery组件父组件状态变更时异常重挂载问题咨询
问题现象
我花了很长时间才发现该组件会在其父组件发生任意状态变更时重挂载,这会导致出现视觉异常。整体运行时,只要ImageGallery的父组件发生任意状态变化,就会触发所有下属ImageGallery实例的useEffect执行。
组件核心代码
function ImageGallery(props) { const [currentIndex, setCurrentIndex] = useState(0); const Direction = props.LTR === undefined ? true : props.LTR; const translateStr = Direction ? "translateX(" : "translateY("; const [height, setHeight] = useState(0); const [width, setWidth] = useState(0); useEffect(() => { console.log("Imagegallery mounted."); // Set size of image gallery to the largest image given. if (props.w === undefined) { setWidth(Math.max(...(props.srcs.map(o => { return o.w })))) } else { let biggestWidth = Math.max(...(props.srcs.map(o => { return o.w }))); if (biggestWidth > props.w) { setWidth(biggestWidth); } else { setWidth(props.w); } } if (props.h === undefined) setHeight(Math.max(...(props.srcs.map(o => { return o.h })))) else { let biggestHeight = Math.max(...(props.srcs.map(o => { return o.h }))); if (biggestHeight > props.h) { setHeight(biggestHeight); } else { setHeight(props.h); } } return ()=>{console.log("Image gallery dismounted.")} }, [props]); // eslint-disable-line react-hooks/exhaustive-deps return ( <div className="overflow-hidden position-relative mx-auto" style={{ "width": width ? (width + "px") : "", "height": height ? (height + "px") : "", "backgroundImage": `${props.bgPath ? "url(" + props.bgPath + ")" : ""}` }}> { props.srcs.map((imgProperties, index) => { return ( <img key={imgProperties.path} className="galleryPhoto mx-auto my-auto" height={imgProperties.h ?? height} width={imgProperties.w ?? width} src={imgProperties.path} alt={imgProperties.alt} style= {{ "transform": translateStr + -((currentIndex - index) * (Direction ? width : height) ) + "px)", "left": ((width - imgProperties.w) * 0.5) + "px", "top": ((height - imgProperties.h) * 0.5) + "px" }} /> ) }) } {/* TODO: add buttons going up and down for LTR == false*/} <div className="GalleryControls user-select-none"> <div className={`galleryButton leftGal px-2 d-flex align-items-center ${currentIndex === 0 ? "greyedOutGalButton" : ""}`} onClick={() => { if (currentIndex > 0) { setCurrentIndex(currentIndex - 1); } }}> < </div> <div className={`galleryButton rightGal px-2 d-flex align-items-center ${currentIndex === props.srcs.length - 1 ? "greyedOutGalButton" : ""}`} onClick={() => { if (currentIndex < props.srcs.length - 1 && props.srcs.length > 1) { setCurrentIndex(currentIndex + 1); } }}> > </div> </div> </div> ) } export default ImageGallery;
参考CSS
.galleryPhoto { position:absolute; left:0px; top:0px; transition: 0.5s ease; } .GalleryControls{ position:relative; width:100%; height:100%; transition: 0.2s ease; } .galleryButton{ position:absolute; background:rgba(48, 48, 48, 0.15); transition:0.15s ease; height:100%; color:#ffffff; font-size:1.2em; } .leftGal{ left:0px; } .rightGal{ right:0px; } @media screen and (min-width:768px){ .leftGal{ transform:translateX(-100%); } .rightGal{ transform:translateX(100%); } .GalleryControls:hover{ background:#FEFEFE14; } } .GalleryControls:hover > .leftGal { transform:translateX(0px); } .GalleryControls:hover > .rightGal { transform:translateX(0px); } .greyedOutGalButton{ opacity:30%; }
使用说明
- 该组件为自主开发,无需依赖第三方
react-image-gallery组件 - 必须传入
srcs属性,该属性是一个对象数组,每个对象至少包含h、w和path三个属性
使用示例
<ImageGallery h="360" w="360" bgPath="any360x360image.png" srcs={[ {path: "epicImage.png", h: 312, w: 312}, {path: "epicImage2.png", h: 360, w: 360}, {path: "epicImage3.png", h: 257, w: 270}]} />
问题根因与修复方案
根因1:useEffect依赖了整个props对象
React每次渲染都会生成新的props对象引用,哪怕props内部的属性值完全没有变化,只要父组件重渲染,依赖项[props]就会判定为变更,触发useEffect执行,你看到的挂载/卸载日志其实是useEffect的清理函数和执行函数被重复触发,大部分情况不是真的组件被卸载重挂载,只是宽高重新计算、状态重置导致的视觉异常。你加的eslint-disable-line react-hooks/exhaustive-deps注释屏蔽了依赖检查,导致这个问题没有提前暴露。
修复方法
把useEffect的依赖项替换为实际用到的属性即可,移除原有的eslint忽略注释即可通过检查:
useEffect(() => { // 原有逻辑不变 }, [props.w, props.h, props.srcs, props.bgPath]);
根因2:传入的props属性引用不稳定
如果父组件使用该组件时,是内联传入srcs数组、或者每次渲染都会生成新的对象/函数属性,哪怕属性值不变,引用变化也会触发useEffect执行。
修复方法
父组件使用时,把不会变化的属性提到组件外部定义,或者用useMemo、useCallback包裹动态属性,保证引用稳定:
// 父组件内稳定srcs引用 const gallerySrcs = useMemo(() => [ {path: "epicImage.png", h: 312, w: 312}, {path: "epicImage2.png", h: 360, w: 360}, {path: "epicImage3.png", h: 257, w: 270} ], []); // 传入稳定的引用 <ImageGallery h="360" w="360" bgPath="any360x360image.png" srcs={gallerySrcs} />
根因3:组件key不稳定
如果父组件渲染ImageGallery时传入了动态变化的key(比如随机值、时间戳),React会认为每次都是新的组件,直接卸载旧实例挂载新实例,导致真正的重挂载。
修复方法
给ImageGallery设置固定不变的key即可。
内容的提问来源于stack exchange,提问作者peanutbatterballs
相关产品推荐
相关产品推荐

