React Server Components到底行不行?

2,011 阅读6分钟

什么是RSC?


前段时间 React 团队发布了一项用于解决 React 页面在多接口请求下的性能问题的解决方案 React Server Components。当然该方案目前还在草案阶段,官方也只是发了视频和一个示例 demo 来说明这个草案。

React Server Component是一种编写React组件的方法,该组件在服务器端呈现,目的是提高React应用程序的性能。

RSC出现背景?


在官网的视频中说明了主要原因是因为大量的 React 组件依赖数据请求之后才能做渲染。如果是这样,那么每个组件自己去请求数据的话会出现子组件要等父组件数据请求完成,到渲染子组件的时候才会开始去请求子组件的数据,也就是官方所谓的 WaterFall 数据请求队列的问题。而将数据请求放在一起请求又非常不便于维护。出现了一个两难的问题。

RSC 如何解决组件依赖数据渲染的问题?


既然组件是依赖数据做渲染的。那为什么接口不直接返回渲染后的组件呢?所以他们提出了 Server Components 的解决方案。方案的大概就是将 React 组件拆分成 Server 组件(.server.tsx)和 Client 组件(.client.tsx)两种类型。其中 Server 组件会在服务端直接渲染并返回。

RSC和SSR有什么区别?


在服务端渲染在返回,就会想到SSR,哪他们有什么区别了。相比SSR将组件在服务端渲染成填充内容的HTML字符串,并在客户端hydrate后使用。Server Components更像我们的在客户端写的普通组件一样,只不过他的运行环境是服务端。

SSR与初始化相关

在使用SSR时,您将HTML渲染好发送给客户端,然后加载React JS,一旦加载了JS,应用程序就恢复成了客户端的React应用程序,改应用程序被”水化“了。

意味着,SSR的应用程序在初始化之后就不是SSR所拥有的旧的应用程序,在SSR中,除了您的第一页加载是纯HTML之外,您的所有组件仍然都是客户端组件!

RSC始终在服务端

React Server组件始终在服务器上呈现。这些可能是从后端获取某些数据的组件,因此将这些组件的呈现与正在获取的数据并置在一起是有意义的。每当需要重新渲染这些组件时,它们就会从服务器中重新提取并合并到现有的客户端React组件树中。最酷的是,即使我们从服务器中重新获取视图的某些部分,客户端状态仍会保留。

RSC组件更可能减小软件包大小

由于SSR应用程序是关于初始页面加载的,因此客户端可能会在浏览您的应用程序时最终下载所有依赖项.RSC组件则这些依赖关系将始终仅存在于服务器上,因为这些React Server组件在呈现之前不会交付给前端。

如何使用RSC


官网Demo

RSC优势


天然更接近后端

比较是在服务端渲染之后返回给客户端。任何其他数据源只需要通过React提供的API简单封装,使其支持Suspense,就能接入ServerComponent中。天然更接近后端。

解决waterfall

RSC以流的形势传递给客户端

0打包体积

这可能比较理想化,或者说在一些场景是可以的,为什么了?举一个例子: 假设我们开发一款MD编辑器。服务端传递给前端MD格式的字符串。

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

import marked from 'marked'; // 35.9K (11.2Kgzipped)
import sanitizeHtml from 'sanitize-html'// 206K (63.3K gzipped)
function NoteWithMarkdown({text}) {
  const html = sanitizeHtml(marked(text));
  return (/* render */);
}

只需要简单将NoteWithMarkdown标记为ServerComponent,将引入并解析MD这部分逻辑放在服务端执行。其实就是说把这放在服务端不需要客户端去bundle文件,来达到降低打包体积的问题。

自动代码分割

通过使用React.lazy可以实现组件的动态import。

之前,这需要我们在切换组件/路由时手动执行。在ServerComponent中,都是自动完成的。

减轻客户端渲染压力

同SSR一样,把加载->获取数据->在渲染,这样一个串行的模式,变成一个服务端一次操作的并行模式,来达到减轻客户端渲染压力。

RSC可能带来的问题


接口返回

正常情况下:加载组件,接口返回组件需要的数据,进行视图的更新渲染。

RSC:将组件的加载和数据的加载合二为一,减少了bundle的体积,但是体积转义到了接口返回中,特别是在类似列表这种有分页的请求中,这种劣势会更明显。明明组件只需要在初始的时候进行加载,但是因为被融合进接口里了,每次接口都会返回冗余的组件结构,这样也不知道是好还是不好。可能后续需要优化一下接口二次返回只返回数据会比较好。

服务器成本问题

将客户端渲染行为迁移到服务端时候势必会增加服务端的压力,用户量上来之后这块的成本是成量级的在增加的。关于这个问题,官方提供的回复是随着服务器的成本降低势必 Server Components 带来的优势会抵消这块的劣势。从当前来看,这个成本还是昂贵的。

针对RSC解决的问题,有没有更好的解决方案?


RSC解决的问题:接口请求分散在各组件中带来的子组件的数据请求需要等待父组件请求完成渲染子组件时才能开始请求的数据请求队列问题。

其实是有的,那就是在写法上进行优化,举个例子:

import React, {useState, useEffect} from 'react';
import ReactDOM from 'react-dom';

function App() {
  const [data, setData] = useState([]);
  useEffect(() => {
    fetchData.then(setData);
  }, []);
  
  return (
    <div>
      {!data.length ? 'loading' : null}
      <Child data={data} />
    </div>
  );
}

function Child({data}) {
  const [childData, setData] = useState([]);
  useEffect(() => {
    fetchChildData.then(setData);
  }, []);
  
  if(!data.length) {
	return null;
  }
  
  return (
    <div>{data.length + childData.length}</div>
  );
}

ReactDOM.render(<App />, document.querySelector('#root'));

如示例代码所示,只要加载组件,但是在无数据情况下不返回 DOM 也是可以做到子组件的数据先请求而无需等待的。当然这种需要认为的在写法上进行优化,但我也仍然认为比大费周章的去做 Server Component 要好很多。

RSC:RFC


RFC

在RSC的RFC来看,有人赞成,也有人反对。大部分人对 Server Component 还是持不赞成的态度的,认为它可能并没有像 React Hooks 那样解决业务中的实际痛点。就目前暴露的提案,我个人也觉得 Server Component 是弊大于利的。