注册搜索过滤组件时必须继承 htmlelement,不可直接继承htmldivelement或htmlinputelement;正确做法是继承htmlelement、使用shadow dom封装结构,并在connectedcallback中绑定input事件监听。

用 customElements.define() 注册搜索过滤组件时,必须继承 HTMLElement
直接 extends HTMLDivElement 或 HTMLInputElement 会失败——浏览器只允许自定义元素继承 HTMLElement 或其已知子类(如 HTMLButtonElement),但不包括具体表单控件。否则控制台报错:Failed to execute 'define' on 'CustomElementRegistry': The constructor must extend HTMLElement。
正确做法是让自定义元素包裹内部结构,比如:
class SearchFilter extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<div class="search-container">
<input type="search" placeholder="Search..."><ul class="results"></ul>
</div>
<style>.search-container { display: flex; flex-direction: column; }</style>
`;
}
}
关键点:
- 必须调用
super(),否则 this 未初始化 - 必须用
attachShadow()创建影子 DOM,否则样式和事件易受外部干扰 - 内部
<input type="search">保留原生语义(软键盘“搜索”键、清除按钮、无障碍支持)
input 事件监听写在 connectedCallback 里,别在 constructor 中绑定
constructor 执行时,元素尚未插入 DOM,this.shadowRoot 已存在,但内部 input 元素还没 ready——此时 querySelector('input') 返回 null,绑定失败且无报错。
应该等元素挂载后操作:
connectedCallback() {
const input = this.shadowRoot.querySelector('input');
input.addEventListener('input', e => {
const query = e.target.value.trim();
this.#filterAndRender(query);
});
}
注意:
- 不要用
keyup:中文输入法下,用户按“shu”还没选字,“书”就搜不到 - 不要用
change:失去焦点才触发,不是实时过滤 - 防抖要谨慎:200 条以内列表加 300ms 防抖反而让用户觉得延迟;真卡顿来自 DOM 重排,不是事件频次
过滤逻辑必须区分 textContent 和 id/alt 匹配,且优先级要明确
你传进来的列表项可能是 <button id="user-edit">编辑用户</button>,也可能是 <div alt="删除订单">?️</div>。匹配目标不能硬编码成 el.id 或 el.textContent,得由使用者决定或 fallback。
使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。
推荐策略:
- 若元素有
data-search-key属性,优先用它(显式可控) - 否则 fallback 到
el.alt || el.id || el.textContent - 匹配前统一转小写 +
.trim(),避免 “ react ” 匹配失败 - 用
String.prototype.startsWith()做前缀匹配,比includes()更符合“输入即过滤”的直觉
示例片段:
this.#filterAndRender = (query) => {
const items = this.shadowRoot.querySelectorAll('.search-item');
const lowerQuery = query.toLowerCase();
items.forEach(item => {
const key = item.dataset.searchKey ||
item.alt ||
item.id ||
item.textContent;
const show = !query || (key?.toLowerCase().startsWith(lowerQuery) ?? false);
item.classList.toggle('hidden', !show);
});
};
隐藏列表项必须用 classList.toggle("hidden"),别碰 style.display
硬设 item.style.display = "none" 会覆盖元素原本的 display 类型(比如 inline-flex 按钮变成 block),导致布局塌陷或对齐错乱。尤其当你的列表项是 flex 容器子项时,问题立刻暴露。
正确方式是 CSS 类控制:
.hidden {
opacity: 0;
visibility: hidden;
pointer-events: none;
}
好处:
- 保留占位(如果需要过渡动画)或彻底移出流(配合
display: none) - 不干扰原有 display 值
- 便于调试:检查元素 class 就知道是否被过滤
- 避免和第三方组件库的
!important样式冲突
最后提醒:别在 disconnectedCallback 里手动清理事件监听——影子 DOM 销毁时,内部事件自然解绑;重点是确保 render 方法不重复 append 同一节点,否则 DOM 节点泄漏。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










