❄️React 服务器组件使用指南

简介

最近,React 服务器组件受到了广泛的关注和热捧。这是因为 React 服务器组件允许开发人员将与组件相关的任务外包给服务器。这样,开发人员就无需分发捆绑的 JavaScript 和外部 API 查询,以便将组件水化,同时也避免了会导致客户端应用程序延迟增加的情况。 在本文中,我们将讨论什么是 React 服务器组件,以及如何将它们集成到构建应用程序中。

什么是服务器组件?

服务器组件是 React 18 中引入的新功能,也是 Next.js 13 中的默认功能。服务器组件本质上是一种从服务器检索数据并在服务器上渲染的组件类型。这些内容随后会以客户端应用程序可以呈现的格式流式传输到客户端应用程序中。

服务器组件以自定义格式呈现,这种格式没有标准协议,但类似于 JSON 格式。反应 DOM 可识别这种格式,并在识别后对其进行适当的呈现。

引入 React 服务器组件概念的问题陈述

我们将创建一个场景来介绍服务器组件的概念。可以将产品页面结构化如下:

js 复制代码
const ProductPage = ({ productId })=> {
    return (
        <>
            <ProductDetails productId={productId}>    
                <ProductItem productId={productId} />
                <MatchedItems productId={ productId } />
            <ProductDetails>
        </>
    )
}

现在我们有很多方法去获取组件中需要的数据,如下所示,一次性获取获取到组件所需的数据:

js 复制代码
const ProductPage = ({ productId })=> {
    const data = fetchContentsFromAPI();
    return (
        <>
            <ProductDetails details={data.details} productId={productId}>    
                <ProductItem item={data.product} productId={productId} />
                <MatchedItems items={data.matchedItems} productId={productId} />
            <ProductDetails>
        </>
    )
}

使用这种方法效果很好,而且有其优点,例如:

  • 这种方法适合用户体验,因为所有组件都是在获取数据后在客户端渲染的。

不过,它也可能带来一些问题,例如:

  • 由于它将数据内容与父组件的子组件绑定在一起,因此会产生高度耦合。这会使这些组件难以维护。
  • 这也违背了单一责任的理念,因为子组件并不单独对其数据负责,因此依赖于父组件的数据。
  • 加载时间长,因为它需要一次性获取所有组件的所有数据。

为了实现单一责任,我们可以重组父组件,以如下方式显示组件:

js 复制代码
const ProductDetails = ({productId, children}) => {
    const details = fetchProductDetails(productId);
    return (
        <>
            {{ children }}
        </>
    )
}

const ProductItem = ({productId}) => {
    const item = fetchProductItem(productId);
    return (...)
}

const MatchedItems = ({productId}) => {
    const items = fetchMatchedItems(productId);
    return (...)
}


const ProductPage = ({ productId })=> {
    return (
        <>
            <ProductDetails productId={productId}>    
                <ProductItem productId={productId} />
                <MatchedItems productId={productId} />
            <ProductDetails>
        </>
    )
}

使用这种方法效果很好,而且有其优点,例如:

  • 单一责任: 每个组件对自己的数据负责。

但是,它可能会产生一些问题,例如:

  • 它可能不适合用户体验,因为任何一个子组件都可能根据其 API 调用的加载时间,先于另一个组件在客户端渲染,从而使用户先于另一个组件看到页面的一部分。
  • 此外,由于 ProductDetails 组件将先于子组件(ProductItem、MatchedItems)呈现,因此数据的顺序获取会造成网络瀑布流的情况。

这些方法各有利弊,但有一个共同的局限性。这个限制是,这两种方法都需要从客户端向服务器调用 API,这会造成客户端和服务器之间的高延迟。

正是这一限制促使 React 团队引入了服务器组件:服务器上的组件。由于服务器组件存在于服务器上,因此与在应用程序客户端渲染的组件相比,它们可以更快地进行 API 调用并快速渲染。

虽然最初是为了解决高延迟的限制,但新的应用也随之出现。由于组件驻留在服务器上,它们可以访问服务器基础设施,这意味着它们可以连接到数据库并对其进行查询。

React 服务器组件与客户端组件的区别

服务器组件和客户端组件的一个主要区别是,服务器组件在服务器上呈现组件,而客户端组件在客户端上呈现。

通常,对于客户端的 React 应用程序来说,当用户从服务器请求网页时,服务器会将网页(Javascript 文件)响应给浏览器。浏览器下载数据(Javascript 文件)并用于构建网页。 另一方面,客户端组件会发送到客户端,从而增加了应用程序的捆绑大小(客户端组件是典型的传统 React 组件)。

另一个区别在于它们的呈现环境,这赋予了它们不同的属性,具体解释如下:

  • 服务器组件不能使用 React 钩子,如 useState、useReducer、useEffect 等。这是因为服务器组件是在服务器上呈现的,因此无法访问可影响 DOM(文档对象模型)的钩子,而 DOM 仅存在于客户端。另一方面,客户端组件是普通的 React 组件,仍然可以访问钩子。
  • 服务器组件无法访问浏览器 API,如 SessionStorage、localStorage 等。另一方面,客户端组件是普通的 React 组件,仍然可以访问浏览器 API。
  • 服务器组件可以对数据库、内部服务、文件系统等服务器专用数据源使用 async/await,而客户端组件则不能直接访问服务器专用数据源。

React 服务器组件与 React 服务器端呈现(SSR)的区别。

React 中的服务器端呈现(SSR)是指应用程序将服务器上的 React 组件转化为完全呈现给客户端的静态 HTML 页面的能力。 另一方面,React 服务器组件(React Server Components)通过一个中介结构(类似于 JSON 格式的协议)与 SSR 协作,从而在不向客户端交付任何捆绑包的情况下实现渲染。

服务器组件案例研究。

我们将说明如何在传统的 React 应用程序和 Next.js 应用程序中使用服务器组件。

在 React 应用程序中使用服务器组件。 在典型的 React 应用程序中,服务器组件就像普通的 React 组件一样。

请注意,要在带有 .tsx 文件的 typescript 组件中使用 async/await,需要将 typescript 版本升级到 5.1.1。要了解更多信息,请访问此处

下面是一个服务器组件的示例:

js 复制代码
// Server Component

 const BlogPost = async({id, isEditing}) => {
 const post = await db.posts.get(id);

  return (
    <div>
      <h1>{post.title}</h1>
      <section>{post.body}</section>
    </div>
  );
}

客户端组件看起来就像普通的 React 组件,但在组件文件中添加了 use client 指令。从技术上讲,use client 指令声明了服务器组件和客户端组件之间的边界。

js 复制代码
// A client component

'use client'

import React, { useState } from "react";
import { v4 as uuidv4 } from 'uuid';

const PostEditor = ({ blogPost }) => {

  const [post, setPost] = useState<any>({
    id: uuidv4(),
    title: blogPost.title,
    content: blogPost.content,
  })

  const onChange = (type: any, value: any)=> {
    switch(type){
      case "title":
        setPost({...post, title: value})
        break;
      case "content":
        setPost({...post, content: value})
        break;
      default:
        break
    }
  }

  const submitPost = ()=> {
    // save blog post
  };

  return (
    <div>
      <div className="md:mx-auto px-6 md:px-0 mt-10 md:w-9/12">
        <h1 className="my-4 text-center">Create Post</h1>

        <form onSubmit={submitPost}>
          <div className="mt-8">
            <label className="text-white mb-2"> Title </label>
            <input 
              type="text" placeholder="" 
              value={post.title}
              required 
              onChange={(e)=> onChange("title", e.target.value)}
            />
          </div>

          <div className="mt-8">
            <label className="text-white mb-2">
              Add your Blog content
            </label>
            <textarea
              value={post.content}
              required
              onChange={(e)=> onChange("content", e.target.value)}
            ></textarea>
          </div>

          <div className="flex justify-end mt-8">
            <button
              type="submit"
              className="px-4 py-4 bg-[#0e9f64] c-white border-radius"
            >
              Create Post
            </button>
          </div>
        </form>
      </div>
    </div>
  );
};

export default PostEditor;

在使用服务器和客户端组件时,有一些特定的规则需要了解:

服务器组件不能导入到客户端组件中,但客户端组件可以导入到服务器组件中。我们将用下面的例子来说明如何将客户端组件导入到服务器组件中:

js 复制代码
// Server Component

import db from 'db'; 
import NoteEditor from 'NoteEditor';

async function BlogPost({id, isEditing}) {
  const post = await db.posts.get(id);

  return (
    <div>
      <h1>{post.title}</h1>
      <section>{post.body}</section>
      {isEditing 
        ? <PostEditor blogPost={post} />
        : null
      }
    </div>
  );
}

在上面的代码中,我们将 PostEditor(客户端组件)导入了服务器组件。

当客户端组件位于服务器组件内时,服务器组件可以作为子 prop 传递给客户端组件。

js 复制代码
const ServerComponent1 = () => {
    return (
        <ClientComponent>
            <ServerComponent2 />
        </ClientComponent>
    )
}

在 Next 应用程序中使用服务器组件

默认情况下,服务器组件是在 Next 13 中新引入的 App 目录中创建的普通 React 组件。

js 复制代码
// Server Component

 const BlogPost = async({id, isEditing}) => {
 const post = await db.posts.get(id);

  return (
    <div>
      <h1>{post.title}</h1>
      <section>{post.body}</section>
    </div>
  );
}

Next 13 中的客户端组件看起来像普通的 React 组件,但在组件文件中添加了 use client 指令。

js 复制代码
// A client component

'use client'

import React, { useState } from "react";
import { v4 as uuidv4 } from 'uuid';

const PostEditor = ({ blogPost }) => {

  const [post, setPost] = useState<any>({
    id: uuidv4(),
    title: blogPost.title,
    content: blogPost.content,
  })

  const onChange = (type: any, value: any)=> {
    switch(type){
      case "title":
        setPost({...post, title: value})
        break;
      case "content":
        setPost({...post, content: value})
        break;
      default:
        break
    }
  }

  const submitPost = ()=> {
    // save blog post
  };

  return (
    <div>
      <div className="md:mx-auto px-6 md:px-0 mt-10 md:w-9/12">
        <h1 className="my-4 text-center">Create Post</h1>

        <form onSubmit={submitPost}>
          <div className="mt-8">
            <label className="text-white mb-2"> Title </label>
            <input 
              type="text" placeholder="" 
              value={post.title}
              required 
              onChange={(e)=> onChange("title", e.target.value)}
            />
          </div>

          <div className="mt-8">
            <label className="text-white mb-2">
              Add your Blog content
            </label>
            <textarea
              value={post.content}
              required
              onChange={(e)=> onChange("content", e.target.value)}
            ></textarea>
          </div>

          <div className="flex justify-end mt-8">
            <button
              type="submit"
              className="px-4 py-4 bg-[#0e9f64] c-white border-radius"
            >
              Create Post
            </button>
          </div>
        </form>
      </div>
    </div>
  );
};

export default PostEditor;

React 服务器组件的利与弊

我们将介绍在开发中使用服务器组件的优点以及缺点。

优点:

  • 减少捆绑: 服务器组件是 "零捆绑 "组件,因为它们不会增加客户端渲染的 Javascript 捆绑大小。
  • 访问服务器基础设施: 使用服务器组件,可以无缝连接数据库、文件系统等服务器基础设施。
  • 由于可以将 API 调用委托给在服务器上运行的服务器组件,因此减少了客户端的延迟。

缺点:

  • 服务器组件无法访问客户端功能。
  • 由于服务器组件可提供与普通 SSR(服务器端渲染)应用程序几乎相同的优势,而且许多人已习惯使用 SSR,因此其采用可能不会很快。
  • 由于服务器组件可以访问服务器基础架构,它可能会导致应用程序设计不佳,因为它可能会鼓励开发人员回避创建 API 或甚至独立的后端,而是直接通过服务器组件执行查询和连接数据库。

结论

在本文中,我们介绍了 React 中的服务器组件,并讨论了它们的用途和优点。React 服务器组件使我们能够以一种全新的方式在 React 应用程序中将客户端和服务器端渲染组件的优点结合起来。

相关推荐
麒麟而非淇淋6 分钟前
AJAX 入门 day3
前端·javascript·ajax
茶茶只知道学习18 分钟前
通过鼠标移动来调整两个盒子的宽度(响应式)
前端·javascript·css
蒟蒻的贤21 分钟前
Web APIs 第二天
开发语言·前端·javascript
清灵xmf25 分钟前
揭开 Vue 3 中大量使用 ref 的隐藏危机
前端·javascript·vue.js·ref
蘑菇头爱平底锅27 分钟前
十万条数据渲染到页面上如何优化
前端·javascript·面试
su1ka11130 分钟前
re题(35)BUUCTF-[FlareOn4]IgniteMe
前端
测试界柠檬32 分钟前
面试真题 | web自动化关闭浏览器,quit()和close()的区别
前端·自动化测试·软件测试·功能测试·程序人生·面试·自动化
多多*33 分钟前
OJ在线评测系统 登录页面开发 前端后端联调实现全栈开发
linux·服务器·前端·ubuntu·docker·前端框架
2301_8010741533 分钟前
TypeScript异常处理
前端·javascript·typescript
小阿飞_35 分钟前
报错合计-1
前端