3、React Transition Group入门:安装与基本概念、CSSTransition组件详解、Transition组件详解、实战:列表动画
3.1 为什么需要React Transition Group?
说实话,我刚用React那会儿,做动画全靠CSS硬写。组件挂载、卸载的过渡效果?那叫一个头疼。后来发现了一个宝藏库——React Transition Group。
它不是什么动画引擎,不帮你算贝塞尔曲线,也不管你用什么CSS框架。它的核心就一件事:管理组件在进入、退出时的状态。说白了,就是告诉你「现在该加enter类了,现在该加entered类了」,剩下的动画效果你自己用CSS搞定。
我在项目中遇到过好几次这样的场景:弹窗关闭时直接消失,用户体验极差。用了这个库之后,加几行CSS就能实现淡出效果。嗯,真香。
3.2 安装与基本概念
安装很简单,一行命令搞定:
npm install react-transition-group
或者用yarn:
yarn add react-transition-group
这个库主要提供三个核心组件:
- Transition:最底层的组件,直接操作状态机
- CSSTransition:基于Transition封装,配合CSS类名使用
- TransitionGroup:管理一组列表项的进出动画
你想想看,这三个组件其实是一个递进关系。Transition是基础,CSSTransition是常用方案,TransitionGroup解决批量问题。我个人习惯,90%的场景直接用CSSTransition就够了。
核心概念:动画状态机
每个组件在进入或退出时,会经历四个状态:entering → entered → exiting → exited。你只需要在不同状态下定义不同的样式,过渡效果自然就有了。
3.3 CSSTransition组件详解
CSSTransition是我用得最多的组件。它接收一个in属性来控制显示隐藏,然后自动帮你切换CSS类名。
来看一个最简单的淡入淡出效果:
import { CSSTransition } from 'react-transition-group';
function FadeBox({ show }) {
return (
<CSSTransition
in={show}
timeout={300}
classNames="fade"
unmountOnExit
>
<div className="box">Hello World</div>
</CSSTransition>
);
}
对应的CSS这样写:
.fade-enter {
opacity: 0;
}
.fade-enter-active {
opacity: 1;
transition: opacity 300ms;
}
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: opacity 300ms;
}
为什么会这样?因为CSSTransition在组件进入时,会依次添加fade-enter和fade-enter-active类。退出时则添加fade-exit和fade-exit-active。你只需要在这些类里定义样式,动画就自动跑起来了。
小技巧:classNames属性可以传对象,自定义每个状态的类名。比如classNames={{ enter: 'my-enter', enterActive: 'my-enter-active' }}。我在项目中经常这么干,避免和全局样式冲突。
几个关键属性你得记住:
| 属性 | 类型 | 说明 |
|---|---|---|
| in | boolean | 控制显示/隐藏 |
| timeout | number | object | 动画持续时间,可分别设置enter/exit |
| classNames | string | object | CSS类名前缀或映射 |
| unmountOnExit | boolean | 退出后是否卸载DOM |
| mountOnEnter | boolean | 首次进入时才挂载DOM |
注意:timeout的值必须和CSS中的transition时长一致。我曾经因为timeout设了500ms,CSS里写了300ms,结果动画还没结束组件就被卸载了,画面直接卡住。嗯,这种bug找起来挺费劲的。
3.4 Transition组件详解
Transition是更底层的组件。它不关心CSS,只给你四个状态回调:onEnter、onEntering、onEntered、onExit、onExiting、onExited。
什么时候用Transition?当你需要更精细的控制时。比如你想在动画开始时触发一个数据请求,或者记录日志。
import { Transition } from 'react-transition-group';
const duration = 300;
const defaultStyle = {
transition: `opacity ${duration}ms ease-in-out`,
opacity: 0,
};
const transitionStyles = {
entering: { opacity: 1 },
entered: { opacity: 1 },
exiting: { opacity: 0 },
exited: { opacity: 0 },
};
function FadeWithTransition({ in: inProp }) {
return (
<Transition in={inProp} timeout={duration}>
{(state) => (
<div style={{
...defaultStyle,
...transitionStyles[state]
}}>
我是用Transition控制的
</div>
)}
</Transition>
);
}
你看,Transition用的是render props模式。它把当前状态作为参数传给你,你根据状态返回不同的样式或内容。这种方式更灵活,但代码量也更大。
我个人建议:能用CSSTransition就别用Transition。除非你需要做非CSS的动画(比如canvas动画),或者需要在动画过程中执行JS逻辑。
3.5 实战:列表动画
好了,理论说完了,咱们来点实战。列表动画是前端最常见的场景之一——新增、删除列表项时,希望有平滑的过渡效果。
这里要用到TransitionGroup组件。它包裹整个列表,配合CSSTransition实现每个项的进出动画。
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import { useState } from 'react';
function TodoList() {
const [items, setItems] = useState([
{ id: 1, text: '学习React Transition Group' },
{ id: 2, text: '写一个列表动画' },
]);
const addItem = () => {
const id = Date.now();
setItems([...items, { id, text: `新任务 ${id}` }]);
};
const removeItem = (id) => {
setItems(items.filter(item => item.id !== id));
};
return (
<div>
<button onClick={addItem}>添加任务</button>
<TransitionGroup>
{items.map(item => (
<CSSTransition
key={item.id}
timeout={500}
classNames="item"
>
<div className="todo-item">
<span>{item.text}</span>
<button onClick={() => removeItem(item.id)}>删除</button>
</div>
</CSSTransition>
))}
</TransitionGroup>
</div>
);
}
CSS部分:
.item-enter {
opacity: 0;
transform: translateX(-50px);
}
.item-enter-active {
opacity: 1;
transform: translateX(0);
transition: all 500ms ease;
}
.item-exit {
opacity: 1;
transform: translateX(0);
}
.item-exit-active {
opacity: 0;
transform: translateX(50px);
transition: all 500ms ease;
}
关键点:每个CSSTransition必须有一个唯一的key。TransitionGroup通过key来追踪哪些项是新增的、哪些是删除的。没有key,动画就乱套了。
我曾经在项目里犯过一个低级错误:给所有列表项用了同一个key。结果删除时动画全乱套,新增的项直接闪现出来。排查了半天才发现是key的问题。嗯,这种坑踩过一次就记住了。
3.6 避坑指南
最后分享几个我踩过的坑:
- timeout不匹配:CSS的transition时长和timeout属性必须一致,否则动画会中断或卡顿
- 忘记加unmountOnExit:如果不加,组件退出后仍然占据DOM空间,可能影响布局
- key重复:列表动画中key必须唯一且稳定,不要用index作为key
- 嵌套动画:如果列表项内部还有子动画,需要单独处理,TransitionGroup不会自动递归
好了,这一章的内容就到这里。下一章我们会深入探讨SwitchTransition和更高级的动画编排技巧。记得动手敲一遍代码,光看是学不会动画的。