乐闻世界logo
搜索文章和话题

Next.js 13+ 的 React Server Components 是什么?

2月17日 23:33

Next.js 13 引入了 React Server Components(RSC),这是一个重大的架构变革,彻底改变了我们在 Next.js 中构建应用的方式。

什么是 React Server Components?

React Server Components 是一种新的组件类型,它们在服务器上渲染,而不是在客户端。这意味着:

  1. 服务器端渲染:组件在服务器上执行,生成 HTML
  2. 零客户端 JavaScript:服务器组件不会发送任何 JavaScript 到客户端
  3. 直接访问后端资源:可以直接访问数据库、文件系统等
  4. 保持代码私密:服务器代码不会暴露给客户端

服务器组件 vs 客户端组件

服务器组件(默认)

javascript
// 默认情况下,所有组件都是服务器组件 async function BlogList() { // 可以直接访问数据库 const posts = await db.post.findMany(); // 可以使用文件系统 const content = await fs.readFile('./content.md', 'utf-8'); return ( <div> {posts.map(post => ( <PostCard key={post.id} post={post} /> ))} </div> ); }

特点:

  • 在服务器上渲染
  • 不能使用 React Hooks(useState, useEffect 等)
  • 不能使用浏览器 API(window, document 等)
  • 不能使用事件处理器(onClick, onChange 等)
  • 可以直接访问数据库和文件系统
  • 不会发送 JavaScript 到客户端

客户端组件

javascript
'use client'; import { useState, useEffect } from 'react'; export default function InteractiveCounter() { const [count, setCount] = useState(0); useEffect(() => { // 可以使用浏览器 API document.title = `Count: ${count}`; }, [count]); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(c => c + 1)}>Increment</button> </div> ); }

特点:

  • 在客户端渲染
  • 可以使用所有 React Hooks
  • 可以使用浏览器 API
  • 可以使用事件处理器
  • 不能直接访问数据库
  • 会发送 JavaScript 到客户端

混合使用服务器和客户端组件

在服务器组件中使用客户端组件

javascript
// 服务器组件 async function BlogPage() { const posts = await fetchPosts(); return ( <div> <h1>Blog Posts</h1> <PostList posts={posts} /> <LikeButton postId={posts[0].id} /> </div> ); } // 客户端组件 'use client'; function LikeButton({ postId }) { const [liked, setLiked] = useState(false); return ( <button onClick={() => setLiked(!liked)}> {liked ? '❤️' : '🤍'} </button> ); }

在客户端组件中使用服务器组件

javascript
// 客户端组件 'use client'; import dynamic from 'next/dynamic'; // 动态导入服务器组件 const ServerComponent = dynamic(() => import('./ServerComponent'), { ssr: true }); export default function ClientComponent() { return ( <div> <h1>Client Component</h1> <ServerComponent /> </div> ); }

服务器组件的优势

1. 减少客户端 JavaScript

javascript
// 传统方式(客户端组件) 'use client'; import { useState, useEffect } from 'react'; function BlogList() { const [posts, setPosts] = useState([]); useEffect(() => { fetch('/api/posts') .then(res => res.json()) .then(setPosts); }, []); return ( <div> {posts.map(post => <Post key={post.id} {...post} />)} </div> ); } // 服务器组件方式 async function BlogList() { const posts = await db.post.findMany(); return ( <div> {posts.map(post => <Post key={post.id} {...post} />)} </div> ); }

2. 直接访问数据库

javascript
import { prisma } from '@/lib/prisma'; async function UserDashboard({ userId }) { // 直接访问数据库,无需 API 路由 const user = await prisma.user.findUnique({ where: { id: userId }, include: { posts: true, comments: true } }); return ( <div> <h1>Welcome, {user.name}</h1> <p>You have {user.posts.length} posts</p> </div> ); }

3. 保持代码私密

javascript
// 服务器组件中的敏感代码不会暴露给客户端 async function AdminPanel() { const apiKey = process.env.SECRET_API_KEY; // 这个 API 调用不会暴露给客户端 const data = await fetch(`https://api.example.com?key=${apiKey}`) .then(res => res.json()); return <div>{data.content}</div>; }

4. 更好的性能

javascript
// 服务器组件可以并行获取数据 async function Dashboard() { const [user, posts, notifications] = await Promise.all([ fetchUser(), fetchPosts(), fetchNotifications() ]); return ( <div> <UserProfile user={user} /> <PostList posts={posts} /> <NotificationList notifications={notifications} /> </div> ); }

实际应用场景

1. 博客文章列表

javascript
// app/blog/page.js async function BlogPage() { const posts = await db.post.findMany({ orderBy: { createdAt: 'desc' }, take: 10 }); return ( <div> <h1>Latest Posts</h1> {posts.map(post => ( <PostCard key={post.id} post={post} /> ))} </div> ); } // components/PostCard.js export default function PostCard({ post }) { return ( <article> <h2>{post.title}</h2> <p>{post.excerpt}</p> <Link href={`/blog/${post.slug}`}>Read more</Link> </article> ); }

2. 电商产品页面

javascript
// app/products/[id]/page.js async function ProductPage({ params }) { const product = await db.product.findUnique({ where: { id: params.id }, include: { reviews: true, relatedProducts: true } }); return ( <div> <ProductDetails product={product} /> <ProductReviews reviews={product.reviews} /> <RelatedProducts products={product.relatedProducts} /> <AddToCartButton productId={product.id} /> </div> ); } 'use client'; function AddToCartButton({ productId }) { const [loading, setLoading] = useState(false); const handleAddToCart = async () => { setLoading(true); await fetch('/api/cart', { method: 'POST', body: JSON.stringify({ productId }) }); setLoading(false); }; return ( <button onClick={handleAddToCart} disabled={loading}> {loading ? 'Adding...' : 'Add to Cart'} </button> ); }

3. 仪表板

javascript
// app/dashboard/page.js import { auth } from '@/auth'; async function Dashboard() { const session = await auth(); const [stats, recentActivity, notifications] = await Promise.all([ getUserStats(session.user.id), getRecentActivity(session.user.id), getNotifications(session.user.id) ]); return ( <div> <DashboardStats stats={stats} /> <RecentActivity activities={recentActivity} /> <NotificationPanel notifications={notifications} /> </div> ); }

最佳实践

1. 默认使用服务器组件

javascript
// ✅ 好的做法 async function Page() { const data = await fetchData(); return <div>{data.content}</div>; } // ❌ 不好的做法 'use client'; function Page() { const [data, setData] = useState(null); useEffect(() => { fetchData().then(setData); }, []); return <div>{data?.content}</div>; }

2. 只在需要交互的地方使用客户端组件

javascript
// 服务器组件 async function ProductList() { const products = await fetchProducts(); return ( <div> {products.map(product => ( <ProductCard key={product.id} product={product} /> ))} </div> ); } // 只在需要交互的子组件中使用 'use client' 'use client'; function ProductCard({ product }) { const [liked, setLiked] = useState(false); return ( <div> <h3>{product.name}</h3> <button onClick={() => setLiked(!liked)}> {liked ? '❤️' : '🤍'} </button> </div> ); }

3. 将客户端组件移到组件树的底部

javascript
// ✅ 好的做法:客户端组件在底部 async function Page() { const data = await fetchData(); return ( <div> <Header /> <Content data={data} /> <InteractiveWidget /> </div> ); } 'use client'; function InteractiveWidget() { // 交互逻辑 } // ❌ 不好的做法:客户端组件在顶部 'use client'; function Page() { const [data, setData] = useState(null); useEffect(() => { fetchData().then(setData); }, []); return ( <div> <Header /> <Content data={data} /> </div> ); }

4. 使用动态导入减少客户端 JavaScript

javascript
import dynamic from 'next/dynamic'; // 动态导入重型组件 const HeavyComponent = dynamic(() => import('./HeavyComponent'), { loading: () => <div>Loading...</div>, ssr: false // 禁用服务器端渲染 }); async function Page() { const data = await fetchData(); return ( <div> <LightContent data={data} /> <HeavyComponent /> </div> ); }

常见问题

Q: 如何在服务器组件中使用状态?

A: 服务器组件不能使用 useState,但可以通过以下方式处理:

javascript
// 使用 URL 参数管理状态 async function Page({ searchParams }) { const page = parseInt(searchParams.page || '1'); const posts = await getPosts(page); return ( <div> <PostList posts={posts} /> <Pagination currentPage={page} /> </div> ); } 'use client'; function Pagination({ currentPage }) { const router = useRouter(); return ( <div> <button onClick={() => router.push(`?page=${currentPage - 1}`)}> Previous </button> <button onClick={() => router.push(`?page=${currentPage + 1}`)}> Next </button> </div> ); }

Q: 如何在服务器组件中处理表单提交?

A: 使用 Server Actions:

javascript
'use server'; import { revalidatePath } from 'next/cache'; export async function createPost(formData) { const title = formData.get('title'); const content = formData.get('content'); await db.post.create({ data: { title, content } }); revalidatePath('/blog'); } // 在组件中使用 import { createPost } from './actions'; export default function CreatePostForm() { return ( <form action={createPost}> <input name="title" /> <textarea name="content" /> <button type="submit">Create</button> </form> ); }

React Server Components 是 Next.js 的未来,通过合理使用服务器组件和客户端组件,可以构建出性能更好、用户体验更佳的应用。

标签:Next.js