Next.js 面试题详细答案 - Q2
Q2: App Router vs Pages Router:为什么 Next.js 要推出 App Router?它带来了哪些根本性的变革?(基于 React Server Components、布局、流式传输等)
为什么推出 App Router
Pages Router 的局限性
-
客户端渲染为主
// Pages Router - 客户端组件 function HomePage() { const [data, setData] = useState(null) useEffect(() => { fetch('/api/data').then((res) => setData(res)) }, []) return <div>{data}</div> } -
布局嵌套复杂
// 需要手动处理布局嵌套 function Layout({ children }) { return ( <div> <Header /> {children} <Footer /> </div> ) } -
数据获取模式单一
// 只能在页面级别获取数据 export async function getServerSideProps() { const data = await fetchData() return { props: { data } } }
App Router 的根本性变革
1. React Server Components (RSC)
核心概念:服务端组件在服务器渲染,客户端组件在浏览器渲染
// Server Component (默认)
async function BlogPost({ slug }) {
const post = await db.posts.findBySlug(slug)
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
// Client Component (需要 'use client')
;('use client')
function LikeButton({ postId }) {
const [liked, setLiked] = useState(false)
return <button onClick={() => setLiked(!liked)}>{liked ? '❤️' : '🤍'}</button>
}
2. 文件系统路由革新
App Router 结构:
app/
layout.js // 根布局
page.js // 首页
loading.js // 全局加载状态
error.js // 全局错误处理
not-found.js // 404 页面
blog/
layout.js // 博客布局
page.js // 博客列表
[slug]/
page.js // 博客详情
loading.js // 博客详情加载状态
dashboard/
layout.js // 仪表盘布局
page.js // 仪表盘首页
settings/
page.js // 设置页面
3. 嵌套布局系统
// app/layout.js - 根布局
export default function RootLayout({ children }) {
return (
<html>
<body>
<nav>全局导航</nav>
{children}
<footer>全局页脚</footer>
</body>
</html>
)
}
// app/blog/layout.js - 博客布局
export default function BlogLayout({ children }) {
return (
<div>
<h1>博客</h1>
<aside>博客侧边栏</aside>
{children}
</div>
)
}
4. 流式传输 (Streaming)
// app/blog/page.js
import { Suspense } from 'react'
export default function BlogPage() {
return (
<div>
<h1>博客列表</h1>
<Suspense fallback={<div>加载中...</div>}>
<BlogList />
</Suspense>
<Suspense fallback={<div>推荐中...</div>}>
<Recommendations />
</Suspense>
</div>
)
}
async function BlogList() {
const posts = await fetch('/api/posts')
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
5. 数据获取模式变革
App Router 数据获取:
// 直接在组件中获取数据
async function ProductPage({ params }) {
const product = await fetch(`/api/products/${params.id}`)
return <div>{product.name}</div>
}
// 缓存控制
async function getData() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }, // 1小时缓存
})
return res.json()
}
6. 特殊文件功能
// loading.js - 自动加载状态
export default function Loading() {
return <div>加载中...</div>
}
// error.js - 错误边界
'use client'
export default function Error({ error, reset }) {
return (
<div>
<h2>出错了!</h2>
<button onClick={() => reset()}>重试</button>
</div>
)
}
// not-found.js - 404 页面
export default function NotFound() {
return <h2>页面未找到</h2>
}
核心优势对比
| 特性 | Pages Router | App Router |
|---|---|---|
| 渲染模式 | 客户端为主 | 服务端优先 |
| 布局系统 | 手动嵌套 | 自动嵌套 |
| 数据获取 | getServerSideProps | 直接 fetch |
| 代码分割 | 页面级别 | 组件级别 |
| 流式传输 | 不支持 | 原生支持 |
| 缓存策略 | 简单 | 精细化控制 |
迁移建议
- 新项目:直接使用 App Router
- 现有项目:逐步迁移,从新页面开始
- 混合使用:App Router 和 Pages Router 可以共存
总结
App Router 代表了 Next.js 的根本性范式转变:
- 从客户端到服务端:优先使用 Server Components
- 从页面到组件:更细粒度的控制
- 从静态到动态:更好的流式传输支持
- 从简单到智能:自动化的布局和缓存策略
这些变革让 Next.js 能够更好地处理现代 Web 应用的复杂需求,提供更好的性能和开发体验。