将 React 代码库迁移到 Web 组件

2025-06-07

将 React 代码库迁移到 Web 组件

我最近将一个浏览器扩展从 React 迁移到了 Web Components。这篇文章描述了我的观察、学习和痛点。文中提到的所有内容仅适用于原生 Web Components。stencil和lit等第三方库提供了不同的解决方案。

该扩展有何作用?

该扩展程序可从浏览器的任何标签页控制 Youtube™ 视频的播放。即使浏览器处于后台,用户也可以设置快捷方式来播放/暂停视频。

您可以在 Chrome 上安装该扩展程序并在 Github 上检查代码。

我为什么要移民?

该扩展最初于 2015 年使用原生 JavaScript 编写。随着代码库的复杂性不断增加,UI 的维护也变得越来越困难。我想要一个小型框架来帮助我将界面组织成组件。我选择了hyperapp,因为它的包体积小,API 也看似简单。毕竟,它学习起来并不容易,而且当时文档也比较匮乏。

几个月后,我迁移到了 React。我想要一个熟悉的库,这样就不需要从头开始学习了。最终,我对代码质量很满意。然而,我越想越意识到,我的解决方案设计过度了。

我真的需要发送两个相当大的包reactreact-dom?答案是否定的,即使扩展程序是在本地加载的,包大小也不是主要问题。另一个问题是代码压缩。虽然压缩代码并不被禁止,但它可能会在某些平台上延迟审核流程。

我需要一个不依赖外部库的轻量级解决方案。于是就有了 Web Components。

Web 组件概述

Web 组件是一组具有非常好的浏览器支持的标准:

  • 自定义 HTML 元素
  • 影子 DOM
  • 模板
  • EJS 模块

阅读有关规格的更多信息。

Web Components 与 React 的比较

以下是我在迁移过程中学到的东西的列表。

自定义元素是类

自从 Hooks 发布以来,我完全停止在 React 中编写类组件了。然而,自定义元素只能声明为类。它们拥有特定的方法(这里没有双关语)来钩住元素的生命周期。从这个意义上讲,它们与 React 中的类组件非常相似。最大的区别之一是缺少 render 方法。

回到命令式编程

构建 React 组件归根结底就是描述最终结果,然后让库处理剩下的事情。这可以通过类组件的 render 方法或函数式组件的返回结果来实现。另一方面,自定义元素需要直接操作 DOM 元素才能达到相同的效果。DOM 元素会被查询、创建、插入和修改。

反应:

const CapitalisedText = ({ text }) => {
  return <div>{text.toUpperCase()}</div>;
};
Enter fullscreen mode Exit fullscreen mode

Web 组件:

class CapitalisedText extends HTMLElement {
  connectedCallback() {
    const text = this.getAttribute("text");
    const div = document.createElement("div");
    div.appendChild(document.createTextNode(text.toUpperCase()));

    this.appendChild(div);
  }
}
Enter fullscreen mode Exit fullscreen mode

模板中没有绑定

模板并不等同于 React 组件的渲染块。它无法传递和渲染 JavaScript 数据,也无法运行条件或循环。所有这些都必须在自定义元素的生命周期方法中进行。

文档中定义的模板:

<template id="capitalised-text-template">
  <div></div>
</template>
Enter fullscreen mode Exit fullscreen mode

Web 组件使用模板但必须执行必要的 DOM 更新:

class CapitalisedText extends HTMLElement {
  connectedCallback() {
    const template = document.querySelector("#capitalised-text-template");
    this.appendChild(template.content.cloneNode(true));

    const text = this.getAttribute("text");
    const div = this.querySelector("div");
    div.appendChild(document.createTextNode(text.toUpperCase()));
  }
}
Enter fullscreen mode Exit fullscreen mode

开箱即用的 CSS 作用域

有很多解决方案可以用于在 React 组件中设置 CSS 的作用域。例如 CSS 模块、不同的 CSS-in-JS 库等等。在自定义元素中使用 Shadow DOM 即可获得开箱即用的支持。自定义元素中定义的样式不会泄露到文档的其余部分,文档中其他位置声明的样式也不会泄露到自定义元素中。在编写可复用组件时,此功能非常强大,但在其他情况下可能会受到限制。不过,编写自定义元素时始终可以使用 Shadow DOM。

使用React 的css 模块来避免样式冲突:

import styles from "./stlyle.css";

const CapitalisedText = ({ text }) => {
  return <div className={styles.text}>{text.toUpperCase()}</div>;
};
Enter fullscreen mode Exit fullscreen mode

使用 Web 组件中的影子 DOM 来封装样式:

<template id="capitalised-text-template">
  <style>
    .text {
      font-weight: 600;
    }
  </style>
  <div class="text"></div>
</template>
Enter fullscreen mode Exit fullscreen mode
class CapitalisedText extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: "open" });

    const template = document.querySelector("#capitalised-text-template");
    shadowRoot.appendChild(template.content.cloneNode(true));
  }
  connectedCallback() {
    const text = this.getAttribute("text");
    const div = this.shadowRoot.querySelector("div");
    div.appendChild(document.createTextNode(text.toUpperCase()));
  }
}
Enter fullscreen mode Exit fullscreen mode

自定义元素保留在 DOM 树中

当 React 渲染组件时,它会创建并附加 DOM 元素。组件本身在 DOM 树中是看不到的。自定义元素是 DOM 树的一部分。在考虑查询和样式时,这一点非常重要。

反应:
React 组件的 dom 元素结构

Web 组件:
Web 组件的 DOM 元素结构

属性与特性

React 组件的 props 可以是任意数据类型。自定义元素与任何内置 HTML 元素一样,具有只能包含字符串的属性 (Attribute)。它们还具有可以包含任意数据类型但只能使用 JavaScript 设置的属性 (Property)。了解更多关于属性 (Attribute) 和属性 (Property) 的信息。

监听属性变化是可选的

当 props 值发生变化时,React 组件会重新渲染。Web 组件会公开attributeChangedCallback可用于响应属性变化来更新 UI 的回调。但是,此回调默认不会触发。每个 Web 组件都必须使用静态observedAttributes方法明确列出要监听的属性。

结论

结论:
Web 组件在使用原生 JavaScript 管理代码库方面表现出色。然而,一些开发者可能认为当前的标准只是基本框架。事实上,“组件”一词有些误导。它与现有的框架和库类似,但这些框架和库的组件功能更多。

总的来说,我对迁移到 Web Components 的决定感到满意。我肯定会在其他项目中再次使用它们。

你对 Web Components 有什么看法?你认为它们能取代 React 或 Vue 之类的工具吗?

文章来源:https://dev.to/maroun_baydoun/migration-a-react-codebase-to-web-components-3d8m
PREV
人工智能初创企业的 8 大开源工具
NEXT
学习 JavaScript Promises 什么是 Promises?为什么要使用 Promises?处理多个 Promises