Skip to content

Commit d43f62b

Browse files
committed
update RSC
1 parent 2fcc2fc commit d43f62b

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

froentend/react/rsc.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# React server components
2+
3+
## Server Components
4+
5+
简单来说,RSC 就是在 server 渲染组件,然后把 JSX 传给客户端。可以利用 https://rsc-parser.vercel.app/ 来理解 RSC protocol。
6+
7+
RSC 不可以使用 Client 的东西,比如 state 和 error boundary。
8+
9+
但是 RSC 可以解决 data loading waterfall 的问题。利用 suspense 可以让一个 RSC 请求 streaming JSX 到客户端,这样可以让界面快速显示。
10+
11+
然后可以利用 asynclocalstorage 让一个 RSC 请求共享数据,这样可以避免传入 props。
12+
13+
> 如果 RSC 渲染出错,还是需要 client 端的 error boundary 来处理错误。
14+
15+
## Client Components
16+
17+
怎么把一个 client component 利用 JSX protocol 进行传输呢?首先你需要有个标记,比如 `use client`。然后在 import client component 的时候,利用 bundler 或者 node loader 进行转化,转换成下面的格式
18+
19+
```js
20+
"$$typeof": "Symbol(react.client.reference)",
21+
"$$id": "ui/edit-text.js#EditableText"
22+
```
23+
24+
然后在调用 `renderToPipeableStream` 的时候,client component 会被转化成一个 `client reference``b:I["/edit-text.js","EditableText"]`
25+
26+
Client createFromFetch 就会解析这个 `client reference`,下载 client component 的 JS code,然后在客户端进行渲染。
27+
28+
```
29+
const initialContentFetchPromise = fetch(`/rsc${initialLocation}`)
30+
const initialContentPromise = createFromFetch(initialContentFetchPromise, {
31+
moduleBaseURL: `${window.location.origin}/ui` // 🐨 add a moduleBaseURL option here set to `${window.location.origin}/ui`
32+
})
33+
```
34+
35+
## Client Router
36+
37+
如果使用 RSC,我们使用 form 或者点击,就会发送请求给服务器。服务器返回一个新的 RSC。这样就会导致页面刷新。
38+
39+
我们需要利用禁止 form 和 link,让所有的请求走到我们自己定义的 navigator 里面,然后利用 `createFromFetch` 来渲染新的 RSC。
40+
41+
```jsx
42+
function Root() {
43+
......
44+
function navigate(nextLocation, { replace = false } = {}) {
45+
setNextLocation(nextLocation)
46+
const thisNav = Symbol(`Nav for ${nextLocation}`)
47+
latestNav.current = thisNav
48+
49+
// 🐨 create a nextContentKey with generateKey()
50+
const nextContentKey = generateKey()
51+
const nextContentPromise = createFromFetch(
52+
fetchContent(nextLocation).then((response) => {
53+
if (thisNav !== latestNav.current) return
54+
if (replace) {
55+
window.history.replaceState({ key: nextContentKey }, '', nextLocation)
56+
} else {
57+
window.history.pushState({ key: nextContentKey }, '', nextLocation)
58+
}
59+
return response
60+
}),
61+
)
62+
63+
contentCache.set(nextContentKey, nextContentPromise)
64+
65+
// 🐨 update this to setContentKey(nextContentKey)
66+
startTransition(() => setContentKey(nextContentPromise))
67+
}
68+
}
69+
70+
```
71+
72+
## Server Actions
73+
74+
Server action 是 open door 给 client component。具体是利用 `use server` 来标记一个 action。然后在 client component 里面调用这个 action。RSC protocol 会把这个 action 变成一个 `server reference`
75+
76+
```
77+
13:{"id":"file:///C:/BaiduNetdiskDownload/Learn%20React%2019%20with%20Epic%20React%20v2/github/react-server-components/playground/ui/actions.js#updateShipName","bound":null}
78+
```
79+
80+
当解析 RSC 时候,就会自动把 action 变成一个 RPC over fetch 的 call。
81+
82+
```js
83+
function createFromFetch(fetchPromise) {
84+
return RSC.createFromFetch(fetchPromise, {
85+
moduleBaseURL: `${window.location.origin}/ui`,
86+
callServer,
87+
});
88+
}
89+
90+
async function callServer(id, args) {
91+
const fetchPromise = fetch(`/action${getGlobalLocation()}`, {
92+
method: "POST",
93+
headers: { "rsc-action": id },
94+
body: await RSC.encodeReply(args),
95+
});
96+
const contentKey = window.history.state?.key ?? generateKey();
97+
onStreamFinished(fetchPromise, () => {
98+
updateContentKey(contentKey);
99+
});
100+
const actionResponsePromise = createFromFetch(fetchPromise);
101+
contentCache.set(contentKey, actionResponsePromise);
102+
const { returnValue } = await actionResponsePromise;
103+
return returnValue;
104+
}
105+
```
106+
107+
当 server 接收到 rsc-action 的时候,就会解析这个 action。这样就是实现了 action RPC.
108+
109+
```js
110+
app.post("/action", async (context) => {
111+
const serverReference = context.req.header("rsc-action");
112+
const [filepath, name] = serverReference.split("#");
113+
const action = (await import(filepath))[name]; // 动态import action,然后调用函数。
114+
if (action.$$typeof !== Symbol.for("react.server.reference")) {
115+
throw new Error("Invalid action");
116+
}
117+
118+
const formData = await context.req.formData();
119+
const args = await decodeReply(formData, moduleBasePath);
120+
const result = await action(...args);
121+
return await renderApp(context, result);
122+
});
123+
```

0 commit comments

Comments
 (0)