在 Shopify 探索 React Server Component 的最佳实践

简介: Shopify 是国外的一个允许客户自由搭建商城的 no code 产品,工程师 Cathryn Griffiths 分享了他在 Shopify 中实用 React Server Component 的最佳实践。

Shopify 是国外的一个允许客户自由搭建商城的 no code 产品,工程师 Cathryn Griffiths 分享了他在 Shopify 中实用 React Server Component 的最佳实践。

Hydrogen 是基于 React 的框架用来创建自定义店面的框架,他们试用 RSC(React Server Component)有两个理由:

  1. 再见了,臃肿的 bundle 体积,你好,更棒的购物体验!
  2. 技术人的一种自私情结:这玩意一定很有趣!

这是一件很有挑战性的事。RSC 是一种范式转变,一开始他们遇到的问题是构建的客户端组件太多,服务器组件太少。经过数月的反复尝试和重构才找到较好的方案。

这篇文章将着重讨论工程师在构建 Hydrogen 时候发现的 RSC 最佳实践,不光是对个人的,也是对团队的。希望能让读者们更加理解如何在 RSC 应用中编写组件,减少你的无效时间。


优先写共享组件


当你需要在 RSC 应用程序中从头构建组件时,请从共享组件开始。共享组件可以同时在服务器和客户端上下文中执行,而不会出现任何问题。它们是客户端和服务器组件之间的天然中间地带,是个不错的起点。

从中间地带开始,可以帮助你更好的思考,引导你构建正确类型的组件。你必须问自己:“这段代码只能在客户机上运行吗?”,类似地,“这段代码应该在客户机上执行吗?”下一节列出了一些您应该问的问题。

不要总是默认构建客户端组件。虽然方便,但最后应用程序会太臃肿,很多组建更适合在服务端运行。


在少数情况下选择客户端组件


RSC 应用程序中的大多数组件应该是服务器组件,因此在确定是否需要客户端组件时,需要仔细分析用例。

通常只有客户端特定的逻辑部分需要被提取到客户端组件中:

  • 整合客户端交互性
  • 用了 useStateuseReducer
  • 用了生命周期渲染逻辑(比如 useEffect
  • 用了不支持 RSC 的第三方库
  • 用了服务端不支持的浏览器 APIs

重要说明:不要只是盲目将整个共享组件转换为客户端组件。相反,有意地提取需要的特定功能。这有助于保持您的客户端组件和 bundle 尺寸尽可能的小。文章末尾会有一些示例。


尽可能以服务端组件为主


如果组件不包含任何客户端组件用例,那么它应该被改为服务器组件(如果它符合以下条件之一):

  • 该组件包含不应该在客户端上暴露的代码,如专用业务逻辑和密钥。
  • 客户端组件中不会使用该组件。(RSC 的限制,客户端组件中不能直接导入服务端组件)
  • 代码从不在客户端上执行(据你所知)。
  • 代码需要访问文件系统或数据库(客户端上不可用)。
  • 代码需要从 StoreFront API 获取数据(在 Hydrogen 中特定的情况)。

如果组件需要在客户端组件中使用,可以先深入研究用例和实现。很可能你可以将组件实例作为 children props 传递给客户端组件,而不是让客户端组件直接导入并实用它。这样就不需要把组件转换为客户端组件了。


探索一些例子


有很多东西需要记住,我们可以用 Hydrogen 启动模板[1]来试几个例子。


订阅注册


第一个示例是一个组件,它允许买家注册订阅我的在线商店的时事通讯。它出现在每个页面的页脚,看起来像这样:

我们从一个名为 NewsletterSignup.jsx的共享组件开始:

export default function NewsletterSignup() {
  return (
    <div>
      <p>
        Sign up for our newsletter to never miss out on latest news and product
        drops!
      </p>
      <label for="emailInput">Email</label>
      <input type="text" id="emailInput" name="email" placeholder="Email" />
      <button
        onClick={() => {
          /* TODO */
        }}
      >
        Sign me up
      </button>
    </div>
  );
}

在这个组件中,我们有两个客户端交互部分(输入字段和提交按钮),这说明这个当前编写的组件不能是共享组件。

我们别将其完全转换为客户端组件,而是将客户端功能提取到一个单独的 NewsletterSignupForm.client.jsx组件里:

export default function NewsletterSignupForm() {
  return (
    <>
      <label for="emailInput">Email</label>
      <input type="text" id="emailInput" name="email" placeholder="Email" />
      <button
        onClick={() => {
          /* TODO */
        }}
      >
        Sign me up
      </button>
    </>
  );
}

然后更新 NewsletterSignup 组件来使用这个客户端组件:

import NewsletterSignupForm from './NewsletterSignupForm.client';
export default function NewsletterSignup() {
  return (
    <div>
      <p>
        Sign up for our newsletter to never miss out on latest news and product
        drops!
      </p>
      <NewsletterSignupForm />
    </div>
  );
}

我们很容易到此为止,并将 NewsletterSignup 组件保持为一个共享组件。然而我知道这个组件只在我的在线商店的页脚中使用,而我的页脚组件是一个服务端组件。所以它不需要是一个共享组件,也不需要成为客户端 bundle 的一部分,简单地将其重命名为 NewsletterSignup.server.jsx来安全地将其更改为服务端组件。

搞定,你可以在最终的 Stackblitz 代码示例[2] 中查看这个时事通讯注册组件。


产品常见问题组件


在下一个示例中,我们将产品常见问题部分添加到产品页面。这里的内容是静态的,对我的在线商店中的每个产品都是一样的。来自买家的互动可以展开或收起内容。它看起来是这样的:

让我们从一个共享的ProductFAQs.jsx开始。jsx 组件:

export default function ProductFAQs() {
  return (
    <ul>
      <li>
        <span>Where was this board made?</span>
        <p>
          All our boards are designed in Canada by our Hydrogen design team.
        </p>
        <p>Materials are sourced from local manufacturers.</p>
        <p>
          Assembly is done by our skilled team on site in our brick and mortar
          shop.
        </p>
      </li>
      <li>
        <span>What if I don't like it?</span>
        <p>
          The Hydrogen team stands by their products. We strive to delivery high
          quality boards that will last a lifetime and, importantly, make you
          happy.
        </p>
        <p>
          That said, if you don't like it, you can return it to us (free of
          cost!) and we'll reimburse you the money. Contact us directly for more
          details.
        </p>
      </li>
    </ul>
  );
}

接下来,我们将把它添加到产品页面。ProductDetails.client 组件用于展示此页面的主要内容,因此很容易把ProductFAQs转换为客户端组件,这样 ProductDetails 组件可以直接导入使用它。但是,我们可以通过将 ProductFAQs 传递给 product/[handle].server.jsx 页面来避免这种情况:

import ProductFAQs from '../../components/ProductFAQs';
export default function Product({ country = { isoCode: 'US' } }) {
  // ...
  return (
    <Layout>
      <ProductDetails product={data.product}>
        <ProductFAQs />
      </ProductDetails>
    </Layout>
  );
}

然后更新 ProductDetails组件来使用 children:

export default function ProductDetails({ product, children }) {
  // ...
  return (
    <>
      <Seo product={product} />
      <Product product={product} initialVariantId={initialVariant.id}>
        ...
      </Product>
      {children}
    </>
  );
}

接下来,我们想要将客户端交互部分添加到 ProductFAQs 组件。同样,我们很容易直接将 ProductFAQ 组件从共享组件转换为客户端组件,但没必要。这些交互仅用于展开和收起 FAQ 内容,而内容本身是硬编码的,不需要成为客户端 bundle 的一部分。我们要做的是将客户端交互提取到一个专门的客户端组件Accordion.client.jsx:

import { useState } from 'react';
export default function Accordion({ heading, children }) {
  const [open, setOpen] = useState(false);
  return (
    <div>
      <div
        onClick={() => {
          setOpen(!open);
        }}
      >
        <span>{heading}</span>
        <span>{open ? '-' : '+'}</span>
      </div>
      {open && children}
    </div>
  );
}

更新ProductFAQs组件来使用Accordion

import Accordion from './Accordion.client';
export default function ProductFAQs() {
  return (
    <ul>
      <li>
        <Accordion heading="Where was this board made?">
          <>
            <p>
              All our boards are designed in Canada by our Hydrogen design team.
            </p>
            <p>Materials are sourced from local manufacturers.</p>
            <p>
              Assembly is done by our skilled team on site in our brick and
              mortar shop.
            </p>
          </>
        </Accordion>
      </li>
      <li>
        <Accordion heading="What if I don't like it?">
          <>
            <p>
              The Hydrogen team stands by their products. We strive to delivery
              high quality boards that will last a lifetime and, importantly,
              make you happy.
            </p>
            <p>
              That said, if you don't like it, you can return it to us (free of
              cost!) and we'll reimburse you the money. Contact us directly for
              more details.
            </p>
          </>
        </Accordion>
      </li>
    </ul>
  );
}

此时,不再有理由让 ProductFAQs 组件保持为共享组件了。所有的客户端交互都已经被提取出来,并且,类似于NewsletterSignup组件,我知道这个组件永远不会被客户端组件使用。现在剩下的就是:

  • 重命名 ProductFAQs.jsx 文件为 ProductFAQs.server.jsx
  • 更新 product/[handle].server.jsx 中的 import 声明
  • 通过 Tailwind 添加一些漂亮的样式。

你可以在 Stackblitz 中查看 Product FAQ 代码[3]

React Server Components 是一种范式转变,为 RSC 应用程序编写组件可能需要一些时间来适应。当你在构建时,请记住以下几点:

  • 从共享组件开始。
  • 在特定情况下,将功能提取到客户端组件中。
  • 如果代码永远不需要或永远不应该在客户机上执行,则改写为服务端组件。

享受 coding 吧!

Cathryn 是 Shopify Checkout 团队的前端开发人员,也是 Hydrogen 的创始成员。她在加拿大蒙特利尔远程工作。当不写代码的时候,她通常会和她的狗玩、做手工或阅读。

参考:https://shopify.engineering/react-server-components-best-practices-hydrogen

相关文章
|
24天前
|
存储 前端开发 测试技术
React组件的最佳实践
React组件的最佳实践
|
1月前
|
前端开发 JavaScript 测试技术
React Server Side Rendering (SSR) 详解
【10月更文挑战第19天】React Server Side Rendering (SSR) 是一种在服务器端渲染 React 应用的技术,通过在服务器上预先生成 HTML 内容,提高首屏加载速度和 SEO。本文从概念入手,逐步探讨 SSR 的实现步骤、常见问题及解决方案,并通过代码示例进行说明。
203 3
|
2月前
|
前端开发 JavaScript 开发者
React 组件化开发最佳实践
【10月更文挑战第4天】React 组件化开发最佳实践
63 4
|
2月前
|
前端开发 JavaScript API
React将组件作为属性传递的最佳实践
本文探讨了在React中将组件作为属性传递的三种常见方式:作为元素传递、作为组件传递、作为函数传递。通过构建带图标的按钮组件,对比分析了每种方式的优缺点,最终推荐将组件作为函数传递,因为它提供了更好的可控性、灵活性和可扩展性。
48 0
|
4月前
|
前端开发 JavaScript 大数据
React与Web Workers:开启前端多线程时代的钥匙——深入探索计算密集型任务的优化策略与最佳实践
【8月更文挑战第31天】随着Web应用复杂性的提升,单线程JavaScript已难以胜任高计算量任务。Web Workers通过多线程编程解决了这一问题,使耗时任务独立运行而不阻塞主线程。结合React的组件化与虚拟DOM优势,可将大数据处理等任务交由Web Workers完成,确保UI流畅。最佳实践包括定义清晰接口、加强错误处理及合理评估任务特性。这一结合不仅提升了用户体验,更为前端开发带来多线程时代的全新可能。
103 1
|
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` 文件配置示例展示如何实现不同场景下的页面跳转及流程控制,帮助开发者有效管理应用程序的页面流和用户交互,提升应用质量。
58 0
下一篇
DataWorks