React Server Component 综合指南
React 作为 Web 开发最流行的开发框架,其渲染模式一直是前端开发者关注的焦点。在日常开发中,我们使用的最多可能就是 CSR (客户端渲染),该模式会使页面的首屏性能及 SEO 变差。随着 React 不断发展壮大,得到了社区的许多重要贡献,以改善其渲染性能。React核心团队以及在 Gatsby 和 Next.js 等框架上工作的工程师们已经为在服务端渲染页面创建了解决方案,以减轻客户端的负担。
这些解决方案包括静态网站生成(SSG)和服务器端渲染(SSR)。SSG 在构建时发生,意味着它在应用程序最初部署到服务器时进行,而 SSR 则在请求时渲染路由。
SSR 和 SSG 都会在服务端获取路由或网页的内容并生成相应的 HTML。与 CSR 区别的是,用户可以直接看到内容,而不是一个空白屏幕。生成的UI 在客户端 JavaScript 执行后会变得可交互,并做一次 Hyration。
React Server Components(RSCs,或简称为服务器组件)代表了 Web 上预渲染内容的最新进展。它们引入了一个新的思维模型到框架中,使我们能够创建跨越服务器和客户端领域的组件。
通过使用 RSCs,服务器端渲染现在可以在组件级别上进行,而不需要等待整个网页在服务器上渲染——就像在 SSR 中一样。服务器组件还无缝地与客户端组件交织在一起,以提供服务器端的效率和动态的客户端交互的多样性。
在本文中,我们将学习 React 服务器组件的相关知识以及如何在项目中使用它们,如果你还不了解 RSC,一定要学起来哦 ~
1.React 服务器组件介绍
根据 Vercel 的描述:
React Server Components allow you to write UI that can be rendered and optionally cached on the server. React 服务器组件允许您编写可以在服务器上渲染并可选择进行缓存的用户界面。
在服务器组件中,数据获取和数据库变更等组件逻辑仅在服务器上执行。与数据源的接近消除了不必要的客户端-服务器往返,使您的应用能够同时获取数据并在服务器上预渲染组件。
看看这个服务器组件示例:
import getBlogPosts from '@/lib/getVideoCourse'
import AddPostButton from '@components/add-post' // client component
export async function ServerComopnent(){
const posts = await getBlogPosts()
return(
<>
{posts.map((post, index) => {
<Post key={index} post={post} />
})}
<AddPostButton />
</>
)
}
在这里,我们能够异步从外部源获取数据,并在服务器上完全预渲染内容。生成的 HTML 模板随后无缝地流式传输到客户端的 React 树中。服务器组件还可以像上面的 AddPostButton 导入客户端组件。
使用服务器组件有很多性能优势,因为它们不会重新渲染,从而加快页了面加载时间。与 SSR 和 SSG 等渲染技术不同,RSC 生成的 HTML 不会在服务器上水合,并且不会向客户端发送任何 JS。这大大延长了页面加载时间,并减少了 JavaScript 捆绑包的总大小。
Web 开发中服务器组件的其他优势包括:
改进的性能:RSC 将繁重的任务卸载到服务器上,减少了客户端的工作负载。这有助于创建可预测的网页,并改善 Web 页面核心性能指标,如页面上最大内容绘制时间(LCP)和首次输入延迟(FID)。 高效的 SEO:因为 RSC 在服务器端生成HTML,搜索引擎可以轻松索引内容并正确排名页面。 增强的安全性:在 RSC 中执行的敏感数据(如认证令牌或API密钥)在服务器上执行,不会暴露给浏览器,防止意外泄漏。 数据的获取:服务器组件与数据源放置在一起,使数据获取更快,创建更具响应性的Web体验。
那么客户端组件和服务器组件有何区别呢?
在 React 中,客户端组件是指那些能够处理状态管理并与标准 Web API 和事件监听器进行交互,以促进客户端用户交互的组件。服务器组件无法执行客户端交互操作。这意味着 useState
和 useEffect
等React Hooks 无法使用。
2.Next.js 中的客户端组件
客户端组件(Client Component)就是我们已经熟悉的普通 React 组件。不过,由于 Next.js 14 中的每个组件默认都是服务器组件,因此我们必须在文件顶部增加 "use client" 将组件明确标记为客户端组件。
通过这种方式,组件可以使用事件处理程序和客户端Hooks,如useState、useContext、useEffect等。
// AddButton.tsx
"use client"
import { useState } from 'react'
import { addNewPost } from '@lib/addNewPost'
const [isLoading, setIsLoading] = useState(false)
export default function AddButton(){
return (
<button onClick={addNewPost({ /* new post */ })}>
{isLoading ? 'Loading...' : 'Add New Post'}
</button>
)
}
客户端组件和服务器端组件之间的关系也是需要注意的。在之前展示的第一个服务器组件中,你可以看到这个 AddPostButton 客户端组件被引入其中,人们普遍误解为客户端组件不能引入服务器端组件。实际上,可以引入,但有一个重要的条件,当你将一个服务器端组件嵌套在使用 "use client" 指令的客户端组件中时,它实际上将服务器端组件转换为客户端组件。
为了正确地将服务器端组件引入到客户端组件中,请使用以下方法,并利用 children 属性:
"use client"
export default function ClientComponent({ children }){
return (
<div>
{children}
</div>
)
}
现在,您可以在客户端组件中嵌套一个服务器组件:
// page.tsx (server component)
import ServerComponent from './ServerComponent'
import ClientComponent from './ClientComponent'
export default function Home(){
return(
<ClientComponent>
<ServerComponent />
</ClientComponent>
)
}
3.Next.js 中的服务器组件
随着 React 18 的发布,服务器组件被正式支持。通过运行以下命令来初始化一个 Next.js 项目:
npx create-next-app@latest demo
默认情况下,Next.js 应用路由器中的每个组件都是一个服务器组件:
// JavaScript
// app/page.tsx
export default function Home() {
console.log('Live from a server component')
return (
<main>
<h2>This is a server component</h2>
</main>
)
}
使用 npm run dev
启动服务后,可以看到打印信息如下:
在 RSC 中获取数据也比较简单,只需要在组件中附加 async 关键字,在服务器上会启用异步获取。下面来看一个简单的示例:
// app/page.tsx
interface Video {
id: string
image: string
title: string
views: string
published: string
}
async function fetchVideos() {
const videos = await fetch('http://localhost:3000/tutorials').then((res) =>
res.json()
)
return videos
}
export default async function Home() {
const videos: Video[] = await fetchVideos()
return (
<>
{videos.map((video, index) => (
<li className='mb-6' key={index}>
<a
href={`https://www.youtube.com/watch?v=${video.id}`}
target='_blank'
rel='noopener noreferrer'
className='hover:opacity-80'
>
<Image
src={video.image}
alt={video.title}
width={420}
height={200}
className='mb-4 rounded-md'
/>
<h4>{video.title}</h4>
<div>
{video.views} • {video.published}
</div>
</a>
</li>
))}
</>
)
}
4.React Suspense and Streaming
流行的 SSR 技术的一个主要缺点是,它以瀑布式方式获取并向用户显示所有内容。这意味着在客户端水合之前,必须满足所有异步请求并生成用户界面。
在非复杂应用中,这种方法可能会效率低下,并导致加载时间延长。这时,RSC 和 React Suspense 就能发挥作用,提高 SSR 的性能。
通过 React Suspense,我们可以暂停 React 树中组件的呈现,并在后台获取内容并将其分块流式传输到客户端时显示一个正在加载的组件作为占位符。一旦内容准备就绪,它就会无缝地替换由 Suspense
包裹的组件中的加载用户界面:
import { Suspense } from 'react'
import SkeletonScreen from './loading'
export const async function Home(){
const posts = await getPosts()
return (
<Suspense fallback={SkeletonScreen}>
{posts.map(post => (
// posts UI...
))}
</Suspense>
)
}
Next.js 通过一个特殊的 loading.js 文件将 Suspense 直接集成到应用程序路由器中。该文件将自动用 Suspense 封装 page.js 文件,并在 loading.js 中渲染自定义用户界面。下面是模拟示例:
<Layout>
<Suspense fallback={Loading.js}>
<Page />
</Suspense>
</Layout>
我们可以创建这个文件,然后在这里写入 loading 组件:
// app/loading.tsx
export default function SkeletonScreen() {
return (
<>
{Array(6)
.fill(6)
.map((item, index) => (
<li className='my-5' key={index}>
<div className='bg-[#DDDDDD] rounded-md w-[420px] mb-4 h-[200px] '></div>
<div className='bg-[#DDDDDD] rounded-md h-[20px] w-2/3 mb-2'></div>
<div className='bg-[#DDDDDD] rounded-md h-[20px] w-11/12 mb-2'></div>
<div className='bg-[#DDDDDD] rounded-md h-[20px] w-1/2'></div>
</li>
))}
</>
)
}
5.RSC 和其他库集成
由于 RSC 的概念相对较新,开发人员在尝试将第三方软件包与服务器组件无缝集成以实现所需的功能时遇到了困难。目前,由于 "use client" 指令的存在,第三方组件在客户端组件中的表现符合预期,但在服务器组件中却无法保证同样的兼容性。
不过,得益于 Next 13 中引入的增强型获取 API(可与服务器组件协调工作),您可能会发现自己在数据获取和缓存方面对这些外部工具的依赖性降低了。
最后
将 React Server Components 使用在你的 Web 项目中,可以大大提高性能、搜索引擎优化和数据处理能力,最终增强各种应用程序的用户体验。随着这一概念的不断发展,我们应随时关注更新和最佳实践,以便在项目中充分发挥 RSC 的优势。
大家都在看