el-input 源码
学习element-ui 中input组件源码
el-input
基本结构
可以看到input组件使用的是原生的input元素
禁用状态
设置disabled属性,也是利用原生input的disabled属性;如果在form组件中设置了disabled,也会自动设置input组件。
inputDisabled() {
return this.disabled || (this.elForm || {}).disabled;
},
可清空
设置clearable属性,可以清空input元素中的内容。后面的clear icon只会在输入框中有内容,或者hover时才会显示。
showClear() {
return this.clearable &&
!this.inputDisabled &&
!this.readonly &&
this.nativeInputValue &&
(this.focused || this.hovering);
},
点击clear icon会清空元素中的内容,触发clear方法,因为用户在使用input组件时是通过v-model来绑定值的所以会触发input事件,设置值为空。
clear() {
this.$emit('input', '');
this.$emit('change', '');
this.$emit('clear');
},
input没有绑定value值,而是在代码中通过ref来对input元素进行赋值。触发了input事件后,el-input组件通过computed重新设置了nativeInputValue
nativeInputValue() {
return this.value === null || this.value === undefined ? '' : String(this.value);
},
通过watch监听nativeInputValue,来给input元素设置值
nativeInputValue() {
this.setNativeInputValue();
},
setNativeInputValue() {
const input = this.getInput();
if (!input) return;
if (input.value === this.nativeInputValue) return;
input.value = this.nativeInputValue;
},
为什么不直接按以下方式重新设置呢,源代码中贴出了一个bug,主要是为了解决在完成输入之前如果触发了dom更新,输入框中的值并不会更新。
this.$emit('input', value)
bug链接
密码框
设置showPassword,会显示一个 el-icon-view的icon,并且input组件被设置为了passwor类型。
点击view icon 会使input元素在text 和 password两个原生属性之间切换。
handlePasswordVisible() {
this.passwordVisible = !this.passwordVisible;
this.$nextTick(() => {
this.focus();
});
},
带 icon 的输入框
可以通过 prefix-icon 和 suffix-icon 属性在 input 组件首部和尾部增加显示图标,也可以通过 slot 来放置图标。
<!-- 前置内容 -->
<span class="el-input__prefix" v-if="$slots.prefix || prefixIcon">
<slot name="prefix"></slot>
<i class="el-input__icon"
v-if="prefixIcon"
:class="prefixIcon">
</i>
</span>
<!-- 后置内容 -->
<span
class="el-input__suffix"
v-if="getSuffixVisible()">
<span class="el-input__suffix-inner">
<template v-if="!showClear || !showPwdVisible || !isWordLimitVisible">
<slot name="suffix"></slot>
<i class="el-input__icon"
v-if="suffixIcon"
:class="suffixIcon">
</i>
</template>
文本域
设置type 为 textarea属性,使用原生textarea文本域输入框
可自适应文本高度的文本域
设置autosize 文本框可以自适应高度。输入的内容发生变化时,调用以下函数会自动计算文本的高度。
value(val) {
this.$nextTick(this.resizeTextarea);
if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.change', [val]);
}
},
resizeTextarea() {
if (this.$isServer) return;
const { autosize, type } = this;
if (type !== 'textarea') return;
if (!autosize) {
this.textareaCalcStyle = {
minHeight: calcTextareaHeight(this.$refs.textarea).minHeight
};
return;
}
const minRows = autosize.minRows;
const maxRows = autosize.maxRows;
this.textareaCalcStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows);
},
新建一个hiddenTextarea,通过getComputedStyle来计算元素的高度
export default function calcTextareaHeight(
targetElement,
minRows = 1,
maxRows = null
) {
if (!hiddenTextarea) {
hiddenTextarea = document.createElement('textarea');
document.body.appendChild(hiddenTextarea);
}
let {
paddingSize,
borderSize,
boxSizing,
contextStyle
} = calculateNodeStyling(targetElement);
hiddenTextarea.setAttribute('style', `${contextStyle};${HIDDEN_STYLE}`);
hiddenTextarea.value = targetElement.value || targetElement.placeholder || '';
let height = hiddenTextarea.scrollHeight;
const result = {};
if (boxSizing === 'border-box') {
height = height + borderSize;
} else if (boxSizing === 'content-box') {
height = height - paddingSize;
}
hiddenTextarea.value = '';
let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize;
if (minRows !== null) {
let minHeight = singleRowHeight * minRows;
if (boxSizing === 'border-box') {
minHeight = minHeight + paddingSize + borderSize;
}
height = Math.max(minHeight, height);
result.minHeight = `${ minHeight }px`;
}
if (maxRows !== null) {
let maxHeight = singleRowHeight * maxRows;
if (boxSizing === 'border-box') {
maxHeight = maxHeight + paddingSize + borderSize;
}
height = Math.min(maxHeight, height);
}
result.height = `${ height }px`;
hiddenTextarea.parentNode && hiddenTextarea.parentNode.removeChild(hiddenTextarea);
hiddenTextarea = null;
return result;
};
复合输入框
通过slot增加组件前后的内容
<!-- 前置元素 -->
<div class="el-input-group__prepend" v-if="$slots.prepend">
<slot name="prepend"></slot>
</div>
<!-- 后置元素 -->
<div class="el-input-group__append" v-if="$slots.append">
<slot name="append"></slot>
</div>
尺寸
通过size属性来设置不同的尺寸,主要是应用了不同的class
inputSize() {
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
},
inputSize ? 'el-input--' + inputSize : '',
输入长度限制
利用原生的maxlength属性来限制元素的最大输入
el-autocomplete
基础使用
<el-autocomplete
class="inline-input"
v-model="state1"
:fetch-suggestions="querySearch"
placeholder="请输入内容"
@select="handleSelect"
/>
fetch-suggestions 用于自定义显示匹配的内容列表,并且需要返回一个数组
在基础的用法中,首先点击输入框时会触发handlFocus事件,设置activated属性为true,并且调用
debouncedGetData函数,用于获取输入值为空时的所有列表项。
如果triggerOnFocus为false,那么只会在有输入值并且有匹配项的时候,才会显示匹配的列表。
handleFocus(event) {
this.activated = true;
this.$emit('focus', event);
if (this.triggerOnFocus) {
this.debouncedGetData(this.value);
}
},
可以看到debouncedGetData函数,是一个防抖函数,用于获取数据
this.debouncedGetData = debounce(this.debounce, this.getData);
getData方法中会调用用户传进来的fetchSuggestions方法,并且将queryString传进去,经过用户自定义的匹配
函数后,在调用cb,将匹配的内容赋值给this.suggestions
getData(queryString) {
console.log('queryString', 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;
console.log(this.suggestions);
this.highlightedIndex = this.highlightFirstItem ? 0 : -1;
} else {
console.error('[Element Error][Autocomplete]autocomplete suggestions must be an array');
}
});
},
此时computed中的suggestionVisible属性会设置为true
suggestionVisible() {
const suggestions = this.suggestions;
let isValidData = Array.isArray(suggestions) && suggestions.length > 0;
return (isValidData || this.loading) && this.activated;
},
再触发ElAutocompleteSuggestions组件的visible事件,用于显示匹配的列表
watch: {
suggestionVisible(val) {
let $input = this.getInput();
if ($input) {
this.broadcast('ElAutocompleteSuggestions', 'visible', [val, $input.offsetWidth]);
}
}
},
// ElAutocompleteSuggestions
created() {
this.$on('visible', (val, inputWidth) => {
this.dropdownWidth = inputWidth + 'px';
this.showPopper = val;
});
}
当用户输入建议时 会触发input事件
handleInput(value) {
this.$emit('input', value);
this.suggestionDisabled = false;
if (!this.triggerOnFocus && !value) {
this.suggestionDisabled = true;
this.suggestions = [];
return;
}
this.debouncedGetData(value);
},
自定义模板
和el-input组件一样,可以通过prepend、append、prefix、suffix四个插槽来设置组件前后的图标和内容。
可以通过默认的作用域插槽自定义显示匹配的列表内容
<li
v-for="(item, index) in suggestions"
:key="index"
:class="{'highlighted': highlightedIndex === index}"
@click="select(item)"
:id="`${id}-item-${index}`"
role="option"
:aria-selected="highlightedIndex === index"
>
<slot :item="item">
{{ item[valueKey] }}
</slot>
</li>
远程搜索
远程搜索会有个loading状态。
其实就是在fetch-suggestions函数中不会立即调用cb,组件内部getData方法中有个loading属性
不会立即设置为false。