上一篇迁移服务器中有提到 Camera 这个网站用到了 Server Component,这里就简单总结下 Server Component 的能力和使用方式。
Server Component
首先要说明的是,Server Component 不一定要开一个 Server 来承载应用,是可以在 build 时进行生成工作的,比如访问文件系统,或者访问数据库或接口静态生成,但是要注意的是,这样只会生成一次,并不会更新内容。
另一种就是在一个 Server 上运行了,比如 Next.js 框架,可以在每次发送请求时都去重新生成并拉取数据(当然可以通过缓存策略来决定是否要重新生成)。
那官方文档的一个类似于博客的例子来看,这个例子用到了两个点,第一个是服务端直出的页面加载最重要的博客内容,第二个是客户端渲染延迟获取不重要的评论,但是数据请求是服务端页面中就定义好的,客户端直接 use 即可。
// Server Component
import db from './database';
async function Page({ id }) {
// Will suspend the Server Component.
const note = await db.notes.get(id);
// NOTE: not awaited, will start here and await on the client.
const commentsPromise = db.comments.get(note.id);
return (
<div>
{note}
<Suspense fallback={<p>Loading Comments...</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
</div>
);
}
// Client Component
"use client";
import {use} from 'react';
function Comments({ commentsPromise }) {
// NOTE: this will resume the promise from the server.
// It will suspend until the data is available.
const comments = use(commentsPromise);
return comments.map(commment => <p>{comment}</p>);
}
Server Actions
前面 Server Component 是没问题了,但还存在一个问题,客户端组件没办法自由选择时机主动调用请求,也就是交互能力,所以就有了 Server Actions,而且 Server Action 支持执行 Redirect 来跳转页面。
下面代码中,定义了一个 createNoteAction 的 Action,然后在服务端组件里传递给了客户端组件一个引用,客户端组件就可以在需要的时候自行调用。
// Server Component
import Button from './Button';
function EmptyNote () {
async function createNoteAction() {
// Server Action
'use server';
await db.notes.create();
}
return <Button onClick={createNoteAction}/>;
}
"use client";
export default function Button({ onClick }) {
console.log(onClick);
// {$$typeof: Symbol.for("react.server.reference"), $$id: 'createNoteAction'}
return <button onClick={onClick}>Create Empty Note</button>
}
需要注意 “use client” 这个指令,如果使用 Server Component 这种模式,那么客户端组件就得加上 “use client” 这个指令,因为此时所有组件默认都是 Server Component。
如果想单独抽离 Server Actions 放到 Server Component 之外的地方,只要在声明 Server Actrions 的文件顶部写上 “user server” 指令即可。
"use server";
export async function createNoteAction() {
await db.notes.create();
}
"use client";
import { createNoteAction } from './actions';
function EmptyNote() {
console.log(createNoteAction);
// {$$typeof: Symbol.for("react.server.reference"), $$id: 'createNoteAction'}
<button onClick={createNoteAction} />
}
useActionState
除了直接调用服务端传来的引用外,还另外提供了这个新的 hook,主要作用于增强 form 的能力,比如纯 Server Component 可以直接用 form 处理表单交互,比如说在页面脚本未加载出来时,或者脚本被禁用时,使服务还是能够正常运行。
如图,当禁用 JavaScript 执行时,点击请求会使用 form 的原生跳转请求。
export type reqDataStateTypes = {
success: boolean;
msg: string;
id?: string;
} | null;
export async function reqData(prev: reqDataStateTypes, next: FormData) {
const id = next?.get("id")?.toString();
console.log(prev, id);
return {
success: id ? true : false,
msg: `输入的 id -> ${id},前值 id 为 -> ${prev?.id}`,
id,
};
}
"use client";
import { reqData } from "@/actions";
import { useActionState } from "react";
export default function Test() {
const [state, formAction, isLoading] = useActionState(reqData, null);
return (
<div>
{state?.msg}
<br />
<form action={formAction}>
<input type="text" name="id" id="id" />
<button type="submit">Request</button>
</form>
</div>
);
}
目前并没有严格限制只能由 form action 调用,还可以放在 useTransition 中手动调用,虽然此时来看这样用好像没啥意义。
const [state, formAction, isLoading] = useActionState(reqData, null);
const [isPending, startTransition] = useTransition();
<button
onClick={() =>
startTransition(() => {
const d = new FormData();
d.set("id", Math.random().toString());
formAction(d);
})
}
>
Send
</button>
Next.js
上面说到如果要用 Server Component 需要一个 Server,目前比较流行的有 Next 和 Remix,Camera 使用的是 Next。
使用上倒也没有什么花里胡哨,就是需要注意 Next 默认开启了缓存,如果需要数据刷新的时候,可以调用 revalidatePath
来刷新缓存。
Summary
总的来说,做一些小型应用会很方便很方便,大型应用的话还需要看下后续生态更新以及使用上的复杂度。