customelements.define 必须传入继承 htmlelement 的类,不能传字符串;shadow dom 初始化必须在 constructor 中执行 attachshadow,属性监听需通过 static observedattributes 声明并在 attributechangedcallback 中手动同步,slot 仅透传 light dom 内容。

customElements.define 只接受类,不接受 HTML 字符串
直接传字符串给 customElements.define 会报错 TypeError: Class constructor cannot be invoked without 'new'。这不是 API 设计疏漏,而是规范强制要求组件必须是可实例化的类——只有类才能响应生命周期、绑定属性、管理 Shadow DOM。
- 错误写法:
customElements.define('my-card', '<div>Hello</div>') - 正确路径:定义一个继承
HTMLElement的类,在constructor()中调用super()并创建shadowRoot,在connectedCallback()中渲染结构 - 模板建议用
<template id="my-card"></template>预存,再通过document.getElementById('my-card').content.cloneNode(true)插入,避免重复解析字符串 - 若需动态注入后端返回的富文本 HTML,别用
shadowRoot.innerHTML = htmlStr——它会丢失事件绑定且不安全;改用DOMParser解析成DocumentFragment,再shadowRoot.appendChild()
Shadow DOM 初始化必须在 constructor 里完成
this.attachShadow({ mode: 'open' }) 必须在 constructor 中执行,否则后续所有样式隔离、slot 分发、querySelector 查找都会失效。常见错误是把它挪到 connectedCallback,此时元素已挂载,但 Shadow Root 尚未建立,导致样式泄漏或内容无法透传。
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
-
constructor是唯一能安全初始化 Shadow DOM 的时机;connectedCallback只负责“首次渲染”和“状态同步” - 别在
constructor里操作this.innerHTML或this.shadowRoot.innerHTML——前者绕过 Shadow DOM,后者此时shadowRoot还没创建(会报Cannot set property 'innerHTML' of null) - 如果组件依赖异步资源(如字体、图标),应在
connectedCallback中加载,并加防重逻辑,因为该钩子可能被多次触发(比如元素被移除又重新 append)
属性变更必须显式声明 observedAttributes
HTML 属性变化不会自动触发更新,必须靠 static get observedAttributes() 告诉浏览器哪些属性需要监听,再在 attributeChangedCallback 中手动同步。忽略这一步,my-card avatar="xxx" 改了也不会更新头像。
- 只监听实际用到的属性,比如
['avatar', 'name', 'disabled'],不要写*或全量枚举,性能无益且易出错 -
attributeChangedCallback中注意区分oldValue和newValue类型:HTML 属性全是字符串,checked是"true"/null,不是布尔值 - 若属性值是 JSON 字符串(如
data='{"theme":"dark"}'),需在回调里JSON.parse(),并加 try/catch 防止解析失败中断流程 - 避免在该回调中直接操作
shadowRoot.innerHTML = ''清空再重写——这会销毁所有事件监听器;改用shadowRoot.replaceChildren(...)或逐个appendChild()
slot 内容透传只对 light DOM 有效
<slot></slot> 只能分发用户写在组件标签内部的子节点(即 light DOM),它对 JavaScript 动态生成的内容或后端返回的 HTML 字符串完全无感。想让 CMS 返回的富文本进 <slot name="body"></slot>,得手动注入。
- 典型场景:
<my-card><div slot="body">这里是用户写的 HTML</div></my-card>→ slot 自动生效;但fetch('/api/content').then(html => ...)得自己塞进shadowRoot.querySelector('[slot="body"]').appendChild(...) - 若要支持 HTML 字符串注入,推荐用
DOMParser:const frag = new DOMParser().parseFromString(htmlStr, 'text/html').body.children[0],再shadowRoot.querySelector('[slot="body"]').appendChild(frag) - 注意:slot 元素本身不参与渲染,它只是“出口”,真正渲染的是它包裹的内容;所以不要给
<slot></slot>加样式,而应作用于其父容器或使用::slotted(*) - 如果组件内部有多个
<slot></slot>,记得每个都设name,否则默认 slot 会吞掉所有未命名内容,造成意外交互
:host 或 ::slotted 显式控制。封装成本比 Lit 或 Vue 高,但换来的是零构建依赖、纯浏览器运行、以及真正的 DOM 语义一致性。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










