React Server Components手把手教学(二)

简介: React Server Components手把手教学(二)

性能成本

我们将要讨论的最后一个问题领域是性能成本

image.png

上图形象的描绘了JavaScript对客户端带来的负担

React组件是客户端JavaScript函数。它们是我们的React应用程序的构建块。当我们在客户端加载应用程序时,组件会下载到客户端,React会执行必要的操作来为我们渲染它们。

但是这会带来两个重要问题:

首先,当用户发送请求时,应用程序会下载HTML以及链接的JavaScriptCSS和其他资产,如Image

在客户端(浏览器上),React开始执行其魔法,并进行HTML结构的水合(hydrates)。它解析HTML,将事件侦听器附加到DOM,并从存储中获取数据。因此,该站点变成了一个完全操作的React应用程序。

但问题是,客户端上会发生很多事情。我们最终会将所有这些代码都下载到客户端。


image.png

通常情况下,我们需要将外部库(Node模块)作为项目的依赖项。所有这些依赖项都会在客户端上下载,使其变得更加臃肿。


SSR 和 Suspense 解决的痛点

为了更好地理解对 RSC 的需求,首先需要理解对服务器端渲染(SSR)和 Suspense 的需求。

SSR 关注初始页面加载,将预渲染的 HTML 发送到客户端,然后在它被下载的 JavaScript 注入后,才会表现为典型的 React 应用程序行为。SSR仅发生一次:在直接导航到页面时

仅仅使用 SSR,用户可以更快地获取 HTML,但必须在all or nothing的瀑布流之前等待,然后才能与 JavaScript 进行交互:

  • 必须从服务器获取所有数据,然后才能显示其中的任何内容。
  • 必须从服务器下载所有 JavaScript,然后才能将客户端注入其中。
  • 必须在客户端上完成所有的注入,然后才能与任何内容进行交互。

为了解决这个问题,React 创建了 Suspense,它允许在服务器端进行 HTML 流式传输,并在客户端上进行选择性的注入。通过将组件包装在 <Suspense> 中,我们可以告诉服务器将该组件的渲染和注入降低优先级,让其他组件在不受较重组件阻塞的情况下加载

当我们在 <Suspense> 中有多个组件时,React 会按照我们编写的顺序从上往下处理树状结构,使我们的应用程序能够进行最优化的流式传输。然而,如果用户尝试与某个特定组件进行交互,该组件将优先于其他组件。

这大大改善了情况,但仍然存在一些问题:

  • 在显示任何组件之前,必须从服务器获取整个页面的数据。唯一的方法是在 useEffect() 钩子中在客户端进行数据获取,这比服务器端获取需要更长的往返时间,并且仅在组件渲染和注入后才发生。
  • 所有页面的 JavaScript 最终都会被下载,即使它以异步方式流式传输到浏览器。随着应用程序的复杂性增加,用户下载的代码量也会增加。
  • 尽管优化了注入,用户仍然无法与组件进行交互,直到客户端的 JavaScript 被下载并且为该组件实现。
  • 大部分 JavaScript 计算负荷仍然位于客户端,可能在各种不同类型的设备上运行。

image.png


通过上面的各种举证和分析,我们或多或少的知道,React在平时开发中遇到的一些令人深恶痛绝的问题. 其实React官方也知道这些问题,所以提出了RSC.

但在我们谈论这些之前,让我们更多地了解一下客户端服务器


4. 客户端-服务器模型

在本文中,我们已经多次使用了“客户端”“服务器”这两个术语。让我们高屋建瓴的解释它们之间的关系

image.png

  • 客户端:在应用程序方面,客户端是在最终用户端执行任务的系统。客户端包括我们的台式电脑、笔记本电脑、移动设备、浏览器等。
  • 服务器:字如其人,服务器为客户端提供服务。它可以与数据存储或数据库共存,以便快速访问数据。
  • 请求:请求是客户端用于向服务器请求服务的通信方式。
  • 响应:响应也是服务器用于将服务(数据/信息)发送回客户端的通信方式。

如果想了解更多关于网络相关的东西,可以参考之前写的网络篇


在服务器组件出现之前,我们编写的所有 React 代码都是在客户端(浏览器)上进行渲染的。因此,为了与在服务器上进行渲染的服务器组件区分开来,从现在开始,我们将常规的 React 组件(其中使用状态、effect、仅限于浏览器的 API 等)称为客户端组件(Client Components)。

React Client Components

传统上React组件存在于客户端。当它们与服务器交互时,它们发送请求并等待响应返回。在接收到响应后,客户端触发下一组操作。

如果请求的服务成功完成,客户端组件将根据UI采取相应操作,并显示成功消息。如果出现错误,客户端组件会向用户报告错误信息。

image.png

当它引起网络瀑布问题时,客户端组件的响应被延迟,从而导致糟糕的用户体验。


React Server Components

我们可以将React组件迁移到服务器上.也就是说我们可以将它们与后台数据一起放置.

让我们现在来了解一下RSC。这些新的组件可以更快地获取数据,因为它们位于服务器上。它们可以访问我们的服务器基础设施,如文件系统数据存储,而无需通过网络进行任何往返

image.png

对于React开发者来说,这是一个完整的范式转变,因为现在我们必须从服务器组件的角度来思考

使用RSC,我们可以将数据获取逻辑移至服务器(使我们的组件无需网络调用即可获取数据),并在服务器上准备好它。返回到客户端的数据是一个精心构造的组件,其中包含了所有的数据。

这意味着使用RSC,我们可以编写如下的代码:

import { dbConnect } from '@/services/mongo'
import { addCourseToDB } from './actions/add-course'
import CourseList from './components/CourseList'
export default async function App() {
  // 建立 MongoDB 链接
  await dbConnect();
  // 从数据库(db)中获取对应的数据信息
  const allCourses = await courses.find();
  // 数据校验(查看是否成功和数据格式)
  console.log({allCourses})
  return (
    <main>
      <div>
        <CourseList allCourses={allCourses} />  
      </div>
    </main>
  )
}

从上面的代码中我们可以注意到一些写法上的变化

  • 组件的类型是async,因为它将处理异步调用。
  • 我们从组件本身连接到数据库(MongoDB)。
  • 在常规的开发中,我们只有在Node.jsExpress中才会看到这种代码
  • 然后我们查询数据库并获取数据,以便将其传递给我们的JSX进行渲染。
  • 注意,控制台日志会在服务器控制台上记录,而不是在我们的浏览器控制台上。

另外,我们完全摆脱了状态管理(useState)和副作用管理(useEffect)。

使用RSC,我们可能不需要使用useEffect老死不相往来的那种)。


6. RSC的红与黑

以下是关于RSC可以做和不能做的事情的列表。尽管服务器组件可能看起来很高级,但并不意味着我们可以在任何地方都使用它们。

可以做的事情:

  • 使用 async/await 与仅限于服务器的数据源,如数据库内部服务文件系统等进行数据获取。
  • 渲染其他服务器组件、本地元素(如 divspan 等)或客户端组件(普通的 React 组件)。

不能做的事情:

  • 无法使用 React 提供的钩子,比如 useStateuseReduceruseEffect 等,因为服务器组件是在服务器上渲染的。
  • 不能使用浏览器 API,比如本地存储等(不过在服务器上可以进行 polyfill)。
  • 不能使用依赖于仅限于浏览器 API(例如本地存储)或依赖于状态或效果的自定义钩子的任何实用函数。

7. 如何同时使用客户端组件和服务器组件

我们的应用程序可以是服务器组件和客户端组件的组合。

服务器组件可以导入并渲染客户端组件,但客户端组件不能在其中渲染服务器组件。如果我们想在客户端组件中使用服务器组件,我们可以将其作为props传递并以这种方式使用。

最好将服务器组件放在组件层次结构的根部,并将客户端组件推向组件树的叶子

数据获取可以在服务器组件的顶部进行,并可以按照React允许的方式进行传递。用户交互(事件处理程序)和访问浏览器API可以在客户端组件中的叶子级别进行处理。

image.png

客户端组件无法导入服务器组件,但反过来是可以的。在服务器组件内部导入客户端组件或服务器组件都是可以的。而且,服务器组件可以将另一个服务器组件作为子组件传递给客户端组件,例如:

const ServerComponentA = () => {
    return (
        <ClientComponent>
            <ServerComponentB />
        </ClientComponent>
    )
}

在上面的示例中,我们将一个名为 ServerComponentB 的服务器组件作为子组件传递给了客户端组件。

让我们总结一下:

  • 可以在服务器组件内部导入客户端组件。
  • 不能在客户端组件内部导入服务器组件。
  • 可以将一个服务器组件作为子组件传递给服务器组件内的客户端组件。

RSC vs SSR

RSCSSR两者的名字都包含了Server这个词,但相似之处仅限于此。

通过SSR,我们将原始HTML从服务器发送到客户端,然后所有客户端的JavaScript都被下载。React开始水合化过程,将HTML转换为可交互的React组件。在SSR中,组件不会留在服务器上

而使用RSC组件会留在服务器上,并且可以访问服务器基础设施,而无需进行任何网络往返。

SSR用于加快应用程序的初始页面加载速度。我们可以在应用程序中同时使用SSRRSC,而不会出现任何问题。


8. RSC的优点

零捆绑包大小的组件

使用库对开发人员很有帮助,但它会增加捆绑包的大小,可能会影响应用程序性能。

应用程序的许多部分并不是交互式的,也不需要完全的数据一致性。例如,详细信息页面通常显示有关产品、用户或其他实体的信息,不需要根据用户交互来更新。

RSC允许开发人员在服务器上渲染静态内容。我们可以自由地在服务器组件中使用第三方包,而不会对捆绑包大小产生任何影响。

常规组件

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 */);
}

如果我们将上面的示例渲染为RSC,我们可以使用完全相同的代码来实现我们的功能,但避免将其发送到客户端 - 这将节省超过 240K 的代码(未压缩)。

Server Component (零捆绑包大小)

import marked from 'marked'; // 零捆绑包
import sanitizeHtml from 'sanitize-html'; // 零捆绑包
function NoteWithMarkdown({text}) {
  // ....
}

简而言之,如果我们在服务器组件内使用任何第三方库,该库将不会包含在客户端的捆绑包中。这将减小 JavaScript 捆绑包的大小。

换句话说,通过服务器组件,初始页面加载更快,更精简。基本的客户端运行时是可缓存的,并且大小是可预测的,不会随着应用程序的增长而增加。额外的面向用户的 JavaScript 主要是在我们的应用程序通过客户端组件需要更多的客户端交互时添加的。

如果我们在任何客户端组件内部使用该库,那么就如我们所想,该库将包含在客户端捆绑包中,并将被浏览器下载以进行解析和执行。


全权访问后端数据

正如前面所讨论的,服务器组件可以利用直接的后端访问来使用数据库、内部(微)服务和其他仅限于后端的数据源。

import db from 'db';
async function Note({id}) {
  const note = await db.notes.get(id);
  return <NoteWithMarkdown note={note} />;
}

在上面的代码片段中,我们将 note 传递给了 NoteWithMarkdown 组件。我们可以直接从数据库中获取这个note.

如果我们仔细查看代码,我们会发现我们没有进行任何获取 API 调用来获取 note。相反,我们只是在 Note 组件内直接执行了 DB 查询(通常我们在服务器端代码中执行 DB 查询)。这是可能的,因为这是一个服务器组件,它在服务器上进行渲染。

让我们再看一个例子,其中我们可以从服务器的服务器组件中访问文件系统

import fs from 'fs';
async function Note({id}) {
  const note = JSON.parse(await fs.readFile(`${id}.json`));
  return <NoteWithMarkdown note={note} />;
}

正如我们在上面的代码中所看到的,我们使用了 fs 模块(文件系统的缩写)来读取服务器上存在的文件。


自动代码分割

服务器组件将所有对客户端组件的导入视为潜在的代码分割点。

有如下的SRC

import OldPhotoRenderer from './OldPhotoRenderer.js';
import NewPhotoRenderer from './NewPhotoRenderer.js';
function Photo(props) {
  // 根据业务进行组件的渲染
  if (FeatureFlags.useNewPhotoRenderer) {
    return <NewPhotoRenderer {...props} />;
  } else {
    return <OldPhotoRenderer {...props} />;
  }
}

在上面的示例中,我们有两个组件 NewPhotoRendererOldPhotoRenderer(两者都是客户端组件),它们是有条件地进行渲染的。

假设 if (FeatureFlags.useNewPhotoRenderer) 值为 True,那么用户将会看到 NewPhotoRenderer 组件。只有该组件会被发送到客户端(或浏览器)。OldPhotoRenderer 将被懒加载(也就是说,它不会立即被发送到客户端)。因此,只有与用户可见的组件相关的 JavaScript 是需要的。


没有瀑布效应

正如前面讨论过的,连续的数据获取会引入瀑布效应。我们希望找到一种方法来避免从客户端到服务器的连续往返延迟(也就是说,我们必须等待一个请求完成,而请求可能需要一些时间来完成,因为它必须从客户端传输到服务器)。

async function Note(props) {
  // NOTE: 在渲染期间加载,在服务器上进行低延迟数据访问
  const note = await db.notes.get(props.id);
  if (note == null) {
    // 处理note 未被获取的逻辑
  }
  return (/* 根据note 渲染相关页面*/);
}

服务器组件通过将连续的往返请求移到服务器上,使应用程序能够实现这一目标(即不再有从客户端到服务器的获取调用)。

问题实际上并不是往返请求本身,而是这些请求是从客户端到服务器的。通过将这个逻辑移到服务器上,我们减少了请求的延迟,提高了性能。

相关文章
|
1月前
|
前端开发 JavaScript 测试技术
React Server Side Rendering (SSR) 详解
【10月更文挑战第19天】React Server Side Rendering (SSR) 是一种在服务器端渲染 React 应用的技术,通过在服务器上预先生成 HTML 内容,提高首屏加载速度和 SEO。本文从概念入手,逐步探讨 SSR 的实现步骤、常见问题及解决方案,并通过代码示例进行说明。
114 3
|
4月前
|
前端开发 JavaScript 算法
React Server Component 使用问题之想在路由切换时保持客户端状态,如何实现
React Server Component 使用问题之想在路由切换时保持客户端状态,如何实现
|
4月前
|
前端开发 JavaScript PHP
React Server Component 使用问题之路由的能力,如何实现
React Server Component 使用问题之路由的能力,如何实现
|
4月前
|
前端开发 JavaScript
React Server Component 使用问题之添加jsx的组件化能力,如何操作
React Server Component 使用问题之添加jsx的组件化能力,如何操作
|
4月前
|
前端开发 PHP 开发者
React Server Component 使用问题之怎么使用Docker运行PHP应用
React Server Component 使用问题之怎么使用Docker运行PHP应用
|
4月前
|
开发者
🔥揭秘JSF导航:如何轻松驾驭页面跳转与流程控制?🎯
【8月更文挑战第31天】在 JavaServer Faces(JSF)中,导航规则是控制页面跳转和流程的关键。本文详细介绍 JSF 的导航规则,包括转发和重定向等跳转方式,并通过 `faces-config.xml` 文件配置示例展示如何实现不同场景下的页面跳转及流程控制,帮助开发者有效管理应用程序的页面流和用户交互,提升应用质量。
53 0
|
4月前
|
前端开发 搜索推荐 UED
React Server Side Rendering的神奇之处:如何用SSR提升SEO与首屏加载速度,让你的项目一鸣惊人?
【8月更文挑战第31天】在现代Web开发中,React服务器端渲染(SSR)能显著提升SEO性能和首屏加载速度。通过在服务器端预渲染组件并发送HTML至客户端,SSR不仅优化了首屏加载时间,增强了用户体验,还生成了便于搜索引擎抓取的静态HTML文件,提升了页面排名。此外,SSR还具备提高安全性的优点,能够有效防范XSS攻击。虽然其开发复杂性和服务器负载是潜在劣势,但借助如Next.js等库、编写高效组件及定期维护等最佳实践,可以充分发挥SSR的优势,为未来Web开发注入更强动力。
60 0
|
4月前
|
前端开发 JavaScript 开发者
React Server Component 使用问题之为什么选择使用 React 官方的 renderToString 来渲染 HTML,如何解决
React Server Component 使用问题之为什么选择使用 React 官方的 renderToString 来渲染 HTML,如何解决
|
6月前
|
前端开发 JavaScript 算法
RSC 就是套壳 PHP ?带你从零实现 React Server Component
RSC 就是套壳 PHP ?带你从零实现 React Server Component
|
Rust 前端开发 JavaScript
用Rust搭建React Server Components 的Web服务器(三)
用Rust搭建React Server Components 的Web服务器(三)
128 0