【译】React 中的服务器组件简介

作者:彼得·奥萨

介绍

最近,React 服务器组件引起了很多炒作和兴奋。这源于 React 服务器组件允许开发人员将与组件相关的任务外包给服务器的想法。这样就无需分发捆绑的 JavaScript 和外部 API 查询来补充组件,也消除了导致客户端应用程序延迟增加的情况。

在本文中,我们将讨论 React 服务器组件是什么以及如何将它们合并到构建应用程序中。

什么是服务器组件?

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

服务器组件以自定义格式呈现,没有标准协议,但类似于 JSON 格式。React DOM 可以识别这种格式,并在识别后适当地呈现它。

问题陈述引入了 React 服务器组件的思想

我们将创建一个场景来介绍服务器组件的概念。

我们可以将产品页面构造如下:

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

现在,有多种方法可以为将在此页面上呈现的 API 内容实现数据获取解决方案。我们可以一次性将数据获取到组件,如下所示:

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 组件将在子组件(ProductItemMatchedItems)之前首先渲染,从而按顺序获取数据。

这些方法各有优缺点,但是它们都有一个共同的局限性。局限性在于这两种方法都需要从客户端 对服务器进行 API 调用,这可能会导致客户端和服务器之间出现高延迟的情况。

这种限制最初促使 React 团队引入服务器组件:服务器上的组件。由于服务器组件存在于服务器上,因此它们可以比在应用程序客户端呈现的组件更快地调用 API,并且可以更快地呈现。

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

React 服务器组件和客户端组件之间的区别

服务器组件和客户端组件之间的主要区别在于,服务器组件在服务器上呈现组件,而客户端组件在客户端上呈现。

通常,对于客户端React应用程序,当用户从服务器请求网页时,服务器通过页面(Javascript文件)响应浏览器。浏览器下载数据(Javascript 文件)并使用它来构建网页。

对于服务器组件,没有 JavaScript 发送到客户端,从而减少了发送到客户端的 JavaScript 包。另一方面,客户端组件被发送到客户端并增加应用程序的包大小(客户端组件是典型的传统 React 组件)。

另一个区别在于它们的渲染环境,这赋予了它们不同的属性,如下所述:

  • 服务器组件不能使用 React hooks useState,例如useReduceruseEffect、 等。这是因为服务器组件是在服务器上渲染的,并且无法访问可以影响DOM仅存在于客户端的(文档对象模型)的钩子。另一方面,客户端组件是一个普通的 React 组件,它仍然可以访问钩子。
  • 服务器组件无法访问浏览器 API SessionStoragelocalStorage例如 等。另一方面,客户端组件是普通的 React 组件,仍然可以访问浏览器 API。
  • 服务器组件可用于async/await仅服务器数据源,例如数据库、内部服务、文件系统等,而客户端组件无法直接访问仅服务器数据源。

React 服务器组件和 React 中的服务器端渲染(SSR)之间的区别。

对于 React 来说,服务器端渲染 (SSR) 是指应用程序将服务器上的 React 组件转换为客户端完全渲染的静态 HTML 页面的能力。

另一方面,React 服务器组件通过中间结构(类似于 JSON 格式的协议)与 SSR 配合使用,以实现渲染,而无需向客户端传递任何包。

服务器组件的案例研究。

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

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

在典型的 React 应用程序中,服务器组件就像常规的 React 组件。

另请注意,为了async/await在带有文件的 TypeScript 组件中使用.tsx,您需要将 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 应用程序中使用服务器组件。

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

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 组件,但在组件文件中Next 13添加了一条指令。'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 应用程序中。我希望本文能够说服您立即测试 React 服务器组件。

相关推荐
天下无贼!37 分钟前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr37 分钟前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林40 分钟前
npm发布插件超级简单版
前端·npm·node.js
我码玄黄1 小时前
THREE.js:网页上的3D世界构建者
开发语言·javascript·3d
罔闻_spider1 小时前
爬虫----webpack
前端·爬虫·webpack
吱吱鼠叔1 小时前
MATLAB数据文件读写:1.格式化读写文件
前端·数据库·matlab
爱喝水的小鼠2 小时前
Vue3(一) Vite创建Vue3工程,选项式API与组合式API;setup的使用;Vue中的响应式ref,reactive
前端·javascript·vue.js
小晗同学2 小时前
Vue 实现高级穿梭框 Transfer 封装
javascript·vue.js·elementui
WeiShuai2 小时前
vue-cli3使用DllPlugin优化webpack打包性能
前端·javascript
Wandra2 小时前
很全但是超级易懂的border-radius讲解,让你快速回忆和上手
前端