随笔

React Server Component

上一篇迁移服务器中有提到 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 处理表单交互,比如说在页面脚本未加载出来时,或者脚本被禁用时,使服务还是能够正常运行。

1.png

如图,当禁用 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

总的来说,做一些小型应用会很方便很方便,大型应用的话还需要看下后续生态更新以及使用上的复杂度。

本文链接:https://note.lilonghe.net/post/react-server-component.html

-- EOF --