--- name: nextjs-view-transitions description: Next.js 中 View Transitions API 的使用指南 metadata: type: reference --- # Next.js 中的 View Transitions ## 设置 `` 开箱即用,支持 `startTransition`/`Suspense` 更新。若要对 `` 导航也启用动画: ```js // next.config.js const nextConfig = { experimental: { viewTransition: true }, }; module.exports = nextConfig; ``` 这会为每个 `` 导航包裹 `document.startViewTransition`。任何 `default="auto"` 的 VT 会在**每次**链接点击时触发——请使用 `default="none"` 来防止动画冲突。 **不要**安装 `react@canary`——详见 SKILL.md 的"可用性"章节。 --- ## Next.js 实现补充 在遵循 `implementation.md` 时,应用以下补充内容: **步骤 2 之后:** 启用上述实验性标志。 **步骤 4:** 在 `` 上使用 `transitionTypes`——详见下文"`transitionTypes` Prop"章节的用法和可用性说明。 **步骤 6 之后:** 对于相同路由的动态段(例如 `/collection/[slug]`),使用 `key` + `name` + `share` 模式——详见下文"相同路由动态段过渡"章节。 --- ## 布局级别的 ViewTransition **如果页面有自己的 VT,不要在布局中添加包裹 `{children}` 的布局级 VT。** 嵌套 VT 在父级 VT 内部时,enter/exit 永远不会触发——页面级的 enter/exit 会静默失效。请完全移除布局级 VT。 一个裸露的 `` 在布局中只有在页面**没有**自己的 VT 时才能正常工作。 **布局在导航间持久存在**——`enter`/`exit` 仅在初始挂载时触发,路由变化时不触发。不要在布局中使用类型键映射。 --- ## `next/link` 上的 `transitionTypes` Prop 无需包装组件,在服务端组件中即可工作: ```tsx 查看产品 ``` 这替代了手动 `onNavigate` + `startTransition` + `addTransitionType` + `router.push()` 的模式。仅在非链接交互(按钮、表单)时保留手动 `startTransition`。 **可用性:** `transitionTypes` 需要启用 `experimental.viewTransition: true`,在 Next.js 15+ canary 构建版本和 Next.js 16+ 中可用。如果不可用,请使用 `startTransition` + `addTransitionType` + `router.push()`(参见下面的"程序化导航"章节)。检查方法:`grep -r "transitionTypes" node_modules/next/dist/`——如果没有结果,则回退到程序化导航。 --- ## 程序化导航 ```tsx 'use client'; import { useRouter } from 'next/navigation'; import { startTransition, addTransitionType } from 'react'; function handleNavigate(href: string) { const router = useRouter(); startTransition(() => { addTransitionType('nav-forward'); router.push(href); }); } ``` --- ## 使用 `router.replace` 实现服务端筛选 对于通过 URL 参数在服务端重新渲染的搜索/排序/筛选,使用 `startTransition` + `router.replace`。VT 会生效,因为状态更新位于 `startTransition` 内部: ```tsx 'use client'; import { useRouter } from 'next/navigation'; import { startTransition } from 'react'; function handleSort(sort: string) { const router = useRouter(); startTransition(() => { router.replace(`?sort=${sort}`); }); } ``` 包裹在 `` 中的列表项会以动画方式重新排序。这是 `patterns.md` 中客户端 `useDeferredValue` 模式的服务端组件替代方案。 --- ## 双层模式(方向动画 + Suspense) 方向滑动动画与 Suspense 展现动画可以共存,因为它们在各自不同的时机触发。请将方向 VT 放在**页面组件**中(而非布局): ```tsx
<骨架屏 />}> <内容 />
``` --- ## 将 `loading.tsx` 作为 Suspense 边界 Next.js 的 `loading.tsx` 是一个隐式的 `` 边界。在 `loading.tsx` 中,将骨架屏包裹在 `` 中;在页面中,将内容包裹在 `` 中: ```tsx // loading.tsx // page.tsx ``` 与显式 `` 规则相同:使用简单字符串 prop(而非类型映射),因为 Suspense 展现动画触发时不带过渡类型。 --- ## 跨路由共享元素 ```tsx // 列表页 {products.map((product) => ( {product.name} ))} // 详情页——相同的 name {product.name} ``` --- ## 相同路由动态段过渡 当在同一路由的不同动态段之间导航时(例如 `/collection/[slug]`),页面保持挂载状态——enter/exit 永远不会触发。请使用 `key` + `name` + `share`: ```tsx }> ``` - `key={slug}` 在变化时强制卸载/重新挂载 - `name` + `share="auto"` 创建共享元素的交叉淡入淡出效果 - 位于 `` 内部的 VT(不对 Suspense 设置 key)在加载期间保持旧内容可见 --- ## 服务端组件 - `` 在服务端组件和客户端组件中均可使用 - `` 在服务端组件中可用——无需 `'use client'` - 程序化导航的 `addTransitionType` 和 `startTransition` 需要客户端组件