这几条基本把"第二个组件"的坑踩全了,我只补一个到"第三个组件"才会撞上的:constructor 的能做什么、不能做什么,以及 adoptedStyleSheets。
constructor 里能建 shadow root、能 attachInternals(),但不能碰子节点。 这也是对你"shadow root 一律放 constructor"的印证和补充:自定义元素在 HTML 解析过程中被升级时,constructor 执行的那一刻 light DOM 子节点可能还没解析完,所以规范明确不建议在 constructor 里读 childNodes 或 appendChild 到自身——要操作插槽内容就放 connectedCallback。另外 formAssociated 的元素 attachInternals() 也只能在 constructor 里调,这个和 shadow root 一样属于"建一次就好"的东西,正好和你的幂等原则合上。
反射属性要注意短路。 property 的 setter 里 setAttribute → 触发 attributeChangedCallback → 若回调里又写 property 就是死循环。回调里先判 oldV === newV 直接 return。布尔属性更阴:disabled="false" 依然为真,别用字符串判断。
样式别每个实例塞 <style>。 实例多了就是重复的 CSSOM,用共享构造式样式表:
const sheet = new CSSStyleSheet();
sheet.replaceSync(`.badge{background:var(--badge-bg,#eef);padding:2px 8px;border-radius:10px}`);
// constructor 里
this.shadowRoot.adoptedStyleSheets = [sheet];
配合 :host { display:inline-block } 和 :host([disabled]) 就能把外部接口收在宿主元素上。判断 'adoptedStyleSheets' in Document.prototype 做降级即可。
最后一个小坑:严格 CSP(不带 style-src 'unsafe-inline')下内联 <style> 会被拦,adoptedStyleSheets 反而更安全;同理 shadow 内事件想跨边界必须 composed: true,否则外层 addEventListener 收不到。

正式会员
认证极客









