实践使用React服务器组件

162 阅读8分钟

实践React服务器组件

与服务器端渲染不同,React服务器组件旨在用服务器上的工作完全取代客户端的功能。让我们来看看这是如何工作的。

React仍然是前端JavaScript框架中的旗舰,React团队继续寻求途径以保持其相关性。路线图上更重要的发展之一是React服务器组件。

React服务器组件提供了一种将组件背后的工作卸载到服务器的方法。这避免了运送捆绑的JavaScript,也避免了为组件提供二次API请求。

React服务器组件是一个预览功能,可以在React 18中启用。

为什么使用React服务器组件

在我们看React服务器组件将如何工作之前,让我们想想为什么。 首先,注意到React服务器组件不同于服务器端渲染(SSR)是很有用的。正如React团队的RFC所说。

[SSR和服务器组件是]互补的。SSR主要是一种快速显示客户端组件的非交互式版本的技术。在最初的HTML加载之后,你仍然需要支付下载、解析和执行这些客户端组件的成本。

因此,与SSR相比,我们的目标是渲染出一个初始版本的组件,然后作为一个正常的客户端动物,React服务器组件打算用服务器上的工作完全取代客户端的功能。这有两个主要好处:

  1. 捆绑的JavaScript不需要通过电线运送到客户端。JavaScript在服务器上被导入和执行,其结果也被消耗。
  2. 最初的Ajax/API请求不需要给组件注入水。该组件可以直接与后端服务交互以满足这些需求。 这使得客户端不那么健谈,并避免了有时看到的 "瀑布式请求",因为浏览器完成了相互关联的数据获取。

React服务器组件的局限性

因为React服务器组件是在服务器端环境中执行的,所以它们有一定的局限性,或者我们称之为特性。因为虽然这些特性在某些方面可能是限制,但它们也有助于我们理解为什么React服务器组件是有用的。

与普通的客户端组件相比,规范所规定的主要限制:

  • 不使用状态(例如,不支持useState() )。为什么?因为组件只运行一次,结果流向客户端;也就是说,组件不在客户端上运行,保持状态。
  • 没有像useEffect() 的生命周期事件。同样,因为该组件不是在浏览器中执行的,在那里它可以利用事件和副作用。
  • 除非你在服务器上进行聚填,否则就没有DOM等仅适用于浏览器的API。特别是考虑到fetch API,服务器端的渲染引擎通常提供一个polyfill,这样服务器端的功能看起来就像浏览器的API调用。
  • 没有依赖状态或效果的自定义钩子,也没有依赖浏览器专用API的实用函数。这些只是进行中的限制的余波。

React服务器组件所支持的客户端组件所不支持的功能:

  • 使用纯服务器的数据源,如数据库、内部服务和文件系统。简而言之,组件可以完全访问它所处的节点环境。
  • 使用服务器钩子。对服务器端能力的访问,如文件系统,可以用钩子包装起来,与普通钩子一样的精神来分享功能。
  • 能够渲染其他服务器端组件、本地元素(div、span等)和客户端组件。

请记住最后一条。React服务器组件存在于一个分层的组件树中,它混合了服务器组件和客户端组件,彼此嵌套在一起。

还要注意的是,React服务器组件绝不是要取代React生态系统的其他部分。特别是,React服务器组件并不取代正常的客户端组件。相反,它们通过允许你在适当的地方将纯服务器组件插入树中来增强客户端组件。

此外,React服务器组件仍然可以将道具传递给它们的子客户端组件。这意味着你可以智能地将你的应用程序分为互动部分,由客户端组件处理,并包含服务器组件,这些组件的状态完全来自后端提前加载。

使用React服务器组件

由于现在有两种组件,你可以通过使用server.js和client.js(以及其他相关的扩展,如server.jsx和client.jsx)分别代表服务器组件和客户端组件来区分它们。请注意,client.js组件并不是什么新东西。它们与你已经熟悉的React组件完全一样,只是它们现在有一个文件扩展名,以便引擎知道哪个是哪个。

如果你看一下React团队创建的演示应用程序,你会看到/src目录下的文件相互交织,相互依赖。例如,有NoteList.server.js和SideBarNote.client.js文件。

看看清单1中的NoteList.server.js源文件吧。

清单1.NoteList.server.js

import {fetch} from 'react-fetch';

这里说明了几件事。首先,注意到第1行由react-fetch 提供的获取API的polyfill。同样,这让你写的API请求看起来就像客户端组件。

其次,观察数据存储是如何通过一个统一的API访问的(import ofdb )。 这是React团队提供的一个惯例;理论上你可以通过一个典型的节点API来访问数据库。在任何情况下,notes 变量都是通过直接点击数据库来填充的,代码中的注释是警告在现实生活中不要这样做(容易被SQL注入)。

第三,注意视图模板的主体是如何由函数返回定义的典型JSX。

第四,最后,请看SideBarNote 组件是如何像其他组件一样被导入的,尽管它是一个定义在SideBarNote.client.js文件中的客户端组件。

无捆绑组件

React服务器组件最引人注目的一点是,该组件所依赖的JavaScript--那些被导入的第三方包--不需要被运送到客户端。它们被导入、解释,并完全在服务器上使用。只有结果被发送。

例如,如果你看一下Note.server.js,你会发现它导入了一个数据格式化工具(通过import {format} from 'date-fns'; )。一切都发生在服务器端,而不是压缩和发送,然后解压缩和执行。你也避免了滚动你自己的数据格式化器的丑陋选择(呸)。

改进代码拆分

另一个你有可能看到性能和简单性的领域是在代码分割方面。这是因为服务器组件可以在运行时告诉你正在执行什么代码路径,并在那时决定纳入什么代码。

这类似于使用React.lazy() 来导入代码,只是分割是在没有干预的情况下进行的。另外,服务器组件可以比客户组件更早地开始加载必要的代码路径,而客户组件必须等到决策路径被加载和执行。

RFC引用的例子在清单2中,值得快速浏览一下。

清单2.服务器组件中的懒惰加载

import React from 'react';

在清单2中,我们根据一个标志(FeatureFlags.useNewPhotoRenderer )来决定加载哪个组件(OldPhotoRendererNewPhotoRenderer )。如果这是用React.lazy ,这里的组件就必须在浏览器上进行评估,然后再进行必要的选择,进行懒惰加载。相反,使用服务器组件,不需要使用Lazy,只要这段代码在服务器上执行,正确的代码路径就会开始懒散地加载。

React服务器组件如何工作

考虑一下React服务器组件RFC中的下面这段话。

服务器组件是逐步渲染的,并逐步将UI的渲染单元流向客户端。结合Suspense,这允许开发者精心设计有意的加载状态,并在等待页面的剩余部分加载时快速显示重要内容。

有趣的是。因此,React服务器组件实际上没有做类似SSR的事情,即组件在服务器上渲染,并在客户端减少到HTML和最小的JS来引导自己。相反,该框架实际上是在准备好后立即流式传输经过提炼的UI状态。

想象一下,服务器遇到了渲染一个服务器组件的需要。一旦渲染准备好了,结果就会被整理成一种紧凑的格式,并立即开始流向客户端。

这意味着,正如RFC所指出的,用户界面的基本元素可以被识别并尽快渲染。同时,Suspense可以用来智能地处理交互式客户端的部分。

React遇上服务器

React服务器组件对于这样一个流行的、有企业支持的JavaScript项目来说是一个大胆的举动。它向世界清楚地表明,React和它的团队致力于参与JavaScript框架中持续不断的创新活动。