React中useState切换状态时DOM元素Fade动画未重新触发问题
问题场景与现象
我用array.map()渲染对象数组,切换筛选选项('All'、'BACKEND'、'FRONTEND'、'ML')时,通过useEffect更新data状态:
- 选中All时渲染全部数据
- 选中Backend时展示backendData
- 选中Frontend时展示对应数据
问题:数据渲染正常,但react-awesome-reveal的Fade淡入动画只有在页面刷新,或新渲染元素数量多于当前屏幕元素时才触发新增元素的动画。比如:
- 从'Frontend'(2个元素)切换到'Backend'(3个元素),前2个元素无动画,第3个有
- 从'All'(9个元素)切换到'ML'(2个元素),元素直接渲染无动画
推测是React复用了已存在的DOM元素,导致动画没有重新触发,希望切换选项后所有元素都能像页面刷新一样重新播放动画。已经试过用Array.from、扩展运算符克隆数组更新state,但没用,课程数据是硬编码从文件读取的。
相关代码
Courses 组件核心代码
import { backendCourses, frontendCourses, mlCourses, allCourses, } from "../../data"; export default function Courses() { const [selected, setSelected] = useState(); const [data, setData] = useState(allCourses); const list = [ { id: "all", title: "All" }, { /* 其他选项 */ }, ... ]; useEffect(() => { switch (selected) { case "all": setData(Array.from(allCourses)); break; case "backend": setData([...backendCourses]); break; case "frontend": setData([...frontendCourses]); break; case "ml": setData([...mlCourses]); break; default: setData([...allCourses]); } }, [selected]); return ( <div className="courses" id="courses"> <h1>Courses</h1> <ul> {list.map((item, index) => ( <CoursesList id={item.id} title={item.title} active={selected === item.id} setSelected={setSelected} /> ))} </ul> <div className="container"> {data.map((item, index) => ( <Fade delay={index * 500}> <div className="item"> <img src={item.img} alt={item.title} id={index}></img> <a href={item.url} target="_blank"> <h3>{item.title}</h3> </a> </div> </Fade> ))} </div> </div> ); }
CoursesList 组件代码
export default function CoursesList({ id, title, active, setSelected }) { return ( <li className={active ? "coursesList active" : "coursesList"} onClick={() => setSelected(id)} > {title} </li> ); }
解决方案
方法1:给Fade组件添加关联筛选状态的唯一key
React复用DOM是因为map的key未关联筛选状态,导致新旧元素key重复。把key设置为${selected}-${item.id}(假设每个课程有唯一id),切换筛选时所有元素的key都会变化,强制React重新渲染组件并触发动画:
<div className="container"> {data.map((item, index) => ( <Fade key={`${selected}-${item.id}`} delay={index * 500} > <div className="item"> <img src={item.img} alt={item.title}></img> <a href={item.url} target="_blank"> <h3>{item.title}</h3> </a> </div> </Fade> ))} </div>
如果课程没有唯一id,可以临时用${selected}-${index},但优先使用课程自身的唯一标识。
方法2:利用react-awesome-reveal的reset属性
Fade组件支持reset属性,当属性值变化时会重新触发动画。把selected作为reset的值,每次切换筛选选项都会触发动画重置:
<div className="container"> {data.map((item, index) => ( <Fade delay={index * 500} reset={selected} > <div className="item"> <img src={item.img} alt={item.title}></img> <a href={item.url} target="_blank"> <h3>{item.title}</h3> </a> </div> </Fade> ))} </div>
这个方法无需修改key,利用组件自身机制实现,更优雅。
方法3:先清空数据再加载新数据(不推荐)
在useEffect中先将data设为空数组,再加载目标数据,强制DOM先销毁再重建。但会有短暂闪烁,仅作为备选方案:
useEffect(() => { setData([]); setTimeout(() => { switch (selected) { case "all": setData([...allCourses]); break; case "backend": setData([...backendCourses]); break; case "frontend": setData([...frontendCourses]); break; case "ml": setData([...mlCourses]); break; default: setData([...allCourses]); } }, 0); }, [selected]);
内容的提问来源于stack exchange,提问作者UberNoob
相关产品推荐
相关产品推荐

