App Router 中的 layout.js、page.js 和 template.js 的区别
在 Next.js App Router 中,page.js、layout.js 和 template.js 是构建路由和 UI 的三个核心文件。
简单来说:
page.js是内容(特定路由的 UI)。layout.js是框架(共享 UI,状态保留,不重新渲染)。template.js是特殊框架(共享 UI,状态重置,每次导航都会重新渲染)。
下面是详细的区别解析:
1. page.js (页面)
这是路由的叶子节点。它是特定 URL 路径下实际显示的 UI 内容。
- 作用:定义特定路由的 UI。
- 特性:
- 每个文件夹(路由段)必须有一个
page.js才能使该路径可访问。 - 它可以接收
params(路径参数) 和searchParams(查询参数)。
- 每个文件夹(路由段)必须有一个
- 生命周期:当你访问该路由时渲染。
2. layout.js (布局)
这是共享 UI,用于包裹多个页面。
- 作用:在多个路由之间共享 UI(如导航栏、侧边栏、页脚)。
- 特性(核心区别):
- 状态持久化 (Persists State):当在同一布局下的不同页面之间导航时,Layout 不会重新挂载(Remount)。这意味着组件的状态(如输入框的值、滚动位置)会保持不变。
- 高性能:因为不重新渲染,切换页面时只更新变化的部分(即 Page 内容),交互更流畅。
- 嵌套:Layout 可以嵌套(父路由的 Layout 包裹子路由的 Layout)。
- 典型场景:全局导航栏、Sidebar、全局 Context Provider。
3. template.js (模板)
这就有点像 layout.js 的孪生兄弟,但有一个关键区别。
- 作用:也是用于包裹页面和子布局的共享 UI。
- 特性(核心区别):
- 创建新实例 (Creates a new instance):当在共享同一个 Template 的路由之间导航时,Template 会卸载旧实例并挂载新实例。
- 状态重置:这意味着 DOM 元素会重新创建,状态(State)会丢失,
useEffect会重新执行。
- 典型场景:
- 进入/退出动画(如使用 Framer Motion,每次切换页面都需要触发动画)。
- 依赖于
useEffect的页面记录(如每次进入页面都要发送埋点数据)。 - 重置状态(如切换页面时希望重置表单数据)。
核心对比:Layout vs Template
这是最容易混淆的地方。假设你有一个计数器组件 Counter。
| 特性 | Layout (layout.js) |
Template (template.js) |
|---|---|---|
| 导航行为 | 路由切换时,组件保持不动 | 路由切换时,组件销毁并重建 |
| State (状态) | 保留 (计数器数字不变) | 重置 (计数器归零) |
| DOM | 不更新 (除非 Props 变了) | 重新创建 DOM 节点 |
| useEffect | 仅在首次加载时触发 | 每次路由切换都触发 |
| 主要用途 | 导航栏、页脚、持久化播放器 | 页面转场动画、埋点统计 |
渲染层级结构
Next.js 在渲染时,会将它们按照以下顺序嵌套:
jsx
<Layout>
<Template>
<ErrorBoundary> {/* error.js */}
<Suspense> {/* loading.js */}
<Page />
</Suspense>
</ErrorBoundary>
</Template>
</Layout>
注意:Template 是被包裹在 Layout 内部,但在 Page 外部的。
代码示例
假设文件结构如下:
plaintext
app/
dashboard/
layout.js
template.js
page.js
settings/
page.js
当你从 /dashboard 跳转到 /dashboard/settings 时:
app/dashboard/layout.js:- ⛔️ 不会 重新渲染。
- ✅ 其中的
<input>输入内容会保留。
app/dashboard/template.js:- 🔄 会 重新挂载。
- ❌ 其中的
useState状态会重置。 - ✨ 如果写了 CSS 动画,动画会重新播放。
app/dashboard/page.js(旧页面):- 🗑 被卸载。
app/dashboard/settings/page.js(新页面):- 🆕 被挂载。
总结建议
- 默认使用
layout.js。99% 的情况下,你想要的是布局(保留导航栏状态,减少重绘)。 - 仅在需要特定行为时使用
template.js。比如你需要每次切换页面都触发淡入淡出动画,或者你需要确保每次切换页面时某个组件的状态都被清空。