React 服务器组件允许开发者在服务器上渲染组件,减少发送到客户端的 JavaScript
包大小并提高性能。与传统的客户端渲染或服务器端渲染不同,RSC 创建了一种新的思维模型,其中某些组件专门在服务器上运行,而其他组件则在客户端运行。
React 团队的文档提供了关于服务器组件最权威的解释。他们涵盖了核心概念、架构和使用模式。
以下是一个服务器组件的基本示例:
// This file runs only on the server
export async function ServerComponent() {
// Direct database access without exposing credentials to the client
const data = await db.query('SELECT * FROM posts')
return (
<div>
<h1>Posts from the Database</h1>
<ul>
{data.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
)
}
Dan Abramov,React 团队的成员,已经广泛撰写了关于 React 服务器组件架构的文章。他的解释帮助开发者理解 RSC 如何融入 React 生态系统。
他解释的一个关键概念是服务器组件和客户端组件之间的分离:
// This file runs only on the client
'use client'
import { useState } from 'react'
export function ClientComponent() {
// Client components can use React hooks
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
以及它们如何组合在一起:
import { ClientComponent } from './client-component'
export async function ServerComponent() {
const data = await fetchSomeData();
return (
<div>
<h1>Server-rendered content</h1>
<p>{data.message}</p>
{/* Client components can be rendered within server components */}
<ClientComponent />
</div>
)
}
Next.js 已将 React 服务器组件作为其架构的核心部分。他们的文档提供了在全栈应用程序上下文中使用 RSC 的实用示例。
以下是在 Next.js 服务器组件中数据获取的工作方式:
// Server Component by default in Next.js 13+
export default async function Page() {
// This data fetching happens on the server
const response = await fetch('https://api.example.com/data')
const data = await response.json()
return (
<main>
<h1>Welcome to my Next.js App</h1>
<div>
{data.items.map(item => (
<article key={item.id}>
<h2>{item.title}</h2>
<p>{item.description}</p>
</article>
))}
</div>
</main>
)
}
Kent C. Dodds 撰写了关于使用 React 服务器组件所需的思维模型转变的文章。他的博客文章帮助开发者理解何时使用服务器组件与客户端组件。
他讨论的一个关键模式是 RSC 上下文中的"容器/呈现器"模式:
// Server Component
import { UserProfilePresenter } from './user-profile-presenter'
export async function UserProfileContainer({ userId }) {
// Fetch data on the server
const userData = await fetchUserData(userId)
const userPosts = await fetchUserPosts(userId)
// Pass data to client component for interactivity
return <UserProfilePresenter userData={userData} posts={userPosts} />
}
// Client Component
'use client'
import { useState } from 'react'
export function UserProfilePresenter({ userData, posts }) {
const [activeTab, setActiveTab] = useState('profile')
return (
<div>
<nav>
<button
onClick={() => setActiveTab('profile')}
className={activeTab === 'profile' ? 'active' : ''}
>
Profile
</button>
<button
onClick={() => setActiveTab('posts')}
className={activeTab === 'posts' ? 'active' : ''}
>
Posts
</button>
</nav>
{activeTab === 'profile' ? (
<div>
<h1>{userData.name}</h1>
<p>{userData.bio}</p>
</div>
) : (
<div>
<h1>Posts by {userData.name}</h1>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
)}
</div>
)
}
React 服务器组件最强大的功能之一是能够从服务器流式传输 UI。这允许在数据可用时逐步渲染组件。
// In a Next.js app
import { Suspense } from 'react'
import Loading from './Loading'
import SlowDataComponent from './SlowDataComponent'
import FastDataComponent from './FastDataComponent'
export default async function Page() {
return (
<div>
<h1>My Dashboard</h1>
{/* Fast component renders immediately */}
<FastDataComponent />
{/* Slow component shows loading state until data is ready */}
<Suspense fallback={<Loading />}>
<SlowDataComponent />
</Suspense>
</div>
)
}
使用 RSC,数据获取移至服务器,这里更高效:
// Server Component
export default async function Page({ productId }) {
const product = await getProduct(productId)
const recommendations = await getRecommendations(productId)
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>${product.price}</p>
<h2>You might also like:</h2>
<div className="recommendations">
{recommendations.map(rec => (
<ProductCard key={rec.id} product={rec} />
))}
</div>
</div>
)
}
从服务器组件开始,根据需要用客户端组件增强:
// Server Component
import { SubmitButton } from './submit-button'
export function Form() {
return (
<form action="/api/submit" method="post">
<input type="text" name="name" required />
<textarea name="message" required></textarea>
{/* Use a Client Component just for the interactive button */}
<SubmitButton />
</form>
)
}
// Client Component
'use client'
import { useState } from 'react'
export function SubmitButton() {
const [isSubmitting, setIsSubmitting] = useState(false)
return (
<button
type="submit"
disabled={isSubmitting}
onClick={() => setIsSubmitting(true)}
>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
)
}
由于服务器组件无法访问 React Hook,因此需要采用不同的方法来管理状态:
// Server Component
import { UserSettings } from './user-settings'
export async function UserDashboard() {
const user = await getCurrentUser()
const settings = await getUserSettings(user.id)
return (
<div>
<h1>Welcome, {user.name}</h1>
{/* Pass data to client component for state management */}
<UserSettings initialSettings={settings} userId={user.id} />
</div>
)
}
// Client Component
'use client'
import { useState } from 'react'
export function UserSettings({ initialSettings, userId }) {
const [settings, setSettings] = useState(initialSettings)
async function updateSetting(key, value) {
// Update local state immediately for responsive UI
setSettings({ ...settings, [key]: value })
// Send update to server
await fetch(`/api/users/${userId}/settings`, {
method: 'PATCH',
body: JSON.stringify({ [key]: value }),
headers: { 'Content-Type': 'application/json' }
})
}
return (
<div className="settings-panel">
<h2>User Settings</h2>
<div className="setting">
<label>
Dark Mode:
<input
type="checkbox"
checked={settings.darkMode}
onChange={e => updateSetting('darkMode', e.target.checked)}
/>
</label>
</div>
{/* More settings... */}
</div>
)
}
并非所有库都与服务器组件兼容。以下是处理方法:
// Client Component
'use client'
import { SomeThirdPartyComponent } from 'third-party-library'
export default function ClientWrapper({ data }) {
return <SomeThirdPartyComponent data={data} />
}
// Server Component
import { ClientWrapper } from './client-wrapper'
export async function Page() {
const data = await fetchData()
return (
<div>
<h1>My Page</h1>
{/* Wrap third-party component that requires client features */}
<ClientWrapper data={data} />
</div>
)
}
React 服务器组件代表了 React 应用程序构建方式的重大演变。通过采用同时利用服务器和客户端能力的混合渲染模型,开发者可以创建更高性能和可扩展的应用程序。
本博客提供的资源和代码示例应该能帮助你理解 React 服务器组件开发中使用的核心概念和模式。随着这项技术的不断成熟,我们可以期待看到更多构建现代 Web 应用程序的创新方法。