Skip to content

Commit 11b44ff

Browse files
committed
add deep react server component0
1 parent a4efdf3 commit 11b44ff

File tree

6 files changed

+264
-10
lines changed

6 files changed

+264
-10
lines changed

app/posts/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default async function Posts() {
3434
<div className="space-y-10">
3535
<div className="font-semibold text-black dark:text-white">文章</div>
3636

37-
<div className="space-y-4">
37+
<div className="space-y-8">
3838
{years.map((year) => (
3939
<section className="space-y-4" key={year}>
4040
<div className="font-semibold text-black dark:text-white">{year}</div>
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
---
2+
title: 深入 React 服务器组件(0):服务器组件的优势
3+
topic: content/topics/next-js.mdx
4+
date: 2025-01-01T16:00:00.000Z
5+
draft: false
6+
---
7+
8+
如果你还不熟悉服务器组件,那么我先简单介绍一下。
9+
10+
服务器组件(Server Components) 是 React 18 版本引入的一个新特性,它允许我们直接在服务端获取数据渲染组件,然后将其返回给客户端。
11+
12+
嗯?这听起来和同构渲染很像,但请相信我,它们之间有非常大的区别。在后续的文章中,我会再详细地去介绍。
13+
14+
语法上,服务器组件和传统的 React 组件一样。唯一可见的变化是,我们能在直接在组件中使用 async/await 语法。
15+
16+
就像这样:
17+
18+
```jsx
19+
export default async function Hobby() {
20+
// 直接在 React 组件中进行异步数据获取!
21+
const hobby = await getHobby();
22+
23+
if (!hobby) {
24+
return <div>Empty</div>;
25+
}
26+
27+
return <div>Server message: {hobby}</div>;
28+
}
29+
```
30+
31+
这看起来真的很酷!
32+
33+
但此时,你也许会感到疑惑,获取服务端数据的方案已经有很多了,为什么 React 还要推出服务器组件?
34+
35+
在本篇中,我们将会详细介绍服务器组件究竟有哪些优势。
36+
37+
### 服务器组件的优势
38+
39+
自服务器组件发布以来,我一直在深度使用它,在尝试了各种模式后,这是我体会到的服务器组件带来的几个主要优势。
40+
41+
#### 简化数据获取
42+
43+
服务器组件极大的简化了数据获取的代码。我们回顾一下本文前面的示例:
44+
45+
```jsx
46+
export default async function Hobby() {
47+
const hobby = await getHobby();
48+
49+
if (!hobby) {
50+
return <div>Empty</div>;
51+
}
52+
53+
return <div>Server message: {hobby}</div>;
54+
}
55+
```
56+
57+
使用服务器组件,这就是我从数据库中获取数据并将其呈现给浏览器所需的全部代码。
58+
59+
而没有服务器组件,我通常需要写更多的代码才能实现功能。
60+
61+
```jsx
62+
import { useState, useEffect } from 'react';
63+
64+
function Hobby() {
65+
const [isLoading, setIsLoading] = useState(true);
66+
const [hobby, setHobby] = useState('');
67+
68+
useEffect(() => {
69+
setIsLoading(true);
70+
71+
fetch('/api/hobby')
72+
.then((res) => res.json())
73+
.then((res) => setHobby(res.data))
74+
.finally(() => setIsLoading(false));
75+
}, []);
76+
77+
if (!isLoading) {
78+
return <div>Loading...</div>;
79+
}
80+
81+
if (!hobby) {
82+
return <div>Empty</div>;
83+
}
84+
85+
return <div>Client message: {hobby}</div>;
86+
}
87+
```
88+
89+
#### 减少了网络请求
90+
91+
服务器组件减少了加载页面内容所需的请求往返次数,这是目前我认为最重要的一个优点。
92+
93+
我们发出的网络请求越少,页面加载速度就越快(尤其是在慢速网络/移动设备上)。
94+
95+
想象一下这样一个场景:
96+
97+
我们正在开发一个博客网站,在页面组件中引入了渲染正文和评论的组件。
98+
99+
```jsx
100+
import Post from './components/post';
101+
import Comments from './components/comments';
102+
103+
export default function Page() {
104+
return (
105+
<>
106+
<Post />
107+
<Comments />
108+
</>
109+
);
110+
}
111+
```
112+
113+
正文组件会向服务器请求数据,然后渲染:
114+
115+
```jsx
116+
import { useState, useEffect } from 'react';
117+
import { useParams } from 'react-router-dom';
118+
119+
export default function Post() {
120+
const { id } = useParams();
121+
const [post, setPost] = useState('');
122+
123+
useEffect(() => {
124+
fetch(`/api/post?id=${id}`)
125+
.then((res) => res.json())
126+
.then((res) => setPost(res.data));
127+
}, []);
128+
129+
return <div>{post}</div>;
130+
}
131+
```
132+
133+
评论组件也一样:
134+
135+
```jsx
136+
import { useState, useEffect } from 'react';
137+
import { useParams } from 'react-router-dom';
138+
139+
export default function Comments() {
140+
const { id } = useParams();
141+
const [comments, setComments] = useState([]);
142+
143+
useEffect(() => {
144+
fetch(`/api/comment?id=${id}`)
145+
.then((res) => res.json())
146+
.then((res) => setComments(res.data));
147+
}, []);
148+
149+
return (
150+
<ul>
151+
{comments.map((comment) => (
152+
<li key={comment.id}>{comment.content}</li>
153+
))}
154+
</ul>
155+
);
156+
}
157+
```
158+
159+
传统的 React 组件会先从服务器加载页面资源文件,等到组件渲染完成后,再发起数据请求。
160+
161+
因此,网络请求的流程大致如下:
162+
163+
![client-component-request-flow](/client-component-request-flow.png '传统组件的网络请求')
164+
165+
而使用服务器组件时,由于数据获取和渲染都在服务端完成,客户端只需要一次请求往返,就可以获得完整的页面内容。
166+
167+
页面组件我们保持不变:
168+
169+
```jsx
170+
import Post from './components/post';
171+
import Comments from './components/comments';
172+
173+
export default function Page() {
174+
return (
175+
<>
176+
<Post />
177+
<Comments />
178+
</>
179+
);
180+
}
181+
```
182+
183+
修改一下正文组件,将其改为服务器组件:
184+
185+
```jsx
186+
export default async function Post() {
187+
const { id } = await useParams();
188+
const post = await getPost(id);
189+
190+
return <div>{post}</div>;
191+
}
192+
```
193+
194+
评论组件也一样:
195+
196+
```jsx
197+
export default async function Comments() {
198+
const { id } = await useParams();
199+
const comments = await getComments(id);
200+
201+
return (
202+
<ul>
203+
{comments.map((comment) => (
204+
<li key={comment.id}>{comment.content}</li>
205+
))}
206+
</ul>
207+
);
208+
}
209+
```
210+
211+
改为服务器组件后,网络请求的流程大致如下:
212+
213+
![server-component-request-flow](/server-component-request-flow.png '服务器组件的网络请求')
214+
215+
我们可以明显看到,客户端对服务器的请求次数减少了。随着页面组件的复杂度增加,这个优势会越来越明显。
216+
217+
#### 可使用服务端 API
218+
219+
服务器组件是在服务端渲染的,这意味着我们可以直接使用服务端的 API。
220+
221+
例如,我们可以通过 readFile 来读取文件内容:
222+
223+
```jsx
224+
import { readFile } from 'fs/promises';
225+
226+
export default async function Post() {
227+
const post = await readFile('./content/post.text', 'utf8');
228+
229+
return <div>{post}</div>;
230+
}
231+
```
232+
233+
#### 更小的 Bundle Sizes
234+
235+
Bundle size 是 “向客户端运送了多少 JavaScript” 的一种花哨的说法。
236+
237+
传统的 React 组件需要将其所有代码和依赖项都发送给客户端,而服务器组件只会将**渲染的结果**发送给客户端。
238+
239+
也就是说,无论你在组件中使用了多少依赖,最终发送给客户端的 Bundle Size 总是最小的。
240+
241+
较小的捆绑包大小很重要,它会让页面内容的加载时间更快,这对于用户体验来说至关重要。
242+
243+
一直以来,我都有这样一个有趣的想法:
244+
245+
也许 React 团队设计服务器组件时,可能初衷只是为了优化数据获取。但将组件移至服务端执行后,没想到竟然还有意外收获:大幅减小了捆绑包的体积。而这种 "意外收获" 恰恰成为了服务器组件最吸引人的特性之一。
246+
247+
### 结束语
248+
249+
在本篇中,我们详细介绍了 React 服务器组件的几个优势。
250+
251+
虽然服务器组件在 Web 开发社区中激起了各种争论,但我认为它是 React 生态系统向前迈出的一大步。
252+
253+
如果你对服务器组件感兴趣,欢迎关注我的博客,我会持续更新相关内容。

content/posts/deep-server-side-rendering-1.mdx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ app.use('*', async (req, res) => {
246246
247247
### 路由
248248
249-
在本节中,我们将基于 react-router-dom 实现一个文件系统的路由
249+
在本节中,我们将基于 react-router-dom 实现一个文件路由系统
250250
251251
首先,安装一下依赖:
252252
@@ -498,14 +498,15 @@ app.use('*', async (req, res) => {
498498

499499
const ctx = { serverSideProps: null };
500500
const renderer = await render({ url, ctx });
501-
const html = template
502-
.replace('<!--app-html-->', renderer.html ?? '')
503-
.replace(
504-
'<!--app-data-->',
505-
ctx.serverSideProps
506-
? `<script>window.__SSR_DATA__ = ${JSON.stringify({ url, props: ctx.serverSideProps })}</script>`
507-
: ''
508-
);
501+
const html = template.replace('<!--app-html-->', renderer.html ?? '').replace(
502+
'<!--app-data-->',
503+
ctx.serverSideProps
504+
? `<script>window.__SSR_DATA__ = ${JSON.stringify({
505+
url,
506+
props: ctx.serverSideProps,
507+
})}</script>`
508+
: ''
509+
);
509510

510511
res.status(200).set({ 'Content-Type': 'text/html' }).send(html);
511512
});
123 KB
Loading

public/client-request-flow.png

141 KB
Loading
121 KB
Loading

0 commit comments

Comments
 (0)