如何将Vue2实现的自动补全搜索功能迁移为纯Web Components
Web Components自动补全搜索功能问题修复方案
核心问题梳理
- 插槽内容未添加对应
slot属性,无法匹配命名插槽,导致slot内容不渲染 - slot仅作为内容占位节点,原生不支持直接绑定传递函数、复杂类型数据,原有slot传参逻辑完全不生效
- 模板字符串定义在类外部,
this指向全局window,无法读取组件实例的属性和方法 options属性的getter直接返回字符串类型的attribute值,调用数组方法filter会直接报错,复杂类型数据不能存在attribute中- 内联事件绑定(
onchange/onfocus)仅能调用全局作用域函数,类方法序列化后无法正常触发,且原生Web Components不支持Vue的v-if指令 - 父子组件无正常通信逻辑,数据、事件无法在
search-select和auto-complete间同步
修复后完整代码
Search-select 组件
class SearchSelect extends HTMLElement { constructor() { super(); this.query = ""; this.results = []; // 内部存储options数组,不挂载到仅支持字符串的attribute上 this._options = [ "头脑特工队", "疾速追杀", "侏罗纪世界", "指环王", "环太平洋", "加勒比海盗", "猩球崛起", "电锯惊魂", "边境杀手", "僵尸世界大战" ]; this.shadow = this.attachShadow({ mode: "open" }); } setQuery(event) { this.query = event.target.value; this.results = this.filterMethod(this._options, this.query); // 触发自定义事件穿透shadow dom传递结果给子组件 this.dispatchEvent(new CustomEvent('results-update', { detail: this.results, bubbles: true, composed: true })); this.render(); } get options() { return this._options; } set options(val) { if(Array.isArray(val)) { this._options = val; this.results = this.filterMethod(this._options, this.query); this.render(); } } static get observedAttributes() { return ["filterMethod"]; } filterMethod(options, query) { return options.filter((option) => option.toLowerCase().includes(query.toLowerCase()) ); } attributeChangedCallback(prop, oldValue, newValue) { if (prop === "filterMethod" && typeof window[newValue] === 'function') { this.filterMethod = window[newValue]; this.results = this.filterMethod(this._options, this.query); this.render(); } } render() { // 模板放在render方法内,this指向当前组件实例 this.shadow.innerHTML = ` <div> <slot name="autocomp"></slot> </div> `; } connectedCallback() { this.render(); } } customElements.define("search-select", SearchSelect);
Autocomplete 组件
class Autocomplete extends HTMLElement { constructor() { super(); this.dropdownVisible = false; this.results = []; this.shadow = this.attachShadow({ mode: "open" }); } showDropdown() { this.dropdownVisible = true; this.render(); } hideDropdown() { this.dropdownVisible = false; this.render(); } render() { // 动态拼接结果列表,替代v-if指令 const resultList = this.results.map(item => `<li>${item}</li>`).join(''); this.shadow.innerHTML = ` <search-select> <div class="autocomplete" slot="autocomp"> <input type="text" placeholder="输入内容搜索列表" /> <div class="autocomplete-dropdown" style="display: ${this.dropdownVisible ? 'block' : 'none'}"> <ul class="autocomplete-search-results-list"> ${resultList} </ul> </div> </div> </search-select> `; // 手动绑定事件替代内联绑定 const input = this.shadow.querySelector('input'); const searchSelect = this.shadow.querySelector('search-select'); input.addEventListener('input', (e) => searchSelect.setQuery(e)); input.addEventListener('focus', () => this.showDropdown()); // 加延迟避免点击下拉项时下拉框先隐藏导致点击失效 input.addEventListener('blur', () => setTimeout(() => this.hideDropdown(), 200)); // 监听父组件的结果更新事件同步数据 searchSelect.addEventListener('results-update', (e) => { this.results = e.detail; this.render(); }); } connectedCallback() { this.render(); } } customElements.define("auto-complete", Autocomplete);
页面使用方式
直接在HTML中插入组件标签即可调用:
<auto-complete></auto-complete>
内容的提问来源于stack exchange,提问作者scripter2021
相关产品推荐
相关产品推荐

