笔记 | react-transition-group 实现路由切换过渡动画
一、react-transition-group 使用
相关技术的使用:
- React 18
- React router v6
React Transition Group 是一个 React 库,专门用于在 React 应用中管理和处理过渡动画效果。这个库提供了一组组件,包括 Transition、CSSTransition、SwitchTransition 和 TransitionGroup,帮助在组件的进入和退出时应用动画效果。
Transition 是一个与平台无关的组件,通常结合 CSS 完成样式。
CSSTransition 是一个常用的组件,广泛用于添加过渡动画效果。它具有动画的作用时间(timeout)和指定元素首次渲染在页面时是否进行动画(appear)等参数。
SwitchTransition 用于在两个组件显示和隐藏切换时使用。
TransitionGroup 将多个动画组件包裹在其中,一般用于列表中元素的动画。执行中有三个状态:appear,enter,exit,这需要定义对应的 CSS 样式。
React Transition Group 可以应对大量常见简单动画,但如果需要编写高级动画,建议使用其他库如 react-spring、framer-motion 等。
Layout的示例伪代码
import { CSSTransition, SwitchTransition } from 'react-transition-group';
import { useLocation, useNavigate, useOutlet } from 'react-router-dom';
const currentOutlet = useOutlet();
...
<Layout className="site-layout">
<Content className="site-content">
<SwitchTransition mode="out-in">
<CSSTransition
key={location.pathname}
appear={true}
timeout={300}
classNames="page"
unmountOnExit
>
{currentOutlet}
</CSSTransition>
</SwitchTransition>
</Content>
</Layout>
因为 CSSTransition 的 classNames 为 page:
// 页面切换过渡动画
.page {
position: absolute;
left: 15px;
right: 15px;
}
// 页面切换过渡动画 --- 进入
.page-enter {
opacity: 0;
transform: scale(1.1);
}
// 页面切换过渡动画 --- 进入(被激活)
.page-enter-active {
opacity: 1;
transform: scale(1);
transition: opacity 300ms, transform 300ms;
}
// 页面切换过渡动画 --- 离开
.page-exit {
opacity: 1;
transform: scale(1);
}
// 页面切换过渡动画 --- 离开(被激活)
.page-exit-active {
opacity: 0;
transform: scale(0.9);
transition: opacity 300ms, transform 300ms;
}
注意事项:
1.网上可以搜到的许多文章里都提到,组件间的切换动画需要使用 TransitionGroup 来包裹。但是在官网最新的介绍里可以看到这段话
- 第一反应可能是在 TransitionGroup 中包裹所有路由,但这种方法需要 hack 并且在与 React Router 的更棘手的组件(如 Redirect)一起使用时很容易崩溃。 您应该为每个路由使用 CSSTransition 并自行在prop中管理它们。”
2.使用 SwitchTransition 包裹 CSSTransition,这样不会导致页面上同时展示 A 页面和 B 页面的组件,反之使用 TransitionGroup 进行包裹,就会产生两个页面同时存在的情况。
3.最重要的一点: 不要直接使用 Outlet 及<Outlet />
,而要使用useOutlet。在我没阅读官网的代码前,我尝试使用 SwitchTransition 和 CSSTransition 对 <Outlet />
进行包裹,来实现子路由组件的展示。结果:
- 第一个页面的离开的动画丢失了。
- 第二个页面会被加载两次,直观反映就是页面闪烁,
useEffect(()=>{},[])
或者componentDidMount()
被调用两次。
因为<Outlet />
会导致在同一页面的新旧组件各调用一次接口,所以请使用 useOutlet。
二、 <Outlet />
与 useOutlet 区别
<Outlet />
和useOutlet
都是在React Router中用来实现路由导出的功能,但是它们的使用方式和场景有所不同。
<Outlet />
:
<Outlet />
是一个 React 组件,通常在路由配置中使用,用于指定当前路由的子组件应该被渲染到哪个位置。它是一个受控组件,也就是说,它的值(即要渲染的组件)是由父组件(通常是路由组件)控制的。当父组件的状态发生改变时,<Outlet />
会自动更新渲染的组件。
例如,在路由配置中,你可以使用<Outlet />
来指定一个位置,用于渲染当前路由的子路由组件:
<Route path="/user/:userId" component={UserPage}>
<Outlet />
</Route>
在上面的例子中,当用户访问一个符合 /user/:userId
路径的URL时,UserPage组件会被渲染,并且子路由组件会渲染到 <Outlet />
指定的位置。
useOutlet
:
useOutlet
是一个自定义Hook,它允许你在函数组件中使用React Router的功能。通过使用 useOutlet
,你可以在组件中获取到当前路由的子路由组件,并将其渲染到指定的位置。与 <Outlet />
不同的是,useOutlet
是一个非受控组件,它的值是由调用 Hook 的代码控制的。useOutlet
是一个钩子函数,它用于在组件中获取当前嵌套路由的信息。如果嵌套路由没有挂载,则 useOutlet
返回 null;如果嵌套路由已经挂载,则 useOutlet
返回嵌套的路由对象。通过使用 useOutlet
,可以在组件中获取嵌套路由的相关信息,例如路径、查询参数等。
下面是使用 useOutlet
的示例:
import { useOutlet } from 'react-router-dom';
function MyComponent() {
const outlet = useOutlet();
return (
<div>
<h1>My Component</h1>
{outlet}
</div>
);
}
在上面的例子中,我们通过使用useOutlet
获取到一个outlet
对象,然后将这个对象渲染到组件中指定的位置。当路由发生变化时,React Router会自动更新渲染的子路由组件。
总的来说,<Outlet />
和 useOutlet
在 React Router v6 中都用于处理嵌套路由的相关操作。<Outlet />
更适合在 React Router 的路由配置中使用,用于在应用程序的当前路由中渲染嵌套的 Route 组件。而 useOutlet
则更适合在函数组件内部使用,用于在组件中获取当前嵌套路由的信息。