自定义元素是浏览器原生支持的扩展机制,通过customElements.define()注册后可像内置标签一样使用;通知系统适合用它,因其具备封装性、跨组件复用性、DOM生命周期响应能力(如自动清理),且不依赖框架。
customElements.define() 注册后就能像 <div> 一样用。通知系统需要跨组件复用、统一管理生命周期、避免重复渲染——这些恰好是自定义元素的强项:它自带封装、可复用、能响应 DOM 生命周期(比如自动清理过期通知)。不用框架也能跑,但要注意:必须在 DOM 加载完成前注册,否则 document.createElement('notify-toast') 会返回普通 HTMLUnknownElement。
<notify-toast>核心是继承 HTMLElement,并利用 connectedCallback 和 disconnectedCallback 控制显示/销毁逻辑:class NotifyToast extends HTMLElement { static get observedAttributes() { return ['message', 'type', 'duration']; } connectedCallback() { if (this.rendered) return; this.render(); this.rendered = true; this.startAutoClose(); } render() { const message = this.getAttribute('message') || ''; const type = this.getAttribute('type') || 'info'; this.innerHTML = ` <style> :host { position: fixed; top: 1rem; right: 1rem; z-index: 10000; } .toast { padding: 0.5rem 1rem; border-radius: 4px; color: #fff; } .toast.info { background: #2196f3; } .toast.error { background: #f44336; } </style> <div class="toast ${type}">${message}</div> `; } startAutoClose() { const duration = parseInt(this.getAttribute('duration') || '3000'); setTimeout(() => this.remove(), duration); }}customElements.define('notify-toast', NotifyToast);
关键点:
:host 控制定位和层级,避免被父容器样式污染observedAttributes 不是用来响应属性变更的“监听器”,而是告诉浏览器哪些属性变化时触发 attributeChangedCallback;这里没用它,是因为 toast 通常只渲染一次,改属性不重绘更安全render() 里直接操作 this.textContent,容易覆盖内联样式或子节点new NotifyToast() 手动 append,得有个入口函数集中管理。推荐用轻量级事件总线 + 自动挂载:// notify.jsexport function notify(message, options = {}) { const el = document.createElement('notify-toast'); el.setAttribute('message', message); Object.entries(options).forEach(([k, v]) => el.setAttribute(k, v)); // 确保只挂到 body 一次,避免重复添加 if (!document.body.querySelector('notify-toast')) { document.body.append(el); } else { document.body.prepend(el); // 新 toast 置顶 }}
使用场景:
import { notify } from './notify.js'; notify('保存成功', { type: 'success' });
notify('网络错误', { type: 'error', duration: 5000 });
document.body.append(el) 必须确保 body 已存在,否则报错;建议在模块顶层执行前加 if (document.body) 守卫典型错误现象:
立即学习“前端免费学习笔记(深入)”;
top: 1rem; right: 1rem;,互相覆盖setTimeout 在 connectedCallback 中触发,但若元素被快速 remove() 或未挂载到 document,定时器仍运行,导致误删其他元素position: fixed 却没设 transform: translateZ(0),触发硬件加速缺失修复建议:
position: absolute + 动态计算 top 值,或用 flex 容器统一管理子元素顺序disconnectedCallback 中 clearTimeout(this._timer),并在 startAutoClose 中存引用will-change: transform,尤其在高频触发场景下最常被忽略的是:自定义元素无法在 <head> 或 <template> 内注册,必须等 DOM 解析到 script 标签位置才能调用 customElements.define()。如果用 module script,记得加 type="module" 并确保执行时机。