布局、嵌套与导航
布局、嵌套与导航 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
布局是「页面之间不变的那部分」。读完你应该能建立根布局和学习区嵌套布局,验证页面切换时布局不重载,并用 usePathname 做出当前路由高亮的导航。
问题场景
学习工作台顶部有全局导航(课程 / 练习 / 关于),学习区还有一个侧边栏(目录、进度)。如果每个页面都复制粘贴导航,改个 logo 要动所有页面;如果导航是普通组件,页面切换时它会被卸载重建——正在播放的音频、滚动位置、打开的下拉菜单都会丢。
核心解释
App Router 用 layout.tsx 解决:布局包裹子路由,页面切换时只替换 children,布局本身不重渲染。
// app/layout.tsx —— 根布局,必须有 <html> 和 <body>
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh-CN">
<body>
<SiteNav /> {/* 全局导航,任何页面切换都不重载 */}
{children}
</body>
</html>
);
}
嵌套布局建在子目录里,自动包裹该目录下所有页面:
// app/learn/layout.tsx —— /learn 下的页面共用
export default function LearnLayout({ children }: { children: React.ReactNode }) {
return (
<div style={{ display: "flex" }}>
<aside>学习区侧边栏</aside>
<section>{children}</section>
</div>
);
}
布局与页面的两个重要性质:
- 布局保留:从
/learn/a导航到/learn/b,只有 LearnLayout 的 children 变化,布局组件的 state、滚动位置原样保留——这就是把状态类组件放布局里的底气; - 错误隔离:某页面抛错,由它所在目录的 error.tsx 兜住,布局和导航仍然完好;布局本身报错才会向外冒泡到上一级。
导航高亮需要「当前路径」,而布局默认是服务端组件拿不到 pathname。官方姿势是一个 "use client" 的小导航组件:
// components/site-nav.tsx
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
const LINKS = [
{ href: "/courses", label: "课程" },
{ href: "/learn", label: "学习区" },
];
export function SiteNav() {
const pathname = usePathname();
return (
<nav>
{LINKS.map(l => (
<Link key={l.href} href={l.href} aria-current={pathname.startsWith(l.href) ? "page" : undefined}>
{l.label}
</Link>
))}
</nav>
);
}
注意职责划分:交互(读 pathname)收在客户端小组件里,布局仍然是服务端组件——这正是 N05 要展开的边界思维。
示例拆解
本课落地的文件与验证:
app/layout.tsx:根布局,挂 SiteNav 和全局样式;app/learn/layout.tsx:学习区布局,侧边栏 + children;components/site-nav.tsx:usePathname 高亮当前项;- 验证一:在 /learn 下两个页面间切换,侧边栏里放一个 useState 计数器(点一下加一),导航后计数不清零——布局状态保留的直接证据;
- 验证二:让某个 learn 页面抛错,观察侧边栏和顶部导航完好,只有内容区显示错误兜底。
动手任务
在项目里完成三个小改动:
- 建根布局和
/learn嵌套布局,SiteNav 放进根布局。 - 实现 usePathname 高亮,给当前项加下划线或 aria-current。
- 在 LearnLayout 侧边栏加一个 useState 计数器,页面间切换验证它不重置;再让一个页面 throw 看错误隔离。
常见错误
- 根布局缺
<html>或<body>:build 直接报错;整个应用只有根布局负责这两个标签。 - 把 pathname 逻辑写在服务端布局里:服务端组件没有 usePathname;拆一个 use client 的导航子组件出来。
- 用 useEffect + window.location 做「当前路径」:客户端路由切换不触发页面加载事件,高亮会失灵;用 usePathname 响应路由变化。
- 以为布局会随数据变化重渲染:布局默认不参与页面数据流,需要共享的全局数据要么放 Context(客户端),要么在布局里自己获取。
检查与复盘
完成下方检查题确认理解。自查清单:
- 能说出「children 替换、布局保留」的机制并用状态计数验证过
- 导航高亮在 use client 组件里用 usePathname 实现
- 页面级错误不拖垮布局,验证过隔离效果
参考资料
- Next.js 文档:layout.tsx 文件约定 —— 布局的完整规则与保留语义
- Next.js 文档:布局与页面 —— 嵌套与导航总览
- Next.js 文档:usePathname —— 客户端读当前路径的官方 Hook
读完这一课了?
标记完成后,工作台会安排接下来的学习。