constructor是唯一能调用attachshadow()的地方,因为浏览器只允许在元素实例化时的constructor中执行super()和attachshadow(),延迟到connectedcallback会因light dom已含子节点而报错。

能直接用原生 Web Components 实现标准化,不需要框架也能保证复用性、样式隔离和可维护性;但必须严格遵循 customElements.define() 注册规范、observedAttributes 声明机制和 Shadow DOM 初始化时机,否则组件无法响应属性或渲染失败。
为什么 constructor 是唯一能调用 attachShadow() 的地方
浏览器在元素实例化阶段只允许在 constructor 中调用 super() 和 this.attachShadow()。如果拖到 connectedCallback 里再 attach,会触发 Failed to execute 'attachShadow' on 'Element': Shadow root cannot be created on a host which already has children 错误——因为此时 HTML 解析器已把子节点(比如文本、<slot></slot>)插入了 light DOM。
实操建议:
-
constructor中只做最小初始化:调用super()、attachShadow({ mode: 'open' })、设置默认属性或监听器(不操作外部 DOM) - 需要访问子节点或依赖 DOM 就绪的操作(如读取
textContent、计算尺寸),移到connectedCallback中执行 - 避免在
constructor中直接写this.innerHTML = '...',这会污染 light DOM,破坏封装性
observedAttributes 不声明就收不到属性变更通知
HTML 属性变化不会自动同步到 JS 属性,也不会触发重绘。即使你写了 this.size = newValue,若没在类中定义 static get observedAttributes() 返回 ['size'],attributeChangedCallback 根本不会被调用。
常见错误现象:
- 修改
<my-button size="large"></my-button>后样式没变,控制台也无报错 - 动态执行
el.setAttribute('size', 'small'),但组件内部无响应
实操建议:
- 所有需响应的 HTML 属性(如
disabled、label、theme)必须显式列在observedAttributes数组中 -
attributeChangedCallback内部优先校验oldValue !== newValue,避免无效更新 - 属性值是字符串类型,数值型需手动转换:
parseInt(newValue)或parseFloat(newValue)
发布前必须检查自定义标签名是否含连字符
customElements.define() 要求第二个参数是合法的自定义标签名,规则硬性:必须包含且仅含一个连字符(-),不能以数字开头,不能是保留字(如 html、script)。写成 mybutton 或 MyButton 都会抛出 DOMException: Failed to execute 'define' on 'CustomElementRegistry': "mybutton" is not a valid custom element name。
实操建议:
- 命名统一用 kebab-case:如
twe-tab-switcher、kd-avatar-input,和 TW-Elements、Kingdee 等现有生态对齐 - 避免使用
data-前缀(那是 HTML5 自定义属性,不是自定义元素) - 注册前加守卫判断:
if (!customElements.get('twe-tab-switcher')) { customElements.define('twe-tab-switcher', TabSwitcher); },防止重复定义报错
真正容易被忽略的是生命周期与 DOM 就绪状态的错位:很多开发者以为 connectedCallback 就等于“组件完全可用”,其实它不保证子组件已升级、<slot></slot> 内容已分发完毕。需要操作 slot 内容时,得用 slot.assignedNodes() 并监听 slotchange 事件,而不是直接查 this.children。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











