React RFC Server Components是什么,有啥用

转载自: 魔术师卡颂 公众号

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

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

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

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

ServerComponent是什么

一句话归纳:java

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

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

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

咱们能够将组件按照功能分为:segmentfault

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

举个例子,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成功后才能开始渲染。

image.png

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

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

按照这样的理念,以下这棵彻底在客户端渲染的组件树:

image.png

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

image.png

其中在服务端运行的容器组件就是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的数据结构,以的形式传递给前端:

image.png

客户端在运行时直接获取到填充了数据的,并借助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年吧。

参考资料

[1] 视频讲解: https://www.youtube.com/watch...

[2] Demo: https://github.com/pomber/ser...

相关文章
相关标签/搜索