React Server Components实战:解锁服务端渲染新能力
React Server Components实战解锁服务端渲染新能力前言大家好我是前端老炮儿今天咱们来聊聊React Server ComponentsRSC。你以为React只能在客户端渲染那你可太天真了React Server Components正在改变我们构建应用的方式。什么是React Server Components核心概念React Server Components → 在服务端渲染 → 只传输必要数据 → 客户端组装与SSR的区别SSR服务端渲染HTML客户端hydrateRSC服务端渲染组件传输组件树结构优势更小的bundle、更快的首屏、直接访问服务端资源为什么需要RSC性能优势减少JavaScript bundle大小更快的首屏加载减少客户端JavaScript执行开发优势直接访问服务端资源数据库、文件系统自动代码分割简化数据获取逻辑基础使用创建Server Component// Server Component - 在服务端运行 async function BlogPost({ id }) { // 直接访问数据库 const post await db.posts.findById(id); return ( article h1{post.title}/h1 p{post.content}/p AuthorCard author{post.author} / /article ); } export default BlogPost;创建Client Component// Client Component - 在客户端运行 use client; import { useState } from react; function LikeButton({ postId, initialLikes }) { const [likes, setLikes] useState(initialLikes); const [isLiked, setIsLiked] useState(false); const handleLike async () { if (isLiked) { setLikes(l l - 1); await fetch(/api/posts/${postId}/unlike, { method: POST }); } else { setLikes(l l 1); await fetch(/api/posts/${postId}/like, { method: POST }); } setIsLiked(!isLiked); }; return ( button onClick{handleLike} style{{ color: isLiked ? red : gray, cursor: pointer }} ❤️ {likes} /button ); } export default LikeButton;混合使用// Server Component async function BlogPostWithLikes({ id }) { const post await db.posts.findById(id); return ( article h1{post.title}/h1 p{post.content}/p {/* 嵌入Client Component */} LikeButton postId{id} initialLikes{post.likes} / /article ); }数据获取直接数据库访问// Server Component async function ProductList() { // 直接查询数据库 const products await db.products.findMany({ where: { inStock: true }, orderBy: { createdAt: desc }, take: 10 }); return ( div classNameproduct-list {products.map(product ( ProductCard key{product.id} product{product} / ))} /div ); }并行数据获取// Server Component - 并行获取数据 async function Dashboard() { // 并行获取多个数据 const [user, orders, notifications] await Promise.all([ db.users.findById(userId), db.orders.findMany({ where: { userId } }), db.notifications.findMany({ where: { userId, read: false } }) ]); return ( div classNamedashboard UserProfile user{user} / OrderList orders{orders} / NotificationPanel notifications{notifications} / /div ); }嵌套数据获取// Server Component - 父组件 async function PostList() { const posts await db.posts.findMany(); return ( div {posts.map(post ( PostItem key{post.id} post{post} / ))} /div ); } // Server Component - 子组件 async function PostItem({ post }) { // 按需获取额外数据 const author await db.users.findById(post.authorId); return ( div classNamepost-item h3{post.title}/h3 p作者: {author.name}/p /div ); }缓存策略请求级缓存// Server Component - 使用缓存 import { unstable_cache as cache } from next/cache; const getProducts cache(async () { console.log(Fetching products...); return await db.products.findMany(); }, [products]); async function ProductList() { const products await getProducts(); return ( div {products.map(product ( div key{product.id}{product.name}/div ))} /div ); }时间缓存import { unstable_cache as cache } from next/cache; const getTrendingPosts cache(async () { return await db.posts.findMany({ orderBy: { views: desc }, take: 5 }); }, [trending-posts], { revalidate: 3600 // 1小时重新验证 });动态缓存async function UserProfile({ userId }) { const user await db.users.findById(userId, { cache: { maxAge: 60 * 60 // 1小时 } }); return ( div h2{user.name}/h2 p{user.bio}/p /div ); }错误处理错误边界use client; import { useState, useEffect } from react; function ErrorBoundary({ children, fallback }) { const [hasError, setHasError] useState(false); const [error, setError] useState(null); useEffect(() { const handleError (e) { setHasError(true); setError(e); console.error(RSC Error:, e); }; window.addEventListener(error, handleError); return () window.removeEventListener(error, handleError); }, []); if (hasError) { return fallback error{error} /; } return children; } function ErrorFallback({ error }) { return ( div classNameerror-fallback h2Something went wrong/h2 p{error?.message || Unknown error}/p button onClick{() window.location.reload()} Try again /button /div ); } export { ErrorBoundary, ErrorFallback };异步错误处理// Server Component async function DataFetcher() { try { const data await fetchData(); return DataDisplay data{data} /; } catch (error) { console.error(Data fetch error:, error); return ( div classNameerror h3Failed to load data/h3 pPlease try again later./p /div ); } }实战完整示例博客应用// app/blog/[slug]/page.js async function BlogPage({ params }) { const { slug } params; // 获取博客文章 const post await db.posts.findUnique({ where: { slug }, include: { author: true, comments: { include: { author: true }, orderBy: { createdAt: desc } } } }); if (!post) { return ( div classNamenot-found h1404 - Post not found/h1 /div ); } return ( article classNameblog-post header h1{post.title}/h1 AuthorMeta author{post.author} / p classNamedate{post.createdAt.toLocaleDateString()}/p /header div classNamecontent dangerouslySetInnerHTML{{ __html: post.content }} / section classNamecomments h2Comments ({post.comments.length})/h2 CommentList comments{post.comments} / CommentForm postId{post.id} / /section /article ); } export default BlogPage;// app/blog/[slug]/CommentForm.js use client; import { useState } from react; function CommentForm({ postId }) { const [content, setContent] useState(); const [isSubmitting, setIsSubmitting] useState(false); const handleSubmit async (e) { e.preventDefault(); if (!content.trim()) return; setIsSubmitting(true); try { await fetch(/api/posts/${postId}/comments, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ content }) }); setContent(); window.location.reload(); } catch (error) { console.error(Failed to submit comment:, error); } finally { setIsSubmitting(false); } }; return ( form onSubmit{handleSubmit} classNamecomment-form textarea value{content} onChange{(e) setContent(e.target.value)} placeholderWrite a comment... disabled{isSubmitting} / button typesubmit disabled{isSubmitting} {isSubmitting ? Submitting... : Submit} /button /form ); } export default CommentForm;性能优化1. 避免水合// Server Component - 不需要水合 async function StaticContent() { const data await fetchStaticData(); return ( div suppressHydrationWarning {data.map(item ( div key{item.id}{item.content}/div ))} /div ); }2. 流式传输// Server Component - 流式渲染 async function StreamingList() { const stream await db.items.stream(); return ( div {stream.map(async (item) ( div key{item.id}{await item.content}/div ))} /div ); }3. 选择性客户端组件// 只在需要交互时使用客户端组件 async function ProductCard({ product }) { return ( div classNameproduct-card h3{product.name}/h3 p${product.price}/p {/* 只有按钮需要交互 */} AddToCartButton productId{product.id} / /div ); }常见问题与解决方案Q1: Server Component中不能使用useState原因Server Component在服务端运行没有React运行时解决方案将需要状态的部分提取为Client Component使用use client指令标记客户端组件Q2: 如何传递数据给Client Component原因Server Component和Client Component的边界解决方案通过props传递序列化数据避免传递函数或复杂对象Q3: 缓存导致数据不更新原因缓存策略过于激进解决方案调整缓存时间使用revalidate机制手动清除缓存最佳实践将无状态内容放在Server Component将交互逻辑放在Client Component利用并行数据获取合理设置缓存策略使用错误边界处理异常总结React Server Components是前端开发的新方向核心概念服务端渲染组件客户端组装优势更小bundle、更快加载、直接访问服务端资源使用方式混合Server和Client Component数据获取直接数据库访问、并行获取缓存策略请求级缓存、时间缓存、动态缓存希望今天的分享能帮助你掌握React Server Components如果你有任何问题或建议欢迎在评论区留言关注我每天分享前端干货让我们一起成长