一、带注释的完整源码
<template>
<!-- 使用过渡动画,动画名称为 el-zoom-in-top -->
<transition name="el-zoom-in-top" @after-leave="doDestroy">
<!-- 建议列表容器,语义化为区域 -->
<div
v-show="showPopper"
class="el-autocomplete-suggestion el-popper"
:class="{ 'is-loading': !parent.hideLoading && parent.loading }" <!-- 加载状态类 -->
:style="{ width: dropdownWidth }" <!-- 动态宽度控制 -->
role="region"> <!-- 无障碍访问:标记为区域 -->
<!-- 滚动条组件包裹建议列表 -->
<el-scrollbar
tag="ul" <!-- 滚动区域标签类型为 ul -->
wrap-class="el-autocomplete-suggestion__wrap" <!-- 滚动容器类名 -->
view-class="el-autocomplete-suggestion__list"> <!-- 滚动内容类名 -->
<!-- 加载状态提示 -->
<li v-if="!parent.hideLoading && parent.loading"><i class="el-icon-loading"></i></li>
<!-- 建议列表内容(插槽) -->
<slot v-else></slot>
</el-scrollbar>
</div>
</transition>
</template>
<script>
// 导入Popper.js核心功能
import Popper from 'element-ui/src/utils/vue-popper';
// 导入事件分发混合
import Emitter from 'element-ui/src/mixins/emitter';
// 导入滚动条组件
import ElScrollbar from 'element-ui/packages/scrollbar';
export default {
components: { ElScrollbar }, <!-- 注册滚动条组件 -->
mixins: [Popper, Emitter], <!-- 混入Popper和Emitter功能 -->
componentName: 'ElAutocompleteSuggestions', <!-- 组件标识 -->
data() {
return {
parent: this.$parent, <!-- 获取父组件实例 -->
dropdownWidth: '' <!-- 下拉框宽度(动态计算) -->
};
},
props: {
options: { <!-- Popper配置(实际未使用) -->
default() {
return {
gpuAcceleration: false
};
}
},
id: String <!-- 建议列表的id(用于无障碍) -->
},
methods: {
// 选择建议项时触发父组件事件
select(item) {
this.dispatch('ElAutocomplete', 'item-click', item); <!-- 分发事件 -->
}
},
updated() {
// 内容更新后更新弹出框位置
this.$nextTick(_ => {
this.popperJS && this.updatePopper(); <!-- 调用Popper更新 -->
});
},
mounted() {
// 设置Popper所需的关键元素
this.$parent.popperElm = this.popperElm = this.$el; <!-- 弹出框元素 -->
this.referenceElm = this.$parent.$refs.input.$refs.input || this.$parent.$refs.input.$refs.textarea; <!-- 参考元素(输入框) -->
this.referenceList = this.$el.querySelector('.el-autocomplete-suggestion__list'); <!-- 列表元素 -->
this.referenceList.setAttribute('role', 'listbox'); <!-- 无障碍:设置为列表框 -->
this.referenceList.setAttribute('id', this.id); <!-- 设置id用于关联 -->
},
created() {
// 监听父组件的可见性事件
this.$on('visible', (val, inputWidth) => {
this.dropdownWidth = inputWidth + 'px'; <!-- 动态计算宽度 -->
this.showPopper = val; <!-- 控制显示 -->
});
}
};
</script>
二、学习笔记(Markdown格式)
Element UI Autocomplete Suggestions 组件深度解析
一、核心设计思想
该组件是Element UI Autocomplete组件的下拉建议列表容器,核心关注点:
- 语义化布局:使用
role="region"和role="listbox"提升无障碍访问 - 动态定位:通过Popper.js实现精准定位
- 内容隔离:使用插槽实现内容与布局解耦
二、关键部分分析
1. 语义化与无障碍设计(重点!)
<div role="region">
<el-scrollbar ...>
<li v-if="..."><i class="el-icon-loading"></i></li>
<slot></slot>
</el-scrollbar>
</div>
this.referenceList.setAttribute('role', 'listbox');
this.referenceList.setAttribute('id', this.id);
💡 为什么重要:
role="region"标记为独立区域role="listbox"使屏幕阅读器识别为列表框id属性使输入框与列表关联(WCAG 2.0标准)
2. 动态宽度控制(核心功能)
created() {
this.$on('visible', (val, inputWidth) => {
this.dropdownWidth = inputWidth + 'px'; <!-- 关键:动态计算宽度 -->
this.showPopper = val;
});
}
✅ 设计精髓:
- 下拉框宽度 = 输入框宽度(避免内容溢出)
- 通过字符串拼接确保CSS单位正确(
inputWidth + 'px')- 记忆点:所有宽度属性必须带单位(px/%等)
3. Popper.js集成(关键难点)
mixins: [Popper, Emitter],
mounted() {
this.$parent.popperElm = this.popperElm = this.$el; <!-- 设置弹出框元素 -->
this.referenceElm = this.$parent.$refs.input.$refs.input || ...; <!-- 设置参考元素 -->
}
💡 为什么是难点:
- Popper.js需要
popperElm(弹出框)和referenceElm(参考元素)- 参考元素可能是
input或textarea(需兼容处理)- 记忆点:
popperElm和referenceElm是Popper.js的必需属性
4. 滚动条与内容渲染(易错点)
<el-scrollbar
tag="ul"
wrap-class="el-autocomplete-suggestion__wrap"
view-class="el-autocomplete-suggestion__list">
<slot></slot>
</el-scrollbar>
⚠️ 常见错误:
- 未设置
view-class→ 滚动区域样式失效- 未用
tag="ul"→ 语义化错误(列表应为ul)- 记忆点:滚动条组件需正确设置
tag和view-class
三、难点分析(必须掌握)
1. 父子组件通信机制(核心难点)
// 父组件触发(ElAutocomplete)
this.$emit('visible', true, inputWidth);
// 子组件监听(ElAutocompleteSuggestions)
created() {
this.$on('visible', (val, inputWidth) => {
this.dropdownWidth = inputWidth + 'px';
this.showPopper = val;
});
}
✅ 为什么是难点:
- Element UI内部组件通信不使用props,而是通过
$emit/$on- 通信发生在
created钩子(比mounted更早)- 记忆点:所有内部组件通信都通过
$emit/$on,而非props
2. Popper.js的生命周期管理(关键难点)
mounted() {
// 设置Popper所需的关键元素
this.$parent.popperElm = this.popperElm = this.$el;
this.referenceElm = ...;
}
updated() {
this.$nextTick(_ => {
this.popperJS && this.updatePopper(); <!-- 更新定位 -->
});
}
<transition @after-leave="doDestroy"> <!-- 动画结束后销毁 -->
✅ 为什么是难点:
updatePopper()必须在$nextTick中调用(确保DOM更新)doDestroy在动画结束后调用,避免内存泄漏- 记忆点:所有Popper.js组件必须在
after-leave调用doDestroy
3. 加载状态的智能处理(细节难点)
<li v-if="!parent.hideLoading && parent.loading">
<i class="el-icon-loading"></i>
</li>
💡 设计亮点:
hideLoading属性(由父组件控制)决定是否显示加载图标- 通过
!parent.hideLoading && parent.loading双重判断确保逻辑正确- 记忆点:加载状态 = 父组件未隐藏 + 正在加载
四、学习建议
1. 实践验证
<el-autocomplete
v-model="state"
:fetch-suggestions="querySearch"
placeholder="请输入内容">
<template #default="{ item }">
<div class="custom-item">
{{ item.value }}
</div>
</template>
</el-autocomplete>
2. 调试技巧
- 检查无障碍属性:
- 开发者工具 → Elements → 检查
role="listbox"和id属性
- 开发者工具 → Elements → 检查
- 验证宽度:
- 修改输入框宽度 → 观察下拉框宽度是否同步变化
- 调试Popper:
- 在
mounted中打印this.referenceElm确认元素正确
- 在
3. 深入思考
- 为什么不用
props传递宽度?- 父组件(ElAutocomplete)需要动态获取输入框宽度
- 通过事件传递比props更灵活(避免循环依赖)
- 如果父组件没有
$refs.input会怎样?- 代码中已做兼容:
this.$parent.$refs.input.$refs.input || ... - 记忆点:Element UI组件需兼容多种输入类型
- 代码中已做兼容:
💎 总结:这个组件是Element UI内部通信机制和无障碍设计的典范。必须掌握:
- 通过
$on/$emit实现内部通信- Popper.js的生命周期管理(
updatePopper+doDestroy)- 语义化标签与无障碍属性的正确使用
重点记忆:
this.$parent.popperElm = this.popperElm = this.$el这行代码是Popper.js正常工作的关键!
一、带注释的完整源码
<template>
<!-- 主容器:自动完成输入框,语义化为组合框 -->
<div
class="el-autocomplete"
v-clickoutside="close" <!-- 点击外部关闭建议列表 -->
aria-haspopup="listbox" <!-- 无障碍:表示有下拉列表 -->
role="combobox" <!-- 无障碍:组合框角色 -->
:aria-expanded="suggestionVisible" <!-- 无障碍:当前是否展开 -->
:aria-owns="id" <!-- 无障碍:关联建议列表id -->
>
<!-- 内部输入框(基于el-input封装) -->
<el-input
ref="input"
v-bind="[$props, $attrs]" <!-- 保留所有props和attrs -->
@input="handleInput" <!-- 输入事件 -->
@change="handleChange" <!-- 值变化事件 -->
@focus="handleFocus" <!-- 聚焦事件 -->
@blur="handleBlur" <!-- 失焦事件 -->
@clear="handleClear" <!-- 清除事件 -->
@keydown.up.native.prevent="highlight(highlightedIndex - 1)" <!-- 上箭头 -->
@keydown.down.native.prevent="highlight(highlightedIndex + 1)" <!-- 下箭头 -->
@keydown.enter.native="handleKeyEnter" <!-- 回车 -->
@keydown.native.tab="close" <!-- Tab键关闭 -->
>
<!-- 插槽支持(前置/后置/前缀/后缀) -->
<template slot="prepend" v-if="$slots.prepend">
<slot name="prepend"></slot>
</template>
<template slot="append" v-if="$slots.append">
<slot name="append"></slot>
</template>
<template slot="prefix" v-if="$slots.prefix">
<slot name="prefix"></slot>
</template>
<template slot="suffix" v-if="$slots.suffix">
<slot name="suffix"></slot>
</template>
</el-input>
<!-- 建议列表容器(自定义组件) -->
<el-autocomplete-suggestions
visible-arrow
:class="[popperClass ? popperClass : '']" <!-- 自定义弹出层类 -->
:popper-options="popperOptions" <!-- Popper配置 -->
:append-to-body="popperAppendToBody" <!-- 是否追加到body -->
ref="suggestions"
:placement="placement" <!-- 位置(bottom-start等) -->
:id="id"> <!-- 建议列表id(用于无障碍) -->
<!-- 建议项列表 -->
<li
v-for="(item, index) in suggestions"
:key="index"
:class="{'highlighted': highlightedIndex === index}" <!-- 高亮样式 -->
@click="select(item)" <!-- 点击选择 -->
:id="`${id}-item-${index}`" <!-- 每项id(无障碍) -->
role="option" <!-- 无障碍:选项角色 -->
:aria-selected="highlightedIndex === index" <!-- 无障碍:是否选中 -->
>
<!-- 插槽支持自定义建议项 -->
<slot :item="item">
{{ item[valueKey] }} <!-- 默认显示valueKey属性的值 -->
</slot>
</li>
</el-autocomplete-suggestions>
</div>
</template>
<script>
import debounce from 'throttle-debounce/debounce';
import ElInput from 'element-ui/packages/input';
import Clickoutside from 'element-ui/src/utils/clickoutside';
import ElAutocompleteSuggestions from './autocomplete-suggestions.vue';
import Emitter from 'element-ui/src/mixins/emitter';
import Migrating from 'element-ui/src/mixins/migrating';
import { generateId } from 'element-ui/src/utils/util';
import Focus from 'element-ui/src/mixins/focus';
export default {
name: 'ElAutocomplete',
mixins: [Emitter, Focus('input'), Migrating], <!-- 混入事件、焦点和迁移支持 -->
inheritAttrs: false, <!-- 不继承attrs(避免与el-input冲突) -->
componentName: 'ElAutocomplete', <!-- 组件标识 -->
components: {
ElInput,
ElAutocompleteSuggestions
},
directives: { Clickoutside }, <!-- 注册点击外部指令 -->
props: {
valueKey: { <!-- 数据项中显示的键 -->
type: String,
default: 'value'
},
popperClass: String, <!-- 自定义弹出层类名 -->
popperOptions: Object, <!-- Popper配置 -->
placeholder: String, <!-- 占位符 -->
clearable: { <!-- 是否可清除 -->
type: Boolean,
default: false
},
disabled: Boolean, <!-- 是否禁用 -->
name: String, <!-- 输入框name -->
size: String, <!-- 尺寸 -->
value: String, <!-- 当前值 -->
maxlength: Number, <!-- 最大长度 -->
minlength: Number, <!-- 最小长度 -->
autofocus: Boolean, <!-- 自动聚焦 -->
fetchSuggestions: Function, <!-- 数据获取函数 -->
triggerOnFocus: { <!-- 是否聚焦时触发 -->
type: Boolean,
default: true
},
customItem: String, <!-- 废弃属性(使用插槽代替) -->
selectWhenUnmatched: { <!-- 是否匹配未匹配项 -->
type: Boolean,
default: false
},
prefixIcon: String, <!-- 前置图标 -->
suffixIcon: String, <!-- 后置图标 -->
label: String, <!-- label -->
debounce: { <!-- 防抖时间 -->
type: Number,
default: 300
},
placement: { <!-- 弹出位置 -->
type: String,
default: 'bottom-start'
},
hideLoading: Boolean, <!-- 是否隐藏加载状态 -->
popperAppendToBody: { <!-- 是否追加到body -->
type: Boolean,
default: true
},
highlightFirstItem: { <!-- 是否高亮第一个 -->
type: Boolean,
default: false
}
},
data() {
return {
activated: false, <!-- 是否已激活(聚焦状态) -->
suggestions: [], <!-- 建议列表 -->
loading: false, <!-- 加载状态 -->
highlightedIndex: -1, <!-- 高亮项索引 -->
suggestionDisabled: false <!-- 禁用建议列表 -->
};
},
computed: {
// 是否显示建议列表
suggestionVisible() {
const suggestions = this.suggestions;
let isValidData = Array.isArray(suggestions) && suggestions.length > 0;
return (isValidData || this.loading) && this.activated;
},
// 生成唯一id(用于无障碍)
id() {
return `el-autocomplete-${generateId()}`;
}
},
watch: {
// 监听建议列表可见性变化
suggestionVisible(val) {
let $input = this.getInput();
if ($input) {
// 通过broadcast广播给下拉列表组件
this.broadcast('ElAutocompleteSuggestions', 'visible', [val, $input.offsetWidth]);
}
}
},
methods: {
// 迁移配置(废弃属性提示)
getMigratingConfig() {
return {
props: {
'custom-item': 'custom-item is removed, use scoped slot instead.',
'props': 'props is removed, use value-key instead.'
}
};
},
// 获取数据(核心逻辑)
getData(queryString) {
if (this.suggestionDisabled) return;
this.loading = true;
this.fetchSuggestions(queryString, (suggestions) => {
this.loading = false;
if (this.suggestionDisabled) return;
if (Array.isArray(suggestions)) {
this.suggestions = suggestions;
this.highlightedIndex = this.highlightFirstItem ? 0 : -1;
} else {
console.error('[Element Error][Autocomplete]autocomplete suggestions must be an array');
}
});
},
// 输入处理
handleInput(value) {
this.$emit('input', value);
this.suggestionDisabled = false;
if (!this.triggerOnFocus && !value) {
this.suggestionDisabled = true;
this.suggestions = [];
return;
}
this.debouncedGetData(value); <!-- 防抖调用 -->
},
// 值变化处理
handleChange(value) {
this.$emit('change', value);
},
// 聚焦处理
handleFocus(event) {
this.activated = true;
this.$emit('focus', event);
if (this.triggerOnFocus) {
this.debouncedGetData(this.value); <!-- 聚焦时触发 -->
}
},
// 失焦处理
handleBlur(event) {
this.$emit('blur', event);
},
// 清除处理
handleClear() {
this.activated = false;
this.$emit('clear');
},
// 关闭建议列表
close(e) {
this.activated = false;
},
// 回车键处理
handleKeyEnter(e) {
if (this.suggestionVisible && this.highlightedIndex >= 0 && this.highlightedIndex < this.suggestions.length) {
e.preventDefault();
this.select(this.suggestions[this.highlightedIndex]);
} else if (this.selectWhenUnmatched) {
this.$emit('select', {value: this.value});
this.$nextTick(_ => {
this.suggestions = [];
this.highlightedIndex = -1;
});
}
},
// 选择建议项
select(item) {
this.$emit('input', item[this.valueKey]);
this.$emit('select', item);
this.$nextTick(_ => {
this.suggestions = [];
this.highlightedIndex = -1;
});
},
// 高亮项处理
highlight(index) {
if (!this.suggestionVisible || this.loading) return;
if (index < 0) {
this.highlightedIndex = -1;
return;
}
if (index >= this.suggestions.length) {
index = this.suggestions.length - 1;
}
// 滚动到高亮项位置
const suggestion = this.$refs.suggestions.$el.querySelector('.el-autocomplete-suggestion__wrap');
const suggestionList = suggestion.querySelectorAll('.el-autocomplete-suggestion__list li');
let highlightItem = suggestionList[index];
let scrollTop = suggestion.scrollTop;
let offsetTop = highlightItem.offsetTop;
if (offsetTop + highlightItem.scrollHeight > (scrollTop + suggestion.clientHeight)) {
suggestion.scrollTop += highlightItem.scrollHeight;
}
if (offsetTop < scrollTop) {
suggestion.scrollTop -= highlightItem.scrollHeight;
}
this.highlightedIndex = index;
// 更新无障碍属性
let $input = this.getInput();
$input.setAttribute('aria-activedescendant', `${this.id}-item-${this.highlightedIndex}`);
},
// 获取输入框引用
getInput() {
return this.$refs.input.getInput();
}
},
mounted() {
// 创建防抖函数
this.debouncedGetData = debounce(this.debounce, this.getData);
// 监听下拉列表的点击事件
this.$on('item-click', item => {
this.select(item);
});
// 设置无障碍属性
let $input = this.getInput();
$input.setAttribute('role', 'textbox');
$input.setAttribute('aria-autocomplete', 'list');
$input.setAttribute('aria-controls', this.id);
$input.setAttribute('aria-activedescendant', `${this.id}-item-${this.highlightedIndex}`);
},
beforeDestroy() {
// 销毁下拉列表组件
this.$refs.suggestions.$destroy();
}
};
</script>
二、学习笔记(Markdown格式)
Element UI Autocomplete 组件深度解析
一、核心设计思想
Autocomplete 组件是 Element UI 中高度交互的输入组件,核心关注点:
- 无障碍设计:严格遵循WCAG 2.0标准
- 性能优化:通过防抖减少请求频率
- 内容解耦:通过插槽实现内容与布局分离
- 状态管理:精准控制建议列表的显示/隐藏
二、关键部分分析
1. 无障碍设计(重中之重!)
<div
aria-haspopup="listbox"
role="combobox"
:aria-expanded="suggestionVisible"
:aria-owns="id"
>
// 设置输入框无障碍属性
$input.setAttribute('role', 'textbox');
$input.setAttribute('aria-autocomplete', 'list');
$input.setAttribute('aria-controls', this.id);
$input.setAttribute('aria-activedescendant', `${this.id}-item-${this.highlightedIndex}`);
💡 为什么重要:
role="combobox":明确表示组合框aria-autocomplete="list":告知屏幕阅读器有自动完成列表aria-activedescendant:动态跟踪当前高亮项- 记忆点:无障碍属性必须与DOM结构严格匹配
2. 防抖优化(性能关键)
mounted() {
this.debouncedGetData = debounce(this.debounce, this.getData);
}
handleInput(value) {
this.debouncedGetData(value); // 防抖调用
}
✅ 设计精髓:
- 使用
throttle-debounce库实现防抖- 默认300ms防抖(避免频繁请求)
- 记忆点:所有输入型组件必须使用防抖
3. 父子组件通信(核心难点)
// 父组件(ElAutocomplete)发送可见性事件
watch: {
suggestionVisible(val) {
this.broadcast('ElAutocompleteSuggestions', 'visible', [val, $input.offsetWidth]);
}
}
// 子组件(ElAutocompleteSuggestions)监听
created() {
this.$on('visible', (val, inputWidth) => {
this.dropdownWidth = inputWidth + 'px';
this.showPopper = val;
});
}
💡 为什么是难点:
- Element UI内部组件不使用props传递数据
- 通过
broadcast(广播)和$on实现通信- 记忆点:内部组件通信 =
this.broadcast+this.$on
4. 高亮项滚动定位(细节难点)
highlight(index) {
if (!this.suggestionVisible || this.loading) return;
// ... 计算滚动位置 ...
suggestion.scrollTop += highlightItem.scrollHeight; // 滚动
this.highlightedIndex = index;
// 更新无障碍属性
$input.setAttribute('aria-activedescendant', `${this.id}-item-${index}`);
}
⚠️ 常见错误:
- 未处理滚动边界(导致滚动位置错误)
- 未更新
aria-activedescendant(无障碍失效)- 记忆点:滚动定位必须在
$nextTick中执行(但这里直接操作DOM,需确保DOM已渲染)
三、难点分析(必须掌握)
1. 无障碍属性的完整链路(核心难点)
graph LR
A[输入框] -->|aria-autocomplete=list| B[建议列表]
A -->|aria-controls=id| C[建议列表ID]
A -->|aria-activedescendant=当前项ID| D[当前高亮项]
D -->|role=option| E[建议项]
✅ 为什么必须掌握:
- 无障碍是Web标准强制要求
- Element UI的无障碍实现是行业标杆
- 记忆点:
aria-activedescendant必须指向当前高亮项的ID
2. 防抖与请求逻辑(关键难点)
getData(queryString) {
if (this.suggestionDisabled) return;
this.loading = true;
this.fetchSuggestions(queryString, (suggestions) => {
this.loading = false;
// 处理数据...
});
}
handleInput(value) {
this.debouncedGetData(value); // 防抖调用
}
💡 设计精髓:
suggestionDisabled防止无效请求(如清空输入时)loading状态控制加载图标显示- 记忆点:数据请求必须包含
loading状态管理
3. 事件处理的优先级(易错点)
@keydown.up.native.prevent="highlight(highlightedIndex - 1)"
@keydown.down.native.prevent="highlight(highlightedIndex + 1)"
@keydown.enter.native="handleKeyEnter"
⚠️ 为什么重要:
native.prevent阻止默认行为(如输入框的上/下键移动光标)- 优先级:
keydown>input(确保快捷键生效)- 记忆点:键盘事件必须用
native修饰符
四、重点代码总结(必须记忆)
1. 无障碍属性链(核心)
// 输入框
$input.setAttribute('aria-autocomplete', 'list');
$input.setAttribute('aria-controls', this.id);
$input.setAttribute('aria-activedescendant', `${this.id}-item-${this.highlightedIndex}`);
// 建议项
role="option"
:aria-selected="highlightedIndex === index"
💎 为什么是重点:
这是Element UI无障碍设计的完整实现,任何组件都需要遵循此模式
2. 防抖请求逻辑(核心功能)
mounted() {
this.debouncedGetData = debounce(this.debounce, this.getData);
}
handleInput(value) {
this.debouncedGetData(value);
}
💎 为什么是重点:
防抖是所有输入型组件的标配,Element UI通过此模式实现高性能
3. 父子通信机制(内部核心)
// 父组件
this.broadcast('ElAutocompleteSuggestions', 'visible', [val, $input.offsetWidth]);
// 子组件
this.$on('visible', (val, inputWidth) => {
this.dropdownWidth = inputWidth + 'px';
this.showPopper = val;
});
💎 为什么是重点:
这是Element UI内部组件通信的通用模式,必须掌握
五、学习建议
1. 实践验证
<el-autocomplete
v-model="state"
:fetch-suggestions="querySearch"
placeholder="请输入内容"
:trigger-on-focus="false"
@select="handleSelect">
<template #default="{ item }">
<div class="custom-item">
{{ item.value }}
</div>
</template>
</el-autocomplete>
2. 调试技巧
- 无障碍验证:
- 使用屏幕阅读器(如NVDA)测试组件
- 检查
aria-activedescendant是否动态更新
- 性能测试:
- 快速输入测试防抖效果
- 检查请求频率是否符合预期
- 边界测试:
- 输入空字符串时是否禁用建议列表
- 选择未匹配项时是否触发
selectWhenUnmatched
3. 深入思考
-
为什么不用props传递宽度?
- 宽度需要动态获取输入框宽度(
$input.offsetWidth) - 通过事件传递比props更灵活(避免循环依赖)
- 宽度需要动态获取输入框宽度(
-
highlightFirstItem的作用?- 聚焦时自动高亮第一个建议项
- 提升用户体验(快速选择)
-
suggestionDisabled的用途?- 防止在输入为空时触发请求
- 避免无效的API调用
💎 总结:这个组件完美展示了Element UI的无障碍设计、性能优化和内部通信机制。必须掌握的要点:
- 无障碍属性的完整链路(
aria-activedescendant是关键)- 防抖请求的实现逻辑
- 父子组件通信的
broadcast/$on模式重点记忆:
aria-activedescendant必须指向当前高亮项的ID,这是无障碍的核心!