React Server Components (RSC) 与 App Router 简介:Next.js 的未来范式 - 实践
<style>pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-size: 14px !important; line-height: 1.6 !important; padding: 16px !important; margin: 16px 0 !important; background-color: rgba(248, 248, 248, 1) !important; border: 1px solid rgba(225, 228, 232, 1) !important; border-radius: 6px !important; tab-size: 4 !important; -moz-tab-size: 4 !important; max-width: 100% !important; box-sizing: border-box !important }code { font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-size: 14px !important; white-space: pre !important; word-wrap: normal !important; word-break: normal !important; overflow-wrap: normal !important; display: inline !important; background: rgba(0, 0, 0, 0) !important; border: none !important; padding: 0 !important; margin: 0 !important; line-height: inherit !important }
pre code { background: rgba(0, 0, 0, 0) !important; border: 0 !important; border-radius: 0 !important; display: block !important; line-height: 1.6 !important; margin: 0 !important; max-width: none !important; overflow: visible !important; padding: 0 !important; white-space: pre !important; word-wrap: normal !important; word-break: normal !important; color: inherit !important }
.token.comment, .token.prolog, .token.doctype, .token.cdata { color: rgba(112, 128, 144, 1) !important; font-style: italic !important }
.token.punctuation { color: rgba(153, 153, 153, 1) !important }
.token.atrule, .token.attr-value, .token.keyword { color: rgba(0, 119, 170, 1) !important; font-weight: bold !important }
.token.function, .token.class-name { color: rgba(221, 74, 104, 1) !important; font-weight: bold !important }
.token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: rgba(102, 153, 0, 1) !important }
.token.property, .token.tag, .token.boolean, .token.number, .token.constant, .token.symbol, .token.deleted { color: rgba(153, 0, 85, 1) !important }
.cnblogs-markdown pre, .cnblogs-post-body pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; background-color: rgba(248, 248, 248, 1) !important; border: 1px solid rgba(225, 228, 232, 1) !important; border-radius: 6px !important; padding: 16px !important; margin: 16px 0 !important }
pre, pre, pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important }</style>
<style>pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-size: 14px !important; line-height: 1.6 !important; padding: 16px !important; margin: 16px 0 !important; background-color: rgba(248, 248, 248, 1) !important; border: 1px solid rgba(225, 228, 232, 1) !important; border-radius: 6px !important; tab-size: 4 !important; -moz-tab-size: 4 !important; max-width: 100% !important; box-sizing: border-box !important }
code { font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-size: 14px !important; white-space: pre !important; word-wrap: normal !important; word-break: normal !important; overflow-wrap: normal !important; display: inline !important; background: rgba(0, 0, 0, 0) !important; border: none !important; padding: 0 !important; margin: 0 !important; line-height: inherit !important }
pre code { background: rgba(0, 0, 0, 0) !important; border: 0 !important; border-radius: 0 !important; display: block !important; line-height: 1.6 !important; margin: 0 !important; max-width: none !important; overflow: visible !important; padding: 0 !important; white-space: pre !important; word-wrap: normal !important; word-break: normal !important; color: inherit !important }
.token.comment, .token.prolog, .token.doctype, .token.cdata { color: rgba(112, 128, 144, 1) !important; font-style: italic !important }
.token.punctuation { color: rgba(153, 153, 153, 1) !important }
.token.atrule, .token.attr-value, .token.keyword { color: rgba(0, 119, 170, 1) !important; font-weight: bold !important }
.token.function, .token.class-name { color: rgba(221, 74, 104, 1) !important; font-weight: bold !important }
.token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: rgba(102, 153, 0, 1) !important }
.token.property, .token.tag, .token.boolean, .token.number, .token.constant, .token.symbol, .token.deleted { color: rgba(153, 0, 85, 1) !important }
.cnblogs-markdown pre, .cnblogs-post-body pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; background-color: rgba(248, 248, 248, 1) !important; border: 1px solid rgba(225, 228, 232, 1) !important; border-radius: 6px !important; padding: 16px !important; margin: 16px 0 !important }
pre, pre, pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important }</style><div class="markdown_views prism-atom-one-light" id="content_views"><svg style="display: none" xmlns="http://www.w3.org/2000/svg"><path d="M5,0 0,2.5 5,5z" id="raphael-marker-block" stroke-linecap="round" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0)"></path></svg><h2>React Server Components (RSC) 与 App Router 简介:Next.js 的未来范式</h2><p><strong>作者:码力无边</strong></p><hr><p>在过去的十八篇文章中,我们已经深入掌握了 Next.js 的 <code>pages</code> 目录(Pages Router)模型。我们学会了如何使用 <code>getStaticProps</code>, <code>getServerSideProps</code> 等数据获取函数,以及如何构建 API 路由和中间件。这是一个成熟、稳定且强大的模型,支撑了无数生产级应用。</p><p>然而,Web 开发的世界永远在进化。为了追求更优的性能、更佳的开发体验和更灵活的架构,React 核心团队与 Next.js 团队 (Vercel) 联手,推出了一项堪称革命性的新特性——<strong>React Server Components (RSC)</strong>,并围绕它构建了全新的路由和渲染模型:<strong>App Router</strong>。</p><p>从 Next.js 13 开始,App Router 成为了推荐的开发方式(<code>pages</code> 目录依然完全支持)。理解这个新范式,不仅是跟上 Next.js 的发展,更是洞察整个前端开发未来的关键。本文将为你揭开 RSC 和 App Router 的神秘面纱,解释它们是什么,以及它们带来了哪些颠覆性的优势。</p><h3>回顾过去:<code>pages</code> 目录的局限性</h3><p>在我们拥抱未来之前,先要理解它解决了过去的什么问题。<code>pages</code> 目录模型虽然优秀,但存在一些固有的局限性:</p><ol><li><strong>数据获取的“全有或全无”</strong>:在一个页面中,你只能选择一种主要的数据获取策略(SSG 或 SSR)。如果页面上的大部分内容是静态的,但有一个小组件需要动态数据,你往往不得不让整个页面都采用 SSR (<code>getServerSideProps</code>),从而牺牲了性能。</li><li><strong>客户端 JavaScript 的瀑布流</strong>:在 <code>pages</code> 目录中,一个路由下的所有组件代码,最终都会被打包发送到客户端(即使它们是在服务器上预渲染的)。当组件树变得庞大时,客户端需要下载和解析的 JavaScript 也会随之增多。</li><li><strong>布局(Layouts)的实现不够原生</strong>:虽然我们通过 <code>_app.tsx</code> 和 <code>getLayout</code> 模式实现了布局,但这更像是一种“变通方案 (workaround)”,而不是框架原生支持的一等公民。</li></ol><h3>新范式:React Server Components (RSC)</h3><p>RSC 是理解 App Router 的核心。它引入了一种全新的组件类型,从根本上改变了我们对“组件”的认知。</p><p>在此之前,我们所写的所有 React 组件,无论是否经过 SSR/SSG,最终都会在客户端“激活”(hydrate),成为<strong>客户端组件 (Client Components)</strong>。它们可以使用状态 (<code>useState</code>)、生命周期 (<code>useEffect</code>),并响应用户交互。</p><p><strong>React Server Components</strong> 是一种<strong>只在服务器上运行、且永远不会被发送到客户端</strong>的组件。</p><h4>Server Components 的超能力</h4><ol><li><p><strong>直接访问后端资源</strong>:由于它们只在服务器上运行,Server Components 可以像你的后端代码一样,直接、安全地访问数据库、文件系统、内部服务或使用私有环境变量。你不再需要为了获取数据而创建一个专门的 API 路由。</p>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="prism language-tsx">// app/page.tsx (这是一个 Server Component)
import { db } from '@/lib/db'; // 直接导入数据库实例
async function getPosts() {
// 直接进行数据库查询
return await db.post.findMany();
}
export default async function HomePage() {
const posts = await getPosts();
return (
{posts.map(post => )}
);
}</code></pre>
<p>注意到这里的 <code>async/await</code> 了吗?Server Components 可以是 <code>async</code> 函数!这使得数据获取变得前所未有的直观。</p></li><li><p><strong>零客户端 JavaScript</strong>:Server Components 的代码及其所有依赖(包括大型库,如 <code>marked</code> 用于解析 Markdown)都不会被打包进客户端的 JavaScript bundle 中。它们在服务器上渲染成一种特殊的中间格式,然后流式传输到客户端。这能极大地减小客户端 JavaScript 的体积,显著提升应用的初始加载性能。</p></li><li><p><strong>自动代码分割</strong>:每个 Server Component 都可以被看作是一个代码分割点,实现了组件级别的代码分割。</p></li></ol><h3>客户端组件 (Client Components)</h3><p>当然,我们仍然需要能够响应用户交互、使用状态和生命周期的组件。在 App Router 中,这类组件被称为<strong>客户端组件 (Client Components)</strong>。</p><p>你需要通过在文件顶部添加一个 <code>"use client";</code> 指令来明确地将一个组件标记为客户端组件。</p>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="prism language-tsx">// components/Counter.tsx
"use client"; // 标记为客户端组件
import { useState } from 'react';
export default function Counter() {
const = useState(0);
return (
);
}</code></pre>
<p><strong>重要规则</strong>:</p><ul><li>默认情况下,App Router (<code>app</code> 目录) 中的<strong>所有组件都是 Server Components</strong>。</li><li>只有需要使用 React Hooks(如 <code>useState</code>, <code>useEffect</code>)或浏览器 API(如 <code>window</code>)时,才需要使用 <code>"use client";</code> 将其标记为客户端组件。</li></ul><h3>App Router:为 RSC 量身打造的路由系统</h3><p>App Router 是一个基于 RSC 构建的全新路由系统,它位于项目的 <code>app</code> 目录中。</p><h4>核心特性:</h4><ol><li><strong>默认 Server Components</strong>:如上所述,<code>app</code> 目录下的组件默认都是 Server Components,鼓励你将尽可能多的逻辑保留在服务器端。</li><li><strong>原生支持布局 (Layouts)</strong>:App Router 将布局提升为一等公民。你可以在任何目录下创建一个 <code>layout.tsx</code> 文件,它会自动包裹该目录及其所有子目录下的页面。这使得创建复杂的嵌套布局变得极其简单和直观。</li><li><strong>组件化的数据获取</strong>:数据获取不再局限于页面级别。任何一个 Server Component 都可以独立地获取自己的数据。这解决了 <code>pages</code> 目录中“全有或全无”的问题,让数据获取更加精细化。</li><li><strong>流式渲染 (Streaming)</strong>:App Router 与 React Suspense 深度集成,支持服务端流式渲染。服务器可以先快速发送页面的静态部分(如布局),然后随着 Server Components 数据获取的完成,逐步将内容“流”到客户端 UI 中,用户可以更快地看到页面的部分内容,而不是等待整个页面加载完成。</li></ol><h4><code>app</code> 目录结构示例</h4>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>app/
├── layout.tsx # 根布局
├── page.tsx # 首页 (/)
├── dashboard/
│ ├── layout.tsx # Dashboard 区域的专属布局
│ ├── page.tsx # /dashboard 页
│ └── settings/
│ ├── page.tsx# /dashboard/settings 页
└── ...</code></pre>
<p>这种结构比 <code>pages</code> 目录更具表现力和组织性。</p><h3>总结:一次思维的转变</h3><p>从 Pages Router 迁移到 App Router,不仅仅是学习一套新的 API,更是一次<strong>思维模式的深刻转变</strong>。</p><table><thead><tr><th>传统思维 (Pages Router)</th><th>新范式思维 (App Router)</th></tr></thead><tbody><tr><td>组件默认在客户端运行。</td><td>组件默认在<strong>服务器</strong>运行。</td></tr><tr><td>数据获取在页面级别进行。</td><td>数据获取在<strong>组件级别</strong>进行。</td></tr><tr><td>整个页面的 JS 被发送到客户端。</td><td>只有<strong>客户端组件</strong>的 JS 被发送。</td></tr><tr><td>布局是“变通方案”。</td><td>布局是<strong>原生一等公民</strong>。</td></tr></tbody></table><p>React Server Components 和 App Router 代表了 React 和 Next.js 的未来。它们通过将更多的计算工作移回服务器,并以更智能的方式向客户端交付内容,旨在从根本上解决现代 Web 应用面临的性能和复杂性挑战。</p><p>这只是一个开始。在接下来的文章中,我们将深入探讨 App Router 和 Pages Router 的具体差异和选择考量,并逐步学习如何在 App Router 中进行数据获取、状态管理和构建复杂的应用。这次范式转移是激动人心的,让我们一起拥抱它!</p></div><br><br>
来源:https://www.cnblogs.com/yxysuanfa/p/19094651
頁:
[1]