React RFC Server Components

原文作者:魔术师卡颂

12月21日,React 团队公布了一个新的提案 Server Components

伴随这个提案同时发出的,还有一个小时的视频讲解可供运行的Demo、详尽的介绍。

这里有react团队的相关rfc

可见,React 团队很重视这个提案。本文会从如下方面讲解:

  • Server Components 是什么
  • Server Components 解决了什么问题

ServerComponent是什么

一句话概括:

Server Components 是在服务端运行的 React 组件。

咦?这和 服务端渲染(SSR)有什么区别?

相比 SSR 将组件在服务端渲染成填充内容的 HTML 字符串,并在客户端 hydrate 后使用。Server Components 更像我们的在客户端写的普通组件一样,只不过他的运行环境是服务端。

我们可以将组件按照功能分为:

  • 提供数据的 容器组件
  • 渲染数据并提供数据交互的 交互组件

举个例子,Note 组件是 容器组件,他负责请求并缓存数据。NoteEditor 是渲染 note 数据并执行用户交互的 交互组件

function Note(props) {
  const [note, setNote] = useState(null);
  useEffect(() => {
    fetchNote(props.id).then(noteData => {
      setNote(noteData);
    });
  }, [props.id]);
  
  if (note == null) {
    return "Loading";
  } else {
    return <NoteEditor note={note}/>
  }
}

如例子所述,我们可以通过在 useEffect 中发起请求并将返回的数据保存在 state 中。

这种「请求-渲染」模式会遇见被称为 waterfall 的问题:

就像一节一节的瀑布往下流水,NoteEditor 需要等待 Note 请求 note 成功后才能开始渲染。

在这里插入图片描述

交互组件 依赖的数据源越多,waterfall 问题会更明显。

理论上,如果 React 足够聪明,就能在 服务端 执行 容器组件 的渲染逻辑,在 客户端 执行 交互组件 的渲染逻辑。

按照这样的理念,如下这棵完全在客户端渲染的组件树:

在这里插入图片描述

可以拆分为:在 服务端 运行的 容器组件 和在 客户端 运行的 交互组件

在这里插入图片描述

其中在服务端运行的 容器组件 就是 Server Component

ServerComponent的意义

既然 ServerComponent服务端 运行,天然更接近各种 IO(请求数据库、读取文件、缓存…)。

上面的例子完全可以直接从 数据库 获取 note 数据,同时借助 Suspense,采用同步的写法。

function Note(props) {
  const note = db.notes.get(props.id);
  if (note == null) {
    return "Loading";
  }
  return <NoteEditor note={note}/>
}

天然更接近后端

任何其他数据源只需要通过 React 提供的 API 简单封装,使其支持 Suspense,就能接入 ServerComponent 中。天然更接近后端。

解决waterfall

区别于 SSR 传输的 HTML 字符串。ServerComponent 会将 Note 组件及其从 IO 请求到的数据序列化为类似 JSX 的数据结构,以流的形式传递给前端:

在这里插入图片描述

客户端在运行时直接获取到填充了数据的 ,并借助 Concurrent Mode 执行流式渲染。

0打包体积

假设我们开发一款 MD 编辑器。服务端传递给前端 MD 格式的字符串。

我们需要在前端引入将 MD 解析为 HTML 字符串的库。这个库就有206k。

import marked from 'marked'; // 35.9K (11.2K gzipped)
import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)

function NoteWithMarkdown({text}) {
  const html = sanitizeHtml(marked(text));
  return (/* render */);
}

通过 ServerComponent 我们怎么解决这个问题呢?

只需要简单将 NoteWithMarkdown 标记为 ServerComponent,将引入并解析 MD 这部分逻辑放在 服务端 执行。

ServerComponent 并不会增加前端项目打包体积。这个例子中,一次性为我们减少了前端206K (63.3K gzipped)的打包体积以及解析 MD 的时间。

自动代码分割

通过使用 React.lazy 可以实现组件的 动态import。之前,这需要我们在切换组件/路由时手动执行。在 ServerComponent 中,都是自动完成的。

在这里插入图片描述

在上面动图中,左侧列表是 ServerComponent,当点击其中卡片时,组件对应数据会动态加载。

更好的ahead-of-time (AOT)优化

Vue 作为一门使用 模版语言 的框架,模版语言 的固定写法使其能在编译时针对模版内容作出优化。

由于 JSX 仅仅是 JS 的语法糖,React 很难在编译时做出优化。

ServerComponent 对组件提出了更多限制(不能使用 useStateuseEffect…)。这些限制从侧面为 AOT 提供更多优化线索。

ServerComponent的使用

下面我们通过改写一个 记事本 组件讲解 ServerComponent 的使用:

// Note.js 

import fetchData from './fetchData'; 
import NoteEditor from './NoteEditor';

function Note(props) {
  const {id, isEditing} = props;
  const note = fetchData(id);
  
  return (
    <div>
      <h1>{note.title}</h1>
      <section>{note.body}</section>
      {isEditing 
        ? <NoteEditor note={note} />
        : null
      }
    </div>
  );
}

Note 组件的主要功能是根据 props 传入的 id 请求对应的 note 数据。

NoteEditor 用于展示及修改 note

其中 fetchData 方法用于获取数据,数据的加载中状态由组件外的 Suspense 完成。

可以看到,交互部分由 NoteEditor 完成,Note 主要功能是获取并传递数据。

接下来我们将 Note 变为 ServerComponent

// 注意🙋
// Note.server.js - Server Component

// 注意🙋
import db from 'db.server'; 
// 注意🙋
import NoteEditor from './NoteEditor.client';

function Note(props) {
  const {id, isEditing} = props;
  const note = db.posts.get(id);
  
  return (
    <div>
      <h1>{note.title}</h1>
      <section>{note.body}</section>
      {isEditing 
        ? <NoteEditor note={note} />
        : null
      }
    </div>
  );
}

有3点需要注意的改动,我们依次了解下:

1.Note.js 文件名改为 Note.server.js 代表这是 Server Component
2.Note.server.js 运行于服务端,我们不需要客户端的 fetchData 方法,可以直接访问数据库,所以这里调用 db.server 提供的方法。
3.NoteEditor 用于展示及修改 note。这是由客户端用户的交互控制的,所以将文件名改为 NoteEditor.client 代表这是个 Client Component

总结

太阳底下没有新鲜事。早期前端交互简单,仅仅作为服务端的 View 层。

随着前端交互变复杂,出现了前端框架主导的客户端渲染(CSR)。

为了解决首屏渲染速度、SEO问题,出现了服务端渲染(SSR),又回到了曾经作为 View 层的起点,只不过控制的粒度更细。

ServerComponent 提案的出现,预示着 React 的长远目标:将对 View 层的控制细化到组件级别。

为什么是「长远目标」ServerComponent 落地的大前提是 Concurrent Mode 生产环境稳定,让我们一起期待2021年吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值