自定义元素必须手动桥接状态、事件、验证和第三方依赖,否则仅为空壳标签;需在connectedCallback中绑定事件,在observedAttributes和attributeChangedCallback中响应属性变化,通过attachInternals和formAssociated支持表单能力,用light DOM+slot封装第三方库,并在disconnectedCallback中彻底清理资源。

不能靠注册就自动生效,必须手动桥接状态、事件、验证和第三方依赖——否则它只是个空壳标签。
customElements.define() 只是注册,不绑定任何行为
很多人写完 customElements.define('my-form-input', InputClass) 就以为组件能响应点击或输入了。实际它连 onclick 属性都不处理,更别说校验或状态同步。
-
constructor()里只能调super()和this.attachShadow(),不能查 DOM 或读属性(此时this.getAttribute()返回null) - 真实渲染和事件绑定必须放在
connectedCallback():在这里调this.shadowRoot.querySelector('input').addEventListener('input', ...) - 如果组件要响应
disabled或loading这类属性变化,得先在类里声明static get observedAttributes() { return ['disabled', 'loading']; },再写attributeChangedCallback()去更新 UI
表单参与能力不是默认附带的
原生 <input> 能被 form.elements 捕获、能触发 submit、能调 checkValidity(),但自定义元素默认什么都没有。
- 必须显式调
this.attachInternals()并设formAssociated: true(在类定义里加static formAssociated = true) - 要支持
reportValidity()和setCustomValidity(),就得自己实现checkValidity()方法,并在内部维护this.internals_.validity状态 - 异步校验不能只靠
setCustomValidity():它只是快照,必须配合e.preventDefault()+ 手动reportValidity()控制流程,否则提交时根本不会等请求返回
封装第三方库时,Shadow DOM 是主要障碍
Chart.js、Leaflet、CodeMirror 这类库默认依赖外部样式上下文和全局 DOM 查询,放进 shadowRoot 里大概率报错或渲染空白。
立即学习“前端免费学习笔记(深入)”;
- 别在
shadowRoot里直接插<canvas>或<div id="map">;改用 light DOM +<slot>,把容器交由使用者提供 - 第三方实例(如
this._chart)必须在disconnectedCallback()中显式销毁:调.destroy()、清除定时器、取消MutationObserver - 属性变更(如
data-config)不能只重绘 DOM,得映射到库的.update()或.setOptions()方法,否则界面不动
状态管理全靠自己,没有框架帮你 rerender
React/Vue 会自动 diff 和更新视图,但自定义元素不会。你改了 this._isOpen,UI 不会变,除非你主动重建结构。
- 每次状态变更后,必须调一个私有方法(比如
this._render()),用当前this.state和this.attributes重新生成shadowRoot.innerHTML或批量操作节点 - 避免局部 patch:比如只改 class 或 innerText,容易漏掉 aria 属性或子组件状态,导致可访问性失效或逻辑错乱
- 高频事件(如
input)要节流:requestIdleCallback或setTimeout(..., 0)防卡顿,尤其在移动端
最常被跳过的一步是清理:disconnectedCallback 里没清定时器、没销毁第三方实例、没移除事件监听器,组件卸载后内存还在涨,用户切页多了就卡死。这不是“可选优化”,是必做项。



















