Web Components跨框架组件库探索

1. 前言

在网约车业务早期阶段,产品需求迭代迅速,为了支持快速试错与灵活交付, 内部形成了多种技术栈并存的局面:历史项目基于 Vue2,新业务则转向 React。同时,由于早期各项目独立推进,尚未形成统一的设计规范和组件标准,不同项目在组件实现方式、样式规范与交互体验上存在较大差异。

这种多样化在短期内带来了灵活性,使团队能够快速响应业务需求,但随着项目规模和业务复杂度的增加,也逐渐演变成了技术挑战:

  • 组件复用困难:相同功能组件需要在不同框架中重复实现。
  • 维护成本增加:功能或样式的调整须在多套组件库中分别修改。
  • 用户体验不一致:不同框架实现可能导致交互和视觉风格不统一。

为解决这些问题,我们移动端前端团队今年开始探索一种能够“一次开发,多处复用”的组件库方案。

2. 目标与场景

2.1. 核心目标

为了解决团队多框架并存、组件重复开发和体验不一致的痛点,我们确定了三大核心目标:

  • 统一设计规范:建立标准化设计体系和组件规范,确保视觉风格与交互行为在各业务线、各技术栈中保持一致。
  • 跨框架复用:构建框架无关的组件实现层,使同一组件可在 Vue、React、小程序等技术栈中复用。
  • 提升交付一致性:减少风格偏差,降低多端维护成本和迭代风险,快速响应业务需求。

2.2. 应用场景

组件库主要面向以下场景:

  • 多技术栈并存:组件库提供统一的组件标准和实现,降低重复开发。
  • 多终端 / 多容器运行:支持 App WebView、小程序、移动浏览器等环境,保持一致体验。
  • 多租户与模块化业务:可快速适配多租户 SaaS、主题定制及模块化功能,实现灵活复用。

2.3. 现状分析与开发优先级

在启动统一组件库建设前,我们对现有项目使用的组件库进行了调研,发现共有 10 个独立组件库分布在不同业务线和技术栈中,但高频组件主要集中在少数几类,如 Button、Exception、Loading 等。

基于这些数据,我们制定了“以高频、通用组件优先建设”的策略,为快速落地和跨业务复用提供依据。

高频组件统计(示例)

组件

使用频次

技术栈分布

开发优先级

Exception

138

Vue、React

Toast

123

Vue、React

Button

82

Vue、React

Loading

62

Vue、React

Popup

57

Vue、React

注:实际组件库包含 20+ 组件,这里仅展示高频组件作为示例。

3. 业界方案调研与对比

为实现跨框架组件复用与统一设计规范,我们调研了多种业界主流方案,包括多套组件库维护、单一框架统一、F/A 分层设计及 Web Components 标准方案。

方案

实现方式

优点

缺点

代表方案

多套组件库

针对不同框架分别维护

简单快速上手

成本高、复用性差

-

单一框架统一

全量迁移至单一框架(如 React)

规范统一

复用性好

迁移成本、风险高

Vant/Ant Design

F/A分层设计

将组件拆分为基础逻辑层(Foundation)和框架适配层(Adapter)

可复用核心逻辑、支持多技术栈扩展

抽象复杂,学习门槛高

Semi Design

Web Components

基于浏览器原生标准实现

一次开发、多框架复用

工具链与兼容性需处理

Shoelace/FAST/Taro/QuarkD

综合对比可见,多套组件库方案维护成本高;单一框架方案迁移成本与风险较大;F/A 分层设计抽象度高、开发复杂。而 Web Components 方案在复用性、标准化及跨框架兼容性上最具长期价值,已被多个成熟组件库(Shoelace、FAST、Quarkd、Taro)验证可行。因此,我们选择以 Web Components 为底层技术路径,结合工具链(如 StencilJS)实现跨框架适配与一致的设计规范落地。

4. 组件库架构与设计规范

在明确目标与应用场景后,我们基于 Web Components 构建了跨框架组件库。整体过程可以沿着组件库架构图的层级顺序理解,从底层的设计规范到组件实现,再到框架适配,最后落地到各业务线支持的技术平台。

4.1. 组件库架构概览

4.2. 设计规范统一

在组件开发前,我们首先与 UI 同学协作,共同梳理了统一的设计规范,并基于 Design Tokens 统一了颜色、字体、间距、圆角、阴影等基础变量;同时也定义了组件的样式与交互规范,以保证不同业务线组件的视觉与行为一致。

  1. Token设计和实现

在工程实现上,我们采用双层 Token 架构,同时支持 Sass 编译期 TokenCSS Variables 运行时 Token,编译期 Token 负责系统通用变量的静态管理和派生计算,运行时 Token 则支持多品牌定制与主题动态切换,以兼顾体系化管理与动态扩展的灵活性。

例如,以下示例定义了一组中性色阶,用于统一全局灰度体系:

// 透明黑衍生的中性灰(从浅到深) $grays: ( 100: rgba(0, 0, 0, 0.03), 500: rgba(0, 0, 0, 0.2), ... 900: rgba(0, 0, 0, 0.9), ); // 便捷函数 @function gray($level) { @return map-get($grays, $level); }

这些系统通用 Token (如间距、圆角、阴影等)通常不会被业务直接修改,但品牌色或主题相关的变量是支持覆盖的。例如 $brand-color或 CSS Variable --brand-color可以在不同品牌主题文件中自定义,并通过函数式工具,快速生成不同状态的颜色,实现差异化:

$brand-color: #409EFF; // 透明度 @function brand-alpha($opacity) { @return rgba($brand-color, $opacity); } // 亮度调整 @function brand-lightness($delta) { @return adjust-color($brand-color, $lightness: $delta); } // 饱和度调整 @function brand-saturation($delta) { @return adjust-color($brand-color, $saturation: $delta); } // 色相调整 @function brand-hue($delta) { @return adjust-color($brand-color, $hue: $delta); } 

在构建阶段,我们会将 Sass Token 输出为 CSS Variables,确保运行时可覆盖与动态切换:

:root { --color-brand: #{$brand-color}; --color-brand-text: #{brand-lightness(-30)}; --gray-100: #{gray(100)}; --gray-500: #{gray(500)}; --gray-900: #{gray(900)}; }

通过这种方式,我们实现了 编译期 Token 的结构化管理 + 运行时变量覆盖 的双重能力,既保证了系统通用 Token 的一致性,又允许针对品牌和业务场景进行灵活扩展,同时兼顾了 Web Components Shadow DOM 的样式隔离特性,使组件在多框架、多终端下均能继承统一的主题。

  1. 组件设计示例

  1. Icon 设计示例

为提升图标的可维护性和跨终端一致性,我们将图标统一为 SVG 字体进行管理。

4.3. 组件实现

在组件实现的技术选型上,我们选择 Stencil.js 作为核心开发框架。它能够在保留 Web Components 原生特性的同时,提供类型推导、属性声明、状态管理等工程化能力,极大简化了组件开发与跨框架适配的复杂度。关于Stencil 本身的原理与生态网上资料已经较为丰富,这里就不再展开,更多可以参考官方文档或社区教程。在我们的实践中,Stencil 的关键价值主要体现在:组件的跨框架输出、类型一致性和样式隔离 三个方面。

接下来,我们以 Button 组件为例,展示具体实现思路与核心设计特性

import { Component, Host, h, Prop, Listen } from '@stencil/core'; import { pxToVw } from '../../utils/utils'; @Component({ tag: 'blm-button', styleUrl: './blm-button.css', shadow: true, }) export class BlmButton { /** 按钮类型 */ @Prop() type: 'primary' | 'success' | 'danger' | 'warning' = 'primary'; /** 按钮尺寸 */ @Prop() size: 'small' | 'normal' | 'big' | 'large' = 'normal'; /** 按钮图标 */ @Prop() icon?: string; /** 按钮形状 */ @Prop() shape: 'round' | 'square' = 'round'; /** 禁用状态 */ @Prop() disabled = false; /** 加载状态 */ @Prop() loading = false; /** 加载类型 */ @Prop() loadtype: 'circular' | 'spinner' = 'spinner'; /** 加载颜色 */ @Prop() loadingcolor: string = 'currentColor'; /** 加载大小 */ @Prop() loadingSize: number = 20; renderIcon = () => { if (this.icon) { return <blm-icon name={this.icon} />; } if (this.loading) { return ( <blm-loading color={this.loadingcolor} size={pxToVw(this.loadingSize)} type={this.loadtype} /> ); } return null; }; @Listen("click", { capture: true }) onClick(e: Event) { if (this.disabled || this.loading) { e.stopPropagation(); } } render() { const { type, size, shape, disabled } = this; return ( <Host type={type} size={size} shape={shape} disabled={disabled} > {this.renderIcon()} <slot></slot> </Host> ); } } 
:host { position: relative; display: inline-block; box-sizing: border-box; line-height: var(--button-height, 24px); text-align: center; border-radius: var(--button-border-radius, 8px); padding-left: var(--button-hspacing, 12px); padding-right: var(--button-hspacing, 12px); cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent; } :host([type="primary"]) { background-color: var(--color-brand); &:disabled { background-color: var(--gray-500); cursor: not-allowed; } }

在这段代码中,我们通过 Prop 默认值和渲染逻辑处理按钮状态(如加载、禁用、图标),确保组件行为一致。通过 Shadow DOM 样式被隔离,同时结合 Design Token 管理颜色、尺寸等变量,实现跨业务、跨框架一致的视觉体验。

4.4. 框架适配

在核心组件开发完成后,我们进入跨框架适配阶段。此阶段主要依赖 Stencil 的多框架输出能力,确保业务同学能够在熟悉的框架中直接使用组件,同时保持组件逻辑和样式的一致性。为此,我们使用了诸如 @stencil/react-output-target@stencil/vue-output-targetstencil-vue2-output-target等插件,分别生成 React、Vue3、Vue2 版本的组件库。

// stencil.config.ts import { Config } from '@stencil/core'; import { vueOutputTarget } from "@stencil/vue-output-target"; import { vueOutputTarget as vue2OutputTarget } from "stencil-vue2-output-target"; import { reactOutputTarget } from "@stencil/react-output-target"; export const config: Config = { ..., // 省略其他配置 outputTargets: [ { type: "dist-custom-elements", ... }, // React 输出 reactOutputTarget({ componentCorePackage: '@leopard-h5/components', proxiesFile: '../react/src/components.ts', ... }), // Vue3 输出 vueOutputTarget({ ... }), // Vue2 输出 vue2OutputTarget({ ... }), ], }; 

5. 框架适配与问题解决

到目前为止,一切都很顺利:我们使用 Stencil 编写出标准的 Web Components,并成功在 Vue 和 React中集成使用。理论上,这样的组件可以在框架中直接使用,但实际使用过程中,我们发现一些细节问题如:组件类名无法动态更新、事件绑定存在跨框架差异,以及低版本浏览器兼容等问题。

5.1. 宿主类名覆盖导致组件“消失”问题

在 Stencil 中,我们通常使用Host 组件为宿主元素(host element)添加默认类名,例如:

import { Component, Host, h } from '@stencil/core' @Component({ tag: 'blm-button', }) export class BlmButton { render() { return ( <Host> <slot></slot> </Host> ) } } 

渲染后,宿主元素<blm-button>会包含两个类名:

<blm-button>...</blm-button>

其中hydrated为 Stencil 初始化完成后自动添加,用于控制可见性(加载前为visibility: hidden

在业务层中,当我们给组件动态绑定类名,会发现组件在运行时突然“变白”或“消失”。例如:

// React <BlmButton className={dynamicCls}>Click</BlmButton> // Vue <blm-button :class="dynamicCls">Click</blm-button> 

造成这种现象的原因是因为Stencil 的 hydrated类名在组件可见性中起关键作用,而 React 与 Vue 在更新类名时会整体替换宿主元素的class属性,从而移除了hydrated 和原始的 blm-button,导致组件保持隐藏状态。视觉上表现为组件“消失”或“变白”,但实际上 DOM 仍然存在,只是不可见。

解决此问题的核心思路是:动态类名更新时保留内置类名,可从两种方向解决:

  • 框架层适配:在 React 中通过高阶组件 + ref 合并类名;在 Vue 中通过自定义包装组件或渲染函数,确保每次更新时保留 hydrated 与原始类。
  • 组件层适配:提供专用属性(如 cssClass),由组件内部合并到宿主元素上,从源头避免覆盖。

本质上,这是框架与 Web Components 在类名、样式、事件等宿主绑定策略上的差异问题。提前在封装层处理这些差异,可显著提升跨框架稳定性。

5.2. 事件捕获与跨框架一致性问题

在 Stencil 中,我们通常通过 @Listen('click') 来监听点击事件,例如:

@Listen('click') onClick(e: Event) { if (this.disabled || this.loading) { e.stopPropagation(); } } // Vue <blm-button disabled @click="handleClick">默认按钮</blm-button> // React <BlmButton disabled onClick={handleClick}>默认按钮</BlmButton>

在实际使用中,我们发现行为在不同框架下存在差异:

在 Vue 中,即便按钮设置了 disabled,点击事件仍然会触发绑定的 handleClick;而在 React 中,则不会触发。

这种差异的原因在于 React 使用了合成事件系统,默认在冒泡阶段会禁用disabled 元素的事件,而 Vue 监听的是原生事件,冒泡阶段仍会触发 click

为了解决这个问题,可以将事件监听放在捕获阶段:

@Listen('click', { capture: true })

这样事件会先被拦截,从而保证在 Vue 和 React 中行为一致。这也反映出了不同框架在事件捕获和阻止机制上的差异,需要在组件封装层进行跨框架的适配。

5.3. 低版本浏览器兼容与 Polyfill 策略

在我们的业务场景中,需要兼容到部分仍在使用 Android 5 和 iOS 10的用户,必须支持较老版本(如Chrome50)的 WebView。然而 Stencil 从 v3.0.0 开始,已不再支持 IE 11、Edge ≤ 18 和 Safari 10。虽然 v3.0.0 仍可通过一些配置和 polyfill 继续支持这些低版本,但Stencil 最新版本(4.X)中这些功能已经被移除。因此,在实践中,我们最多只能使用 Stencil v3 进行开发。

我们可以使用 Stencil 2 和 3,配置 Stencil 构建支持 ES5 并开启必要的 extras,并引入 Web Components polyfill,保证自定义元素、Shadow DOM、CSS 变量、事件等特性在 Android 5 等低版本浏览器中能正常工作,从而避免组件渲染失败或报错。

{ buildEs5: true, // 为低版本浏览器兼容引入的配置 extras: { cssVarsShim: true, // 支持 CSS 变量 dynamicImportShim: true, // 支持动态 import shadowDomShim: true, // 支持 Shadow DOM safari10: true, // 修复 Safari 10 特性问题 scriptDataOpts: true, // 兼容低版本浏览器 script.dataset appendChildSlotFix: false, // 可选修复 slot appendChild 问题 cloneNodeFix: false, // 可选修复 cloneNode 问题 slotChildNodesFix: true, // 修复 slot.childNodes 在低版本浏览器获取异常 }, } 

5.4. 其他常见集成问题与注意事项

在做框架集成过程中还存在以下典型问题需要关注:

  1. 事件监听问题:React 无法直接监听到 Web Components 发出的自定义事件,需通过 addEventListener手动绑定。
  2. Ref 引用问题:不同框架对 Web Components 的 ref 获取方式不同(如 React 需用 forwardRef,Vue 需用ref+ $el)。
  3. 复杂 Props 处理:传递对象或数组时,框架的响应式系统可能导致数据格式不一致,需手动序列化/反序列化。

实践证明,跨框架集成并不存在“一步到位”的方案,它更像是一场持续的打磨——需要依靠封装与适配把分散的坑一点点补平,才能实现一致、可维护的使用体验。

6. 未来展望

通过本次探索,我们验证了 Web Components 在多技术栈、多终端环境下的可行性和稳定性。借助封装与适配层,我们解决了框架差异、事件一致性以及样式隔离等关键问题,实现了可维护、可复用的组件体系。

未来,随着业务场景和组件数量的扩展,这套体系仍有优化空间:持续完善封装与适配策略,可以进一步降低跨框架使用门槛,使组件在不同技术栈间的行为更加一致。同时,团队可以积累更多最佳探索和使用经验,为多端开发和跨团队协作提供更稳固的技术支撑。

Read more

微信小程序webview postmessage通信指南

微信小程序webview postmessage通信指南

需求概述 在微信小程序中使用 web-view 组件与内嵌网页进行双向通信,主要通过 postMessage 实现。以下是完整的配置和使用方法: 通信指南 微信小程序webview官方文档 1. 基础配置 小程序端配置 // app.json 或 page.json { "usingComponents": {}, "permission": { "scope.webView": { "desc": "用于网页和小程序通信" } } } 网页端配置 <!-- 内嵌网页需引入微信JS-SDK --> <script src="https://res.wx.qq.com/open/

Flutter 与 Web 混合开发:跨平台的完美融合

Flutter 与 Web 混合开发:跨平台的完美融合

Flutter 与 Web 混合开发:跨平台的完美融合 写在前面 今天想和你聊聊一个让跨平台开发更具可能性的话题——Flutter 与 Web 混合开发。在我眼里,Flutter 就像一位多才多艺的艺术家,既能在移动平台上展现精彩,也能在 Web 世界中绽放光芒。 Flutter Web 的崛起 Flutter Web 是 Flutter 的一个重要方向,它允许我们使用同一套代码库构建运行在浏览器中的应用。随着 Flutter 3.0 的发布,Flutter Web 的性能和稳定性得到了显著提升,为混合开发开辟了新的可能。 Flutter Web 的优势 1. 代码复用:使用同一套代码库构建移动应用和 Web 应用,减少开发和维护成本 2. 一致的用户体验:在不同平台上提供一致的视觉和交互体验 3. 高性能:

mT5分类增强版中文-base保姆级教程:WebUI响应超时设置与GPU OOM预防措施

mT5分类增强版中文-base保姆级教程:WebUI响应超时设置与GPU OOM预防措施 1. 这不是普通文本增强,而是全任务零样本学习的中文利器 你有没有遇到过这样的问题:手头只有一小段中文文本,却要快速生成语义一致、表达多样的多个版本?传统方法要么靠人工反复改写,耗时费力;要么用通用大模型,结果跑偏、重复、不专业。而今天要介绍的这个模型,彻底改变了这种局面。 它叫mT5分类增强版中文-base——名字有点长,但记住三个关键词就够了:零样本、中文专精、稳定输出。它不是简单地在英文mT5基础上加点中文数据微调,而是在大量高质量中文语料上做了深度再训练,并特别引入了零样本分类增强技术。这意味着:你不需要准备任何标注数据,也不用写复杂的提示词,只要输入一句话,它就能理解你的意图,自动生成几个风格不同、逻辑通顺、符合中文表达习惯的增强版本。 更关键的是,它的输出稳定性远超同类模型。我们实测过上千条日常短句(比如“用户投诉物流太慢”“产品页面加载卡顿”“客服回复不及时”),92%以上的生成结果语义准确、无事实错误、无生硬翻译感。这不是“能用”,而是“敢用”

基于YOLOv8/YOLOv10/YOLOv11/YOLOv12与SpringBoot的森林火灾烟雾检测系统(DeepSeek智能分析+web交互界面+前后端分离+YOLO数据

基于YOLOv8/YOLOv10/YOLOv11/YOLOv12与SpringBoot的森林火灾烟雾检测系统(DeepSeek智能分析+web交互界面+前后端分离+YOLO数据

摘要 森林火灾是全球面临的重大生态安全挑战,及早发现并预警火情对保护生态环境和人民生命财产安全至关重要。本研究设计并实现了一套集先进深度学习技术与现代化Web架构于一体的森林火灾烟雾智能检测系统。该系统创新性地集成YOLOv8、YOLOv10、YOLOv11和YOLOv12四种最新目标检测模型,专门针对"火焰(fire)"和"烟雾(smoke)"两类关键火情特征进行高精度识别。系统采用SpringBoot框架构建后端服务,结合前后端分离架构,实现了多模态火情检测功能(包括静态图像、动态视频流和实时监控摄像头),并将所有检测记录与用户数据持久化存储于MySQL数据库。为增强系统智能化水平,我们创新性地引入DeepSeek大型语言模型,提供火情检测结果的智能分析与风险评估报告。实验结果表明,本系统在包含2000张标注图像的专业火灾烟雾数据集上表现优异,检测准确率达到预期目标。系统还配备了完善的管理功能,包括用户身份认证、检测记录可视化分析、管理员后台管理等模块,为森林防火工作提供了一套完整、高效、智能的技术解决方案。 关键词: 森林火灾检测;烟雾识别;YOLO系列算法;SpringBo