当前位置: 首页 > article >正文

Web Components深度解析:构建可复用的原生组件

Web Components深度解析构建可复用的原生组件前言大家好我是cannonmonster01今天我们来深入探讨Web Components这个强大的原生组件技术。想象一下你是一个乐高爱好者你可以用不同的积木块搭建出各种各样的模型。Web Components就像是这些积木块它们是独立的、可复用的组件可以在任何框架中使用。如果你想要创建真正跨框架的组件Web Components绝对值得一试Web Components核心概念什么是Web ComponentsWeb Components是一套浏览器原生支持的组件化技术包括Custom Elements自定义HTML元素Shadow DOM隔离的DOM树HTML Templates可复用的模板HTML Imports导入HTML文件已废弃Custom Elementsclass MyButton extends HTMLElement { constructor() { super(); this.attachShadow({ mode: open }); this.shadowRoot.innerHTML style button { padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; } /style buttonslot/slot/button ; } } customElements.define(my-button, MyButton);Shadow DOMmy-buttonClick me/my-button !-- Shadow DOM内部结构 -- #shadow-root (open) ├── style.../style └── button slotClick me/slot /buttonHTML Templatestemplate iduser-card-template div classuser-card img src altUser avatar classavatar div classuser-info h3 classname/h3 p classemail/p /div /div /templateWeb Components实战实战1创建自定义按钮组件class CustomButton extends HTMLElement { constructor() { super(); this.attachShadow({ mode: open }); const type this.getAttribute(type) || primary; const disabled this.hasAttribute(disabled); this.shadowRoot.innerHTML style button { padding: 12px 24px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.3s ease; } .primary { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; } .secondary { background: #f1f1f1; color: #333; } .danger { background: #ef4444; color: white; } button:disabled { opacity: 0.5; cursor: not-allowed; } button:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } /style button class${type} ?disabled${disabled} slot/slot /button ; } static get observedAttributes() { return [type, disabled]; } attributeChangedCallback(name, oldValue, newValue) { const button this.shadowRoot.querySelector(button); if (name type) { button.className newValue; } if (name disabled) { button.disabled this.hasAttribute(disabled); } } } customElements.define(custom-button, CustomButton);custom-button typeprimaryPrimary Button/custom-button custom-button typesecondarySecondary Button/custom-button custom-button typedanger disabledDanger Button/custom-button实战2创建用户卡片组件class UserCard extends HTMLElement { constructor() { super(); this.attachShadow({ mode: open }); this.render(); } render() { const name this.getAttribute(name) || Anonymous; const email this.getAttribute(email) || ; const avatar this.getAttribute(avatar) || https://via.placeholder.com/100; this.shadowRoot.innerHTML style .card { display: flex; align-items: center; padding: 16px; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); max-width: 300px; } .avatar { width: 60px; height: 60px; border-radius: 50%; object-fit: cover; margin-right: 16px; } .user-info { flex: 1; } .name { margin: 0 0 4px 0; font-size: 16px; font-weight: 600; color: #333; } .email { margin: 0; font-size: 14px; color: #666; } /style div classcard img classavatar src${avatar} alt${name} div classuser-info h3 classname${name}/h3 p classemail${email}/p /div /div ; } static get observedAttributes() { return [name, email, avatar]; } attributeChangedCallback() { this.render(); } } customElements.define(user-card, UserCard);user-card nameJohn Doe emailjohnexample.com avatarhttps://example.com/avatar.jpg /user-card实战3创建可交互的计数器组件class CounterWidget extends HTMLElement { constructor() { super(); this.attachShadow({ mode: open }); this.count parseInt(this.getAttribute(initial) || 0); this.render(); } render() { this.shadowRoot.innerHTML style .counter { display: flex; align-items: center; gap: 16px; padding: 16px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; color: white; } button { width: 40px; height: 40px; border: 2px solid rgba(255, 255, 255, 0.3); background: transparent; color: white; border-radius: 8px; font-size: 20px; cursor: pointer; transition: all 0.2s ease; } button:hover { background: rgba(255, 255, 255, 0.2); } .count { font-size: 24px; font-weight: 700; min-width: 40px; text-align: center; } /style div classcounter button iddecrement-/button span classcount${this.count}/span button idincrement/button /div ; this.shadowRoot.getElementById(increment).addEventListener(click, () { this.count; this.render(); this.dispatchEvent(new CustomEvent(count-change, { detail: this.count })); }); this.shadowRoot.getElementById(decrement).addEventListener(click, () { this.count--; this.render(); this.dispatchEvent(new CustomEvent(count-change, { detail: this.count })); }); } get value() { return this.count; } set value(newValue) { this.count newValue; this.render(); } } customElements.define(counter-widget, CounterWidget);counter-widget initial5/counter-widget script const counter document.querySelector(counter-widget); counter.addEventListener(count-change, (e) { console.log(Count changed to:, e.detail); }); /script实战4创建表单验证组件class ValidatedInput extends HTMLElement { constructor() { super(); this.attachShadow({ mode: open }); this.render(); } render() { const label this.getAttribute(label) || ; const type this.getAttribute(type) || text; const required this.hasAttribute(required); const pattern this.getAttribute(pattern) || ; this.shadowRoot.innerHTML style .form-group { display: flex; flex-direction: column; gap: 4px; } label { font-size: 14px; font-weight: 500; color: #333; } input { padding: 10px 12px; border: 2px solid #e1e1e1; border-radius: 6px; font-size: 14px; transition: border-color 0.2s ease; } input:focus { outline: none; border-color: #667eea; } input.error { border-color: #ef4444; } .error-message { font-size: 12px; color: #ef4444; margin: 0; } /style div classform-group label${label}${required ? * : }/label input type${type} ${required ? required : } ${pattern ? pattern${pattern} : } p classerror-message/p /div ; const input this.shadowRoot.querySelector(input); const errorMessage this.shadowRoot.querySelector(.error-message); input.addEventListener(blur, () { this.validate(input, errorMessage); }); input.addEventListener(input, () { if (input.classList.contains(error)) { input.classList.remove(error); errorMessage.textContent ; } }); } validate(input, errorMessage) { if (input.required !input.value) { input.classList.add(error); errorMessage.textContent This field is required; return false; } if (input.pattern !input.checkValidity()) { input.classList.add(error); errorMessage.textContent this.getAttribute(error-message) || Invalid format; return false; } return true; } checkValidity() { const input this.shadowRoot.querySelector(input); const errorMessage this.shadowRoot.querySelector(.error-message); return this.validate(input, errorMessage); } get value() { return this.shadowRoot.querySelector(input).value; } set value(newValue) { this.shadowRoot.querySelector(input).value newValue; } } customElements.define(validated-input, ValidatedInput);validated-input labelEmail typeemail required error-messagePlease enter a valid email address /validated-input validated-input labelPhone typetel pattern[0-9]{10,12} error-messagePlease enter a valid phone number /validated-inputWeb Components最佳实践1. 使用slot进行内容分发class CardComponent extends HTMLElement { constructor() { super(); this.attachShadow({ mode: open }); this.shadowRoot.innerHTML style .card { border: 1px solid #e1e1e1; border-radius: 8px; padding: 16px; } .card-header { font-size: 18px; font-weight: 600; margin-bottom: 8px; } .card-body { font-size: 14px; color: #666; } /style div classcard div classcard-header slot nameheaderDefault Header/slot /div div classcard-body slot namebodyDefault Body/slot /div /div ; } } customElements.define(card-component, CardComponent);card-component span slotheaderMy Custom Header/span p slotbodyThis is the card body content./p /card-component2. 使用CSS变量进行主题化class ThemedButton extends HTMLElement { constructor() { super(); this.attachShadow({ mode: open }); this.shadowRoot.innerHTML style button { --primary-color: #667eea; --hover-color: #5a6fd6; padding: 10px 20px; background: var(--primary-color); color: white; border: none; border-radius: 4px; cursor: pointer; } button:hover { background: var(--hover-color); } /style buttonslot/slot/button ; } } customElements.define(themed-button, ThemedButton);style themed-button { --primary-color: #4CAF50; --hover-color: #45a049; } /style themed-buttonGreen Button/themed-button3. 生命周期管理class LifecycleDemo extends HTMLElement { constructor() { super(); console.log(1. constructor); } connectedCallback() { console.log(2. connectedCallback - Component added to DOM); this.setupEventListeners(); } disconnectedCallback() { console.log(3. disconnectedCallback - Component removed from DOM); this.cleanupEventListeners(); } attributeChangedCallback(name, oldValue, newValue) { console.log(4. attributeChangedCallback - ${name}: ${oldValue} - ${newValue}); } adoptedCallback() { console.log(5. adoptedCallback - Component moved to new document); } setupEventListeners() { // 设置事件监听器 } cleanupEventListeners() { // 清理事件监听器 } } customElements.define(lifecycle-demo, LifecycleDemo);Web Components与框架对比特性Web ComponentsReactVue浏览器原生是否否跨框架是否否学习曲线平缓中等中等生态系统较小庞大庞大性能优秀优秀优秀适用场景跨框架组件React项目Vue项目常见问题解答Q1Web Components支持哪些浏览器A1现代浏览器都支持Web Components包括Chrome、Firefox、Safari 10.1、Edge。Q2Web Components可以和React/Vue一起使用吗A2可以Web Components是原生技术可以在任何框架中使用。Q3Web Components的样式是隔离的吗A3是的Shadow DOM提供了样式隔离组件内部的样式不会影响外部。Q4如何在Web Components中使用CSS框架A4可以在Shadow DOM中引入CSS框架或者使用CSS变量进行主题化。总结Web Components是一套强大的原生组件化技术它让我们可以创建真正跨框架的可复用组件。通过Custom Elements、Shadow DOM和HTML Templates的组合我们可以构建出高质量的UI组件。关注我每天分享更多前端干货如果觉得这篇文章对你有帮助请点赞、收藏、转发三连支持一下

相关文章:

Web Components深度解析:构建可复用的原生组件

Web Components深度解析:构建可复用的原生组件 前言 大家好,我是cannonmonster01!今天我们来深入探讨Web Components这个强大的原生组件技术。 想象一下,你是一个乐高爱好者,你可以用不同的积木块搭建出各种各样的模型…...

从开源项目8co/opentangl看去中心化协作框架的技术架构与实践

1. 项目概述:从“8co/opentangl”看开源协作的深度实践 最近在GitHub上看到一个挺有意思的项目,叫“8co/opentangl”。乍一看这个标题,可能会让人有点摸不着头脑,既不像传统的技术栈命名,也不像某个具体的应用。但恰恰…...

文档播客化最后窗口期!NotebookLM v2.3新增音频锚点功能,不升级将永久丢失时间戳同步能力

更多请点击: https://intelliparadigm.com 第一章:文档播客化的时代必然性与NotebookLM v2.3战略定位 当知识消费从线性阅读转向多模态沉浸,文档不再静默——它开始“说话”。NotebookLM v2.3 的发布并非功能迭代,而是一次范式迁…...

构建自动化漏洞扫描框架:从工具整合到实战工作流

1. 项目概述与核心价值最近在整理自己的渗透测试工作流,发现一个挺普遍的问题:很多安全从业者,尤其是刚入行的朋友,手里攒了一大堆扫描器、漏扫工具,但用起来总是东一榔头西一棒子,缺乏一个统一的、可复现的…...

独立开发者如何借助Taotoken模型广场与按Token计费控制项目成本

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 独立开发者如何借助Taotoken模型广场与按Token计费控制项目成本 对于独立开发者而言,在原型验证或小产品开发阶段&…...

全栈开发实战:基于Turborepo、Next.js与Expo的现代化启动套件深度解析

1. 项目概述:一个面向全栈开发者的现代化启动套件 如果你正在寻找一个能同时启动Web和移动应用开发,并且内置了AI能力、认证和数据库的“全家桶”式解决方案,那么你很可能已经听说过或正在寻找类似shipkit这样的项目。作为一个在Web和移动端开…...

Midjourney 35mm风格私藏参数包泄露(含5套已验证Film Stock预设+EXIF元数据注入技巧,仅限本周开放下载)

更多请点击: https://intelliparadigm.com 第一章:Midjourney 35mm风格的影像美学本质与历史溯源 35mm胶片摄影自20世纪初诞生以来,便以独特的颗粒质感、柔和焦外、微妙色偏与动态范围局限性,塑造了一种被广泛认可的“真实感”视…...

对比使用Taotoken前后在复杂任务中的API调用成功率变化

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 对比使用 Taotoken 前后在复杂任务中的 API 调用成功率变化 在开发实践中,我们常常需要依赖大模型 API 来完成复杂的生…...

API密钥失效?图片生成卡顿?Webhook丢失?Midjourney接入常见故障诊断手册,98%问题5分钟定位

更多请点击: https://intelliparadigm.com 第一章:Midjourney API接入的底层机制与通信模型 Midjourney 并未官方开放 RESTful API,其主流接入方式依赖于 Discord Bot 模拟用户行为与消息事件监听,本质是基于 WebSocket 协议的双…...

接入Taotoken后API调用的成功率与容灾体验提升显著

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 接入Taotoken后API调用的成功率与容灾体验提升显著 对于依赖大模型API构建服务的开发者而言,服务的稳定性与可用性是核…...

做AI测试,我是怎么从不会到找到方法的

刚开始做AI测试,最大的问题是:不知道从哪里下手。 功能测试还好,有需求文档,有业务逻辑,知道测什么。 但AI产品不一样。模型的输出是概率性的,边界在哪里不清楚,也没有人告诉你哪里容易出问题…...

ARM GICD_CLRSPI_NSR寄存器详解与中断管理实践

1. ARM中断控制器中的GICD_CLRSPI_NSR寄存器解析 在ARM架构的嵌入式系统中,通用中断控制器(GIC)是整个系统的中断管理核心。作为GIC Distributor模块的关键组件,GICD_CLRSPI_NSR寄存器专门用于管理非安全SPI(Shared Peripheral Interrupt)的中断状态。理…...

Kubernetes_Pod调度策略与资源管理深度实战

Kubernetes Pod调度策略与资源管理深度实战:从原理到生产级配置指南 🎯 适用人群:有Docker基础的开发者、DevOps工程师、后端架构师 📖 阅读时间:约25分钟 | 代码实战:50+配置示例 💡 核心价值:掌握K8s Pod调度的核心机制,避免生产环境中的资源管理陷阱 前言:为什…...

eBPF可观测性实战

eBPF 可观测性实战:Linux 内核级追踪与性能分析完全指南 🔬 eBPF 是 Linux 内核中最强大的可观测性技术——它能让你在不修改内核代码、不重启系统的情况下,安全地在内核中运行自定义程序。本文从零开始,带你掌握 eBPF 追踪与性能分析。 📌 前言 当你的服务出现以下问题…...

从智能手表到无障碍服务:深入理解Android NotificationListenerService的5种应用场景

从智能手表到无障碍服务:深入理解Android NotificationListenerService的5种应用场景 在移动设备功能日益丰富的今天,系统通知已经成为连接用户与应用的重要桥梁。而作为Android开发者,NotificationListenerService这一系统级API为我们打开了…...

Rust所有权与生命周期深度解析

Rust 所有权与生命周期深度解析:从编译器视角理解内存安全 🦀 Rust 的所有权系统是其最核心的创新——它在编译期消除了一整类内存安全 bug,零运行时开销。本文从编译器内部视角出发,带你彻底搞懂所有权、借用、生命周期的工作原理。 📌 前言 如果你是从 C/C++ 转来的开…...

我很笨--学习PG Vector--我开始研究HNSW的减少内存占用--要不你也试试!!系列 5)

❝开头还是介绍一下群,如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis, OceanBase, Sql Server等有问题,有需求都可以加群群内有各大数据库行业大咖,可以解决你的问题。加群请联系 liuaustin3 ,(共3400人左右 …...

ARM PMU性能监控单元与PMCEID2寄存器详解

1. ARM PMU性能监控单元基础解析性能监控单元(Performance Monitoring Unit, PMU)是现代处理器架构中用于硬件级性能分析的核心组件。在ARM架构中,PMUv3作为第三代性能监控架构,提供了全面的性能事件监控能力。其工作原理是通过配置专用寄存器来选择监控…...

clwatch:AI编码工具版本监控与变更管理解决方案

1. 项目概述:一个为AI编码工具而生的“版本雷达”如果你和我一样,日常工作中重度依赖Claude Code、Codex CLI这类AI编码工具,那你一定遇到过这个痛点:你永远不知道你用的工具什么时候又悄悄更新了。新版本可能带来了梦寐以求的功能…...

免费二维CAD绘图软件LitCAD完整指南:15分钟快速上手专业设计

免费二维CAD绘图软件LitCAD完整指南:15分钟快速上手专业设计 【免费下载链接】LitCAD A very simple CAD developed by C#. 项目地址: https://gitcode.com/gh_mirrors/li/LitCAD 还在为昂贵的CAD软件费用发愁吗?还在为复杂的设计工具学习曲线感到…...

雷达仿真避坑指南:地杂波与海杂波在MATLAB中建模的5个常见误区

雷达仿真避坑指南:地杂波与海杂波在MATLAB中建模的5个常见误区 雷达系统仿真中,地杂波和海杂波的建模直接影响目标检测性能的评估准确性。许多开发者在MATLAB中实现杂波仿真时,常因几个关键环节的疏忽导致结果偏离预期。本文将结合工程实践中…...

RK3568开发板AMP双系统烧写实战:从原理到调试全解析

1. 项目概述:从单核到异构,解锁开发板的并行处理潜能最近在折腾一块瑞芯微RK3568的开发板,具体型号是迅为的iTOP-3568。这块板子性能不错,四核A55的架构,在嵌入式领域算是中坚力量了。但玩着玩着,我发现了一…...

AI文本人性化工具:开源本地化改写方案与同义词替换原理

1. 项目概述与核心价值 最近在折腾一些文本内容,发现一个挺有意思的现象:无论是学生写论文、运营写文案,还是程序员写文档,大家或多或少都会用到AI工具来辅助生成初稿。这效率是上去了,但随之而来的问题也很明显——生…...

开源AI电话系统IMAI.WORK-AI-Phone:从架构到部署的实战指南

1. 项目概述与核心价值最近在AI应用开发圈子里,一个名为“IMAI.WORK-AI-Phone”的项目引起了我的注意。这个项目由imaiwork团队开源,从名字就能看出它的野心——打造一个“AI电话”。听起来是不是有点科幻?但别误会,这可不是要造一…...

地质雷达仿真终极指南:如何使用gprMax进行地下电磁波传播模拟

地质雷达仿真终极指南:如何使用gprMax进行地下电磁波传播模拟 【免费下载链接】gprMax gprMax is open source software that simulates electromagnetic wave propagation using the Finite-Difference Time-Domain (FDTD) method for numerical modelling of Grou…...

Slack集成Cursor Agent:对话驱动开发的自动化工作流实践

1. 项目概述:在Slack里用对话驱动你的代码项目 如果你和我一样,日常开发工作流重度依赖Slack进行团队沟通,同时又对Cursor的Agent功能爱不释手,那你可能也经历过这种“割裂感”:一边在Slack里讨论需求、报Bug&#xf…...

地质雷达仿真利器:gprMax让电磁波传播可视化变得如此简单

地质雷达仿真利器:gprMax让电磁波传播可视化变得如此简单 【免费下载链接】gprMax gprMax is open source software that simulates electromagnetic wave propagation using the Finite-Difference Time-Domain (FDTD) method for numerical modelling of Ground P…...

制造企业实现产品服务化的路径

在 AI 与工业物联网(AIoT)时代,制造企业实现“产品服务化”(Servitization,又称服务型制造),本质是把企业和客户的利益绑定在同一侧:客户不再为“硬件所有权”买单,而是为…...

OpenClaw力工峰:为华人中小经营者打造低成本AI数字员工

1. 项目概述:一个为华人中小经营者量身定制的AI“全能工”如果你是一个小公司老板、个人工作室主理人,或者干脆就是个光杆司令,那你一定对下面这些事深恶痛绝:每天被行政报销、招聘沟通、内容排版、会议纪要这些重复性杂事缠身&am…...

【花雕学编程】Arduino BLDC 之基于MimiClaw+ ESP32S3的智能跟随机器人

导语 该拓展思路以 ESP32‑S3MimiClawArduino BLDC 为核心,融合嵌入式 AI 无刷动力 多模态感知,具备全向机动、地形适应、智能跟随、低功耗长续航等优势,覆盖消费随行、工业协同、特种科考、教育科研等场景。 一、系统概述 Arduino BLDC …...