自定义元素命名必须含短横线且全小写,浏览器注册时直接校验;my-button合法,mybutton、MyButton等非法变体调用customElements.define()即抛DOMException错误,组件无法初始化。

自定义元素命名必须含短横线且全小写
浏览器注册时直接校验名称,my-button合法,mybutton、MyButton、user-card(注意:这个其实是合法的)以外的变体都会抛出 DOMException: The name "xxx" is not a valid custom element name。这不是运行时警告,是 customElements.define() 调用瞬间就失败——组件根本没机会初始化。
常见错误包括:用下划线(my_button)、首字母大写(UserCard)、单个单词(button),或与原生标签重名(div、slot、template)。命名不是风格问题,是强制语法约束;语义清晰优先,但需兼顾可读性,避免过度缩写如 usr-crd。
constructor 只能做基础初始化,DOM 操作必须在 connectedCallback
this.shadowRoot 在 constructor 中一定为 null 或未就绪,此时调用 this.attachShadow({ mode: 'open' }) 合法,但紧跟着写 this.shadowRoot.innerHTML = '...' 或查询子节点,大概率静默失败,尤其在 SSR 或框架嵌套场景下更不稳定。
正确做法是:
立即学习“前端免费学习笔记(深入)”;
-
constructor仅调用super()、创建 shadowRoot、声明私有字段(如this.internals = this.attachInternals())、绑定方法 - 所有 DOM 渲染、属性读取(
this.getAttribute('value'))、事件监听、innerHTML设置,都移入connectedCallback -
attributeChangedCallback不会触发初始属性值,首次渲染仍需在connectedCallback手动读取 - 若组件可能多次挂载/卸载(如路由切换),
disconnectedCallback必须清理定时器和事件监听器,否则内存泄漏
样式必须注入 Shadow DOM,不能依赖外部 CSS
把 <link rel="stylesheet"> 放在自定义元素内部,或试图用 @import 加载外部样式,在 Shadow DOM 中基本无效。Shadow DOM 的样式隔离机制决定了:样式必须显式注入到 shadowRoot 中。
最稳妥的做法是在 connectedCallback 中拼接 <style></style> 字符串并插入:this.shadowRoot.appendChild(document.createElement('style')).textContent = '...';。如果样式复杂,可预编译为字符串常量或用模板函数生成,避免重复解析。现代浏览器支持 adoptedStyleSheets,但兼容性需自行权衡。
让自定义表单元素真正参与 form 提交
很多 <my-input> 看起来像输入框,但点提交时值不到后端——因为没接入表单生命周期。核心坑在于:只封装 UI,没实现 formAssociated 接口。
必须同时满足三点:
- 类定义中显式声明
formAssociated: true(作为customElements.define()的第三个参数) -
constructor中调用this.attachInternals()获取ElementInternals实例 -
connectedCallback中监听子<input>的input或change事件,并调用this.internals.setFormValue(...)同步值
注意:不能继承 HTMLInputElement(extends="input"),Chrome v100+ 已限制,Firefox/Safari 基本不支持;现实路径只有自治型自定义元素(autonomous custom elements)+ attachInternals()。
渐进式 Web 组件架构的复杂点不在“能不能跑”,而在“边界怎么守”:命名规则、生命周期职责、样式注入时机、表单集成契约——这些地方一旦松动,后续维护成本会指数级上升。最容易被忽略的是 attributeChangedCallback 对 JS 属性赋值(如 el.value = 'xxx')完全无感,必须手动补同步逻辑。



















