自定义元素中应缓存 websocket 实例以避免重复连接:在 constructor 初始化私有属性,在 connectedcallback 中检查并创建连接,onclose 时置空;通过 shadow dom 安全渲染消息;监听 attribute 变化时重建连接;dispatchevent 需设置 bubbles: true 和 composed: true 才能跨 shadow boundary 传播。

自定义元素里怎么创建 WebSocket 连接
不能在 connectedCallback 里直接 new WebSocket,否则每次元素被移出再插入 DOM(比如切换 Tab、v-if 切换)都会新建连接,导致重复连接、内存泄漏、服务端连接数暴增。
正确做法是用私有属性缓存连接实例,并在首次连接时初始化:
- 在
constructor中初始化this._socket = null和this._url(从 attribute 或默认值读取) - 在
connectedCallback中检查this._socket是否为null或已关闭(readyState !== WebSocket.OPEN),仅在此时创建新连接 - 监听
onclose时设this._socket = null,避免残留引用
示例关键逻辑:
class ChatWidget extends HTMLElement {
constructor() {
super();
this._socket = null;
this._url = this.getAttribute('endpoint') || 'ws://localhost:8080';
}
connectedCallback() {
if (!this._socket || this._socket.readyState === WebSocket.CLOSED) {
this._socket = new WebSocket(this._url);
this._socket.onmessage = (e) => this._handleMessage(e);
this._socket.onclose = () => { this._socket = null; };
}
}
_handleMessage(event) {
const msg = JSON.parse(event.data);
this.dispatchEvent(new CustomEvent('message', { detail: msg }));
}
}
如何让自定义元素响应消息并更新 UI
自定义元素本身不自动渲染内容,必须手动操作 shadow DOM 或 light DOM。常见错误是直接修改 innerHTML 导致 XSS 漏洞或丢失事件绑定。
推荐方式是用 document.createElement 构建安全节点,再 append 到 shadow root:
- 在
constructor中调用this.attachShadow({ mode: 'open' }) - 在
_handleMessage中创建<div class="msg">,用 <code>textContent设置消息体(不拼接 HTML 字符串) - 对用户昵称等可信任字段,若需富文本支持,应严格白名单过滤,而非直接 innerHTML
- 避免每条消息都重绘整个消息区——用
appendChild追加,而不是替换全部内容 - 在
static get observedAttributes()中返回['endpoint'] - 在
attributeChangedCallback中判断是否是'endpoint'变更,且新旧值不同 - 若已存在连接,先
close()再重建;注意加防抖,防止快速连续 setAttribute 导致频繁重连 - 不要在
attributeChangedCallback里直接调用connect()—— 应复用connectedCallback的连接逻辑,保持单一入口 - 发送时:
this.dispatchEvent(new CustomEvent('message', { detail: msg, bubbles: true, composed: true })) - 接收时:监听必须挂在
document或父容器上,不能只监听chat-widget元素自身(除非用addEventListener('message', ...)在该元素实例上) - 如果父组件是 Vue/React,注意框架可能封装了原生事件,需用
@message.native或onMessage绑定方式
attribute 变化时如何动态更新 WebSocket 地址
observedAttributes 是必须的,否则 attributeChangedCallback 不会触发。只声明它还不够,你还得显式处理地址变更:
为什么 dispatchEvent 后监听不到 custom event
常见原因是事件冒泡被 shadow DOM 阻断,或者监听位置错了。自定义元素发出的事件默认不冒泡出 shadow boundary,除非显式设置 bubbles: true。
正确写法:
shadow DOM 的 composed: true 是关键,漏掉它,外部 JS 就收不到事件。











