react-spring useTransition列表全量动画异常求助
解决react-spring useTransition列表动画异常问题
核心问题拆解
- 全量动画触发根源:要么列表项的
key不稳定(比如用数组索引),要么组件未正确缓存,导致删除顶部项时所有子组件被迫重新渲染,触发不必要的过渡动画 - 上下删除表现差异:从下往上删时,剩余项的
key和对应内容的映射关系没被打乱;从上往下删时,如果用索引当key,后面的项会因为索引前移被react-spring判定为“旧项移除、新项创建”,进而触发全量动画
关键修复步骤
1. 给列表项用唯一稳定的key
绝对别用数组索引当key,必须用每个Item自身的唯一标识(比如item.id)。react-spring的useTransition完全依赖key识别元素的增删,key不稳定直接导致过渡逻辑混乱。
2. 用React.memo缓存单个Item组件
把Item组件用React.memo包裹,避免父组件重渲染时无意义的子组件更新:
const Item = React.memo(({ item, style, onDelete }) => { return ( <animated.div style={style}> <div>{item.content}</div> <button onClick={() => onDelete(item.id)}>删除</button> </animated.div> ); });
注意:如果Item接收的props是引用类型(比如删除回调),必须保证props的引用稳定,否则memo会直接失效。
3. 结合RTK Query稳定数据与回调
- 从RTK Query取数据时,直接用
useGetItemsQuery返回的data即可,RTK Query会自动缓存数据,避免不必要的重渲染 - 删除操作的回调用
useCallback包裹,确保引用稳定,防止触发Item组件的无意义更新:
const ItemList = () => { const { data: items = [] } = useGetItemsQuery(); const dispatch = useDispatch(); const handleDelete = useCallback((itemId) => { dispatch(deleteItem(itemId)); }, [dispatch]); const transitions = useTransition(items, { keys: item => item.id, from: { opacity: 0, height: 0 }, enter: { opacity: 1, height: 'auto' }, leave: { opacity: 0, height: 0 }, }); return ( <div> {transitions((style, item) => ( <Item key={item.id} item={item} style={style} onDelete={handleDelete} /> ))} </div> ); };
4. 检查Redux状态更新逻辑
确保删除action的reducer是不可变更新,绝对不能直接修改原数组,必须返回新数组:
// RTK Slice示例 const itemsSlice = createSlice({ name: 'items', initialState: [], reducers: { deleteItem: (state, action) => { // 正确写法:过滤出不等于目标id的项,返回新数组 return state.filter(item => item.id !== action.payload); // 错误写法:直接修改原数组,比如state.splice(...) }, }, });
如果用RTK Query的mutation,也要保证mutation返回的更新逻辑是不可变的。
上下删除表现差异的本质
如果之前用索引当key,从上往下删时,比如删除索引0的项,原来索引1的项会变成索引0,react-spring会认为原来的索引0项被移除,索引1的项是“新创建”的,所以会触发所有后续项的离开+进入动画;而从下往上删时,前面的项索引不变,react-spring只会处理被删除的项,所以表现正常。换成唯一id当key后,每个项的身份唯一,不管删除哪个位置的项,react-spring都能精准识别出被删除的项,只触发它的离开动画,其他项不受影响。
内容的提问来源于stack exchange,提问作者plaglamist
相关产品推荐
相关产品推荐

