You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 20:15:39