因为div没有语义、行为和生命周期,仅是无功能的容器;重复使用导致样式难维护、交互需手动操作DOM、跨项目复用性差,真正需解决的是让封装代码被浏览器原生识别、开发者直觉理解及工具链自然支持。

为什么不能直接用 div 做组件?
因为 div 没有语义、没有行为、没有生命周期,它只是个容器。当你反复写 <div class="card">
<h3>标题</h3>
<p>内容</p>
</div>,改样式要全局搜,加交互要手动查 DOM,换项目还得重写一遍。
真正的问题不是“怎么封装”,而是“怎么让封装后的代码能被浏览器原生识别、被开发者直觉理解、被工具链自然支持”。所以演进方向很明确:从借用现有标签(比如用 button + is),到定义专属标签(比如 <user-card></user-card>)。
is 属性不是语法糖,是语义桥接的关键
你写 <button is="loading-button">提交</button>,浏览器仍把它当作 HTMLButtonElement 实例——这意味着它天然支持表单提交、disabled 属性、键盘回车触发、屏幕阅读器识别为按钮。如果你直接写 <loading-button></loading-button>,哪怕逻辑一模一样,它默认就是 HTMLElement,不参与表单、不响应空格键、无障碍属性得自己补。
使用 is 的前提是注册时指定 { extends: 'button' }:
立即学习“前端免费学习笔记(深入)”;
class LoadingButton extends HTMLButtonElement {
constructor() {
super();
}
connectedCallback() {
this.addEventListener('click', () => {
this.disabled = true;
this.textContent = '加载中…';
});
}
}
customElements.define('loading-button', LoadingButton, { extends: 'button' });
注意点:
生成可在 deepbi 沙箱 iframe 内运行或作为独立文件运行的交互式 HTML 页面,使用 `window.deepbi` JS SDK 实现数据查询、空间管理、文件浏览及用户操作。当用户提出以下需求时启用该能力: - 创建 KPI 仪表盘、数据表格、文件浏览器、成员列表、管理页面; - 明确要求“转成 dhtml”、“转译成 HTML”、“把 XX 页面转成……”、“控制台页面”、“管理后台”、“DeepBI 页面迁移”; - 提及位于 `apps/console/src/featur
-
constructor必须调用super(),否则原生行为会丢失 - 不能在
constructor里操作 DOM(此时元素尚未插入文档),交互逻辑应放在connectedCallback - IE 完全不支持
is,现代项目若需兼容旧环境,只能退回到 autonomous 元素 + 手动模拟语义
自定义标签名必须含连字符,这不是风格问题
浏览器靠这个区分原生标签和自定义标签:my-button 合法,mybutton 或 MyButton 会直接报错 Failed to execute 'define' on 'CustomElementRegistry': The name must contain a dash。
命名不是随便起的,它直接影响可读性和维护性:
- 前缀建议统一(如
ui-、app-),避免团队内不同人注册button-x和x-button冲突 - 不要用纯数字开头(
3d-model不合法),也不要包含大写字母(MyButton不合法) - 名字要反映职责,而不是实现方式;
collapsible-panel比shadow-wrapper更易懂
Shadow DOM 不是必选项,但它是隔离性的分水岭
你可以只定义一个 class MyCard extends HTMLElement,在 connectedCallback 里拼字符串塞进 this.innerHTML,它也能工作。但它和页面其他 CSS 是裸连的——别人写了个 h2 { color: red; },你的卡片标题就变红了。
加一层 Shadow DOM 就彻底断开:
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>h2 { color: var(--card-title-color, #333); }</style>
<h2><slot name="title"></slot></h2>
<div><slot></slot></div>
`;
}
关键事实:
-
mode: 'open'允许外部 JS 通过element.shadowRoot访问,'closed'则完全隐藏(调试困难,慎用) -
<slot></slot>是内容分发机制,不是占位符;没配name的内容会落到第一个无名<slot></slot>,不是“默认值” - Shadow DOM 内部无法用外部定义的 CSS 变量,除非你在 :host 上显式继承,或用
inherit覆盖
最常被忽略的一点:Shadow DOM 不解决 JS 作用域隔离。你在 shadow 里绑的事件监听器,依然在全局执行上下文中,this 指向仍是当前组件实例,不是 shadow root。


















