如何在react-router-dom 6.4.3中用framer-motion实现嵌套子路由过渡
在React Router v6 Data API中实现嵌套路由过渡动画
我正在升级到最新版react-router-dom并使用新的Data APIs,需要用基于对象的路由重写路由逻辑,替代v5的<Routes> JSX语法。现在想实现v5里那种嵌套路由过渡效果——导航嵌套子路由时,仅子路由Outlet执行动画,父布局(比如Header)保持静止不重渲染。
在v5中,我通过给不同层级的<Routes>设置对应路由段的key,结合AnimatePresence实现:
<AnimatePresence> <Routes location={location} key={locationArr[1]}> <Route path="/" /> <Route path="/nested/*"> <AnimatePresence> <Routes location={location} key={locationArr[2]}> <Route path="/nested/1" /> <Route path="/nested/2" /> </Routes> </AnimatePresence> </Route> </Routes> </AnimatePresence>
但v6 Data API下没有了<Routes>组件,尝试用稳定useOutlet引用的方式实现时,嵌套路由完全不渲染,只有刷新页面才更新;如果把根布局的key改成location.pathname,嵌套路由能正常渲染,但整个父布局会重渲染,不符合需求。
解决方案:分层级管理动画与Outlet
核心思路是在每个路由层级的布局组件中单独处理动画,利用useLocation获取当前路由段,给对应层级的动画容器设置精准的key,确保只有变化的路由段触发动画,父布局保持稳定。
步骤1:定义路由结构(基于Data API)
首先用对象式路由定义嵌套结构:
const router = createBrowserRouter([ { path: "/", element: <RootLayout />, children: [ { path: "/", element: <Home /> }, { path: "nested", element: <NestedLayout />, children: [ { path: "1", element: <NestedPage1 /> }, { path: "2", element: <NestedPage2 /> }, ], }, ], }, ]);
步骤2:根布局组件(RootLayout)
根布局只处理顶级路由的动画,Header保持在动画容器外,确保不会重渲染:
import { useLocation, Outlet } from "react-router-dom"; import { AnimatePresence, motion } from "framer-motion"; export function RootLayout() { const location = useLocation(); // 取第一个路由段作为key,仅顶级路由变化时触发动画 const rootKey = location.pathname.split("/")[1] || ""; return ( <div> {/* Header始终保持,不受动画影响 */} <header style={{ padding: "1rem", background: "#eee" }}> <h1>全局Header</h1> </header> <AnimatePresence mode="wait"> <motion.div key={rootKey} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} transition={{ duration: 0.3 }} > {/* 渲染子路由(包括NestedLayout) */} <Outlet /> </motion.div> </AnimatePresence> </div> ); }
步骤3:嵌套布局组件(NestedLayout)
嵌套布局处理子路由的动画,同样只针对当前层级的路由段设置key:
import { useLocation, Outlet } from "react-router-dom"; import { AnimatePresence, motion } from "framer-motion"; export function NestedLayout() { const location = useLocation(); // 取第二个路由段作为key,仅嵌套子路由变化时触发动画 const nestedKey = location.pathname.split("/")[2] || ""; return ( <div> <p>嵌套路由父内容(不会随子路由重渲染)</p> <AnimatePresence mode="wait"> <motion.div key={nestedKey} initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }} transition={{ duration: 0.3 }} > {/* 渲染嵌套子路由 */} <Outlet /> </motion.div> </AnimatePresence> </div> ); }
步骤4:页面组件示例
export function Home() { return <div>首页内容</div>; } export function NestedPage1() { return <div>嵌套页面1</div>; } export function NestedPage2() { return <div>嵌套页面2</div>; }
关键说明
- 分层key设置:每个布局组件只关注自身层级的路由段(根布局取第1段,嵌套布局取第2段),确保只有当前层级路由变化时触发动画,父布局不会因子路由变化而重渲染。
- AnimatePresence模式:使用
mode="wait"确保前一个动画完成后再渲染新内容,避免布局闪烁。 - Outlet的稳定引用:每个层级的
Outlet由对应布局组件渲染,无需额外处理引用冻结问题,React Router会自动管理Outlet的更新。
这种方式完美复刻了v5中嵌套路由动画的效果,同时适配v6 Data API的对象式路由结构,满足导航嵌套子路由时仅子Outlet动画、父布局静止的需求。
内容的提问来源于stack exchange,提问作者Rutkula
相关产品推荐
相关产品推荐

