4、React Transition Group进阶:SwitchTransition组件、TransitionGroup组件、自定义动画类名、实战:路由切换动画
好,咱们继续往下走。上一章我们把 CSSTransition 玩得挺透了,但实际项目中,动画场景远比「一个元素进进出出」要复杂。比如:两个组件切换时,旧的还没退场,新的就挤上来了,那画面简直没法看。
这一章,我就带你搞定这些「进阶玩法」。说白了,就是让你能优雅地处理多元素、多状态的动画编排。
4.1 SwitchTransition:让两个组件「有秩序」地切换
先说说 SwitchTransition。这玩意儿是干嘛的?
你想想看,一个 Tab 切换,或者一个登录/注册表单切换。旧的组件要退场,新的要进场。如果不用 SwitchTransition,默认行为是:新的直接出现,旧的瞬间消失——生硬得很。
SwitchTransition 的核心思想是:让退场动画和进场动画「串行」执行。先让旧的走完,再让新的进来。这样视觉上就流畅了。
关键点:SwitchTransition 只管理两个子元素的切换。它要求你给每个子元素一个唯一的 key,通过 key 的变化来触发动画。
来看个例子:
import { SwitchTransition, CSSTransition } from 'react-transition-group';
function Tabs({ activeTab }) {
return (
<SwitchTransition mode="out-in">
<CSSTransition
key={activeTab}
timeout={300}
classNames="tab"
>
<div className="tab-content">
{activeTab === 'login' ? <LoginForm /> : <RegisterForm />}
</div>
</CSSTransition>
</SwitchTransition>
);
}
这里有个 mode 属性,我重点说一下:
| mode 值 | 行为 | 适用场景 |
|---|---|---|
out-in |
旧元素先退场,退场完成后新元素再进场 | 页面切换、Tab 切换(推荐) |
in-out |
新元素先进场,进场完成后旧元素再退场 | 通知提示、徽章数字更新 |
我的习惯:大部分场景用 out-in。in-out 容易造成两个元素同时存在,布局会「跳」一下。除非你明确需要新元素先出现占位,否则别轻易用 in-out。
4.2 TransitionGroup:管理「一群」元素的动画
SwitchTransition 管的是两个元素的切换。那如果是一个列表呢?比如待办事项,新增一项、删除一项、排序——每个项都要有自己的进场和退场动画。
这时候就该 TransitionGroup 上场了。
TransitionGroup 是一个容器,它会监听它内部子元素的变化(增、删、排序),然后自动为每个子元素触发对应的动画生命周期。
我举个例子,一个待办列表:
import { TransitionGroup, CSSTransition } from 'react-transition-group';
function TodoList({ todos }) {
return (
<TransitionGroup component="ul" className="todo-list">
{todos.map(todo => (
<CSSTransition
key={todo.id}
timeout={500}
classNames="todo"
>
<li>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>删除</button>
</li>
</CSSTransition>
))}
</TransitionGroup>
);
}
注意看,TransitionGroup 本身不负责动画逻辑,它只负责「感知变化」。真正的动画效果,还是由它内部的 CSSTransition 来驱动。
我曾经踩过的坑:TransitionGroup 的 component 属性默认是 'div'。如果你传了 component="ul",但子元素是 li,那没问题。但如果你传了 component={null},它就不会包裹任何 DOM 节点,这时候你得自己处理布局。我建议新手先别碰 component={null},老老实实用默认的 div 或者指定一个语义化标签。
4.3 自定义动画类名:摆脱「enter/exit」的束缚
CSSTransition 默认的 classNames 是固定的:enter、enter-active、enter-done、exit、exit-active、exit-done。但有时候,我们需要更灵活的命名。
比如,你用了某个 CSS 动画库,它的类名是 fadeIn、fadeOut。或者你团队有自己的一套命名规范。
这时候,classNames 属性可以传一个对象:
<CSSTransition
timeout={300}
classNames={{
enter: 'fadeIn',
enterActive: 'fadeIn-active',
exit: 'fadeOut',
exitActive: 'fadeOut-active',
}}
>
<div>内容</div>
</CSSTransition>
这样,动画类名就完全由你控制了。说白了,就是给 React Transition Group 和你的 CSS 框架之间搭了一座桥。
我个人习惯:如果项目用了 Tailwind CSS,我会把自定义类名和 Tailwind 的 utility class 结合起来。比如 enterActive 写成 'opacity-100 translate-y-0 transition-all duration-300'。这样连 CSS 文件都不用写了。
4.4 实战:路由切换动画
好了,理论讲完了。咱们来点真格的——给 React Router 加上页面切换动画。
这是很多面试官喜欢问的,也是实际项目中非常常见的需求。说白了,就是让用户从一个页面跳到另一个页面时,视觉上有个过渡,而不是「啪」一下变了。
核心思路:用 SwitchTransition 包裹路由内容,用路由的 location.key 作为 key 值。
来看完整实现:
import { SwitchTransition, CSSTransition } from 'react-transition-group';
import { Routes, Route, useLocation } from 'react-router-dom';
function AnimatedRoutes() {
const location = useLocation();
return (
<SwitchTransition mode="out-in">
<CSSTransition
key={location.key}
timeout={300}
classNames="page"
>
<div className="page-wrapper">
<Routes location={location}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</div>
</CSSTransition>
</SwitchTransition>
);
}
对应的 CSS:
.page-enter {
opacity: 0;
transform: translateX(20px);
}
.page-enter-active {
opacity: 1;
transform: translateX(0);
transition: all 300ms ease-out;
}
.page-exit {
opacity: 1;
transform: translateX(0);
}
.page-exit-active {
opacity: 0;
transform: translateX(-20px);
transition: all 300ms ease-in;
}
注意:Routes 组件要显式传入 location={location},否则它不会感知到路由变化。这个细节我当初调试了半小时才找到原因。
为什么用 location.key 而不是 location.pathname?因为同一个路径下,如果只是 query 参数变了(比如 ?page=1 变成 ?page=2),pathname 没变,动画不会触发。而 key 每次路由变化都会生成新值,确保动画一定执行。
嗯,这里还要提一嘴:如果你用了 React Router v6,记得把 <Routes> 放在 <CSSTransition> 内部,并且用 location={location} 覆盖它的默认 location。否则动画会失效。
我曾经遇到过:在嵌套路由场景下,子路由变化时父级页面也跟着重新渲染了。解决方案是:只在最外层的路由容器做动画,子路由内部不要再用 SwitchTransition 包裹。否则动画会「套娃」,性能直接崩掉。
最后总结一下这一章的核心:
- SwitchTransition:两个元素切换,用 mode 控制先后顺序
- TransitionGroup:列表元素的增删排序动画
- 自定义类名:用对象形式覆盖默认的 enter/exit 类名
- 路由动画:SwitchTransition + location.key 实现页面切换过渡
下一章,我们会进入更高级的动画编排——用 Framer Motion 实现复杂交互动画。那玩意儿比 React Transition Group 更「声明式」,写起来也更爽。咱们到时候见。