React setState异步导致手风琴组件过渡动画失效问题排查
嗨,我来帮你搞定这个手风琴过渡动画的问题~
核心问题:过渡失效的真正原因
先给你吃个定心丸:setState的异步特性不是罪魁祸首!你遇到的动画失效,大概率是因为列表渲染时缺失了key属性,导致React无法正确识别DOM元素的身份,直接重新创建了节点而非更新现有节点的类名——这样过渡动画自然就被跳过了。
另外你的第二种写法思路走偏了,我后面会详细解释为啥行不通。
第一步:修复列表的key属性
React渲染列表时必须给每个元素唯一的key,否则它会用“就地更新”的策略,直接销毁再重建元素,打断过渡动画。修改父组件的map渲染代码:
{data.map((node, index) => { return ( <li key={node.id || index} // 优先用node的唯一id,没有的话用index兜底 className="accordion-list-item" > <TestItem {...node} index={index} /> </li> ) })}
第二步:保留第一种写法并优化
你的第一种写法逻辑是正确的,只要补上key,动画就能正常触发。这里给你优化一下类名的拼接方式,用模板字符串更清晰:
const TestItem = ({ index, ...rest }) => { return ( <div className={`accordion-item ${index === this.state.activeIndex ? 'accordion-item-opened' : ''}`} onClick={() => { if (index !== this.state.activeIndex) { this.setState({ activeIndex: index }); } }} > <div className="accordion-item-inner"> {/* 组件其余内容 */} </div> </div> ) }
此时当你点击切换项时,React会正确更新对应元素的类名,DOM变化后,accordion-item-inner的max-height会从0平滑过渡到17rem,动画就能正常工作了。
为啥第二种写法行不通?
你在setState回调里修改test变量的思路有两个致命问题:
- 组件渲染时
test会被重置:每次TestItem重新渲染,let test = null都会执行,之前在回调里赋值的test会被覆盖,三元判断永远是index == null,自然不会生效。 - 修改变量不会触发重新渲染:React组件的UI更新依赖state或props的变化,你修改一个普通变量
test,组件不会重新渲染,类名也就不会更新。
所以这种方法完全不可行,别再往这个方向钻啦~
额外小提示
如果你的手风琴内容高度不固定,17rem可能会导致内容溢出或者过渡不自然,可以用ref获取内容的实际高度,动态设置max-height:
// 在TestItem里添加ref const innerRef = useRef(null); // 切换时获取高度 onClick={() => { if (index !== this.state.activeIndex) { this.setState({ activeIndex: index }, () => { if (innerRef.current) { const height = innerRef.current.scrollHeight; innerRef.current.style.maxHeight = `${height}px`; } }); } else { if (innerRef.current) { innerRef.current.style.maxHeight = '0'; } this.setState({ activeIndex: -1 }); // 收起所有 } }} // 给inner元素加ref <div className="accordion-item-inner" ref={innerRef}> {/* 内容 */} </div>
不过这个是进阶优化,你当前的固定高度方案只要补上key就能正常工作啦~
内容的提问来源于stack exchange,提问作者Neil
相关产品推荐
相关产品推荐

