connectedcallback是发起更新的唯一可靠时机,此时shadowroot已存在、属性可读、dom操作安全;attributechangedcallback仅响应observedattributes声明的attribute变更;disconnectedcallback不保证执行,关键清理需fallback;customelements.define必须同步注册。

connectedCallback 是发起更新的唯一可靠时机
自定义元素不能靠“属性一变就自动刷新”,所有 DOM 更新、请求发起、事件绑定都得在 connectedCallback 里做。它才是元素真正“活过来”的信号——此时 this.shadowRoot 已存在,this.getAttribute() 能读到初始值,fetch 和 querySelector 都可安全调用。
常见错误是把渲染逻辑塞进 constructor:此时 this.shadowRoot 是 null,this.innerHTML = '...' 直接抛 TypeError;或者在 attributeChangedCallback 里直接操作 DOM,却忘了它只响应属性变更,不保证元素已挂载。
- 必须在
connectedCallback中检查是否已初始化(比如用this._rendered = true标志),避免重复 append 或多次触发导致 UI 错乱 - 首次挂载时,需手动同步初始属性值:比如
this.attributeChangedCallback('data-id', null, this.getAttribute('data-id')) - 若依赖外部数据(如全局配置),确保该数据已在
connectedCallback执行前就绪,否则要加 guard 判断
attributeChangedCallback 只响应声明过的属性变更
这个钩子不会自动监听任意 data-* 或普通属性,必须靠 static get observedAttributes() 显式声明,且名字大小写、连字符必须和 HTML 中完全一致。比如 <user-card data-user-id="123"></user-card>,就必须写 return ['data-user-id'],写成 ['userId'] 或 ['datasetid'] 都无效。
更关键的是:它只对 attribute 级变更敏感。你写 el.dataset.userId = '456' 或 el.userId = '456' 都不会触发;只有 el.setAttribute('data-user-id', '456') 或 HTML 解析时的初始值才会进来。
-
oldValue在首次设置时恒为null,所以初始化逻辑不能只靠它,得在connectedCallback中兜底读取 - 不要在该回调里调用
fetch或修改其他元素,它设计初衷是轻量响应,重逻辑仍应交由connectedCallback统一调度 - 若需 property 与 attribute 同步(如
this.disabled = true也更新disabled属性),得在 setter 里手动调this.setAttribute('disabled', '')
disconnectedCallback 清理不执行是常态,不是 bug
这个钩子名义上是“元素被移除时调用”,但浏览器不保证它一定运行。页面刷新、关闭 tab、后台标签页被冻结等场景下,它大概率被跳过。所以任何关键清理(比如取消请求、清除定时器)必须有 fallback。
典型误用是把它当“最后保存点”:在里面写 localStorage.setItem(...),结果用户关掉页面就丢了数据;或只在这里 clearInterval(this._timer),却没在 connectedCallback 里做防重入判断,导致多个 timer 堆积。
- 推荐用
AbortController控制 fetch 请求:在connectedCallback创建new AbortController(),传入signal;在disconnectedCallback调this._abortController.abort(),即使没执行,请求超时或页面销毁也会自然中断 - 定时器必须在
disconnectedCallback清除,但也要在connectedCallback开始前先clearTimeout一次,防止旧 timer 残留 - 不要在里面访问
document或其他可能已失效的 DOM 引用,此时元素已脱离树,部分上下文已被回收
customElements.define 必须在使用前同步注册
这是最容易被忽略的前置条件:如果 HTML 里写了 <ds-button></ds-button>,但 customElements.define('ds-button', ...) 还没执行,浏览器会永久把它当 HTMLUnknownElement 处理——生命周期钩子一个都不会触发,连 connectedCallback 都不会来。
Vite 或 Webpack 动态导入时尤其危险:组件类被拆进 import() 分支,而 HTML 已先解析完毕,导致“标签写了,JS 没到”,整个元素静默失效。
- 所有
customElements.define()必须收口到一个同步加载的入口文件(如components/index.js),并通过<script defer></script>或顶层import加载 - SSR 场景下必须包裹
if (typeof window !== 'undefined'),否则 Node.js 环境直接报ReferenceError: customElements is not defined - 禁止在 Shadow DOM 内部动态注册新元素并期望父文档生命周期响应——它只认当前文档主树的挂载状态
实际开发中,最常被绕过的不是某个钩子怎么写,而是 注册时机 和 attribute/property 区分。前者让整个生命周期形同虚设,后者让 attributeChangedCallback 看似写了却永远不进断点。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











