返回首页

Symbiote.js:无虚拟 DOM 的 Web 组件组合 | 指南

使用 Symbiote.js 创建 Web 组件的技术指南。我们分解选项卡实现、共享上下文机制和低代码方法益处。适合中高级开发者。

Symbiote.js:如何通过 HTML 像积木一样组装界面
Advertisement 728x90

Symbiote.js:无需虚拟 DOM 的 Web 组件高级组合

Symbiote.js 是一个轻量级(~6 KB Brotli)库,用于创建 Web 组件,专注于通过 HTML 标记实现高级组合。它通过消除对虚拟 DOM 的依赖,并提供灵活的数据上下文处理机制,解决了界面结构组织的难题。与传统框架不同,Symbiote.js 直接操作 DOM API,减少样板代码,并允许通过声明式标记组合组件。

结构组合基础

界面开发包括两个方面:逻辑模型(组件、数据)和结构组合(组件连接、数据流)。Symbiote.js 为后者提供了一种独特方法,将 HTML 作为定义依赖的一等工具。主要原则:

  • 组件模板是独立的 HTML 字符串
  • 外部标记定义结构连接
  • 不绑定 JS 运行时
  • 支持混合方法(服务器/客户端渲染)

这允许你激活预生成的标记,创建无自身逻辑的“哑”组件,或使用上下文提供器。例如,使用两个关联组件实现选项卡:

Google AdInline article slot
import Symbiote, { css } from '@symbiotejs/symbiote';

class SuperTabs extends Symbiote {
  init$ = {
    '*currentTabName': 'first',
  };

  renderCallback() {
    this.tabEls = [...this.querySelectorAll('[tab]')];
    this.tabEls.forEach((/** @type {HTMLElement} */ el) => {
      let tab = el.getAttribute('tab');
      if (el.hasAttribute('current')) {
        this.$['*currentTabName'] = tab;
      }
      el.onclick = () => {
        this.$['*currentTabName'] = tab;
      };
    });
    this.sub('*currentTabName', (val) => {
      this.tabEls.forEach((/** @type {HTMLElement} */ el) => {
        if (el.getAttribute('tab') === val) {
          el.setAttribute('current', '');
        } else {
          el.removeAttribute('current');
        }
      });
    });
  }
}

SuperTabs.rootStyles = css`
super-tabs {
  display: inline-flex;
  gap: 2px;
  [tab] {
    cursor: pointer;
    &[current] {
      background-color: transparent;
      pointer-events: none;
    }
  }
}
`;

SuperTabs.reg('super-tabs');

第二个组件负责内容显示:

class SuperTabsView extends Symbiote {
  renderCallback() {
    this.tabCtxEls = [...this.querySelectorAll('[tab-ctx]')];
    this.sub('*currentTabName', (val) => {
      this.tabCtxEls.forEach((/** @type {HTMLElement} */ el) => {
        if (el.getAttribute('tab-ctx') === val) {
          el.setAttribute('active', '');
        } else {
          el.removeAttribute('active');
        }
      });
    });
  }
}

SuperTabsView.rootStyles = css`
super-tabs-view {
  display: block;
  [tab-ctx] {
    display: none;
    &[active] {
      display: contents;
    }
  }
}
`;

SuperTabsView.reg('super-tabs-view');

关键技术特性

示例展示了五个至关重要的机制:

  • rootStyles ——无论父元素是否使用 Shadow DOM,样式都能正确应用。这确保了与 Tailwind、Bootstrap 和全局样式的兼容性。
  • renderCallback ——生命周期钩子,可访问子节点(不同于标准的 connectedCallback)。
  • init$ ——使用默认值初始化响应式属性。
  • sub() ——通过 Pub/Sub 订阅属性变化,无需虚拟 DOM。
  • *通过 (星号)声明属性** ——声明共享上下文(类似于 input[type=radio] 的 name 属性)。

架构特性是直接操作 DOM API 而无需抽象层。这不是反模式,而是有意选择,以消除虚拟表示的开销。同时,该库支持类型检查、响应性和运行时调试。

Google AdInline article slot

低代码范式实战

Symbiote.js 的优势在无需 JavaScript、仅通过 HTML 属性组装界面的场景中得以体现。考虑一个显示当前选项卡的组件:

class SuperCurrent extends Symbiote {}

SuperCurrent.rootStyles = css`
super-current {
  display: block;
  h2 {
    text-transform: capitalize;
  }
}
`;

SuperCurrent.template = html`<h2>{{*currentTabName}}</h2>`;

SuperCurrent.reg('super-current');

使用示例:

<super-current ctx="section-select"></super-current>

这种方法为以下场景开启了可能性:

Google AdInline article slot
  • 设计师:直接在 HTML 中原型化界面
  • 内容管理员:通过属性自定义显示,无需代码
  • 嵌入式解决方案:通过标记配置小部件
  • AI 系统:生成具有可预测标记和行为连接的界面

库中组件越多,通过 HTML 组装复杂界面的难度越低。每组组件通过 ctx 属性隔离,允许页面上存在多个独立实例。

核心要点

  • 零打包工具依赖:组件无需 Webpack/Vite 即可工作
  • 通过 DOM 实现上下文:组件间连接由标记结构定义
  • 6 KB 体积:对性能影响最小
  • 兼容任意技术栈:无需迁移即可集成到现有项目
  • 开箱即用类型支持:无需额外配置即可支持 TypeScript

Symbiote.js 不会取代框架,而是在结构组合领域对其进行补充。对于逻辑任务,它提供极简 API,专注于其他解决方案通过第三方库实现的功能(状态管理、路由)。这在设计界面时降低了认知负担,尤其是在成员多样化的团队中。

— Editorial Team

Advertisement 728x90

继续阅读