打造你自己的UI组件库(一)——一个功能强大的 Vue 输入框组件
在这篇博客中,我们将拆解并深入剖析一个功能强大的 Vue 输入框组件代码。这一组件实现了丰富的功能,如自动聚焦、清除按钮、错误提示、附加图标点击事件等。
组件功能概览
这是一个通用输入框组件,支持以下特性:
- 自定义占位符(
placeholder) - 自动聚焦和自动全选(
isAutoFocus、isAutoSelect) - 前置图标、清除图标、附加图标
- 错误提示及错误样式动画
- 自定义选择范围(
selectedRange) - 输入事件、清除事件等多种事件监听
- 可配置的只读模式(
isReadonly)
接下来,我们将分模块讲解这个组件的实现。
Props:灵活的属性定义
Props 定义说明
组件支持多种参数,以满足不同使用场景。以下是主要的 props 配置:
const props = defineProps({
value: {
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
type: {
type: String,
default: 'text',
},
isError: Boolean,
errorMsg: {
type: String,
default: '',
},
isAutoFocus: {
type: Boolean,
default: false,
},
selectedRange: {
type: Array,
validator(value: unknown): boolean {
return Array.isArray(value) && value.length === 2 && value.every(val => typeof val === 'number')
},
default() {
return undefined
},
},
autocomplete: {
type: String,
default: '',
},
isAutoSelect: {
type: Boolean,
default: false,
},
isReadonly: {
type: Boolean,
default: false,
},
frontIcon: {
type: [String, Object] as PropType<IconType>,
default: '',
},
clearIcon: {
type: [String, Object] as PropType<IconType>,
default: '',
},
...
});
特性解析
-
基础属性
value和placeholder是标准的输入框配置项,用于绑定值和设置占位文本。type定义输入框类型,如text、password等。
-
状态控制
isError和errorMsg用于显示错误样式和错误信息。isAutoFocus和isAutoSelect控制自动聚焦和自动全选功能。
-
验证机制
selectedRange是一个数组,表示输入框的选择范围([start, end])。我们通过validator确保其为合法的[number, number]数组。
核心逻辑实现
自动聚焦与全选功能
组件在 onMounted 生命周期中通过 DOM 操作实现自动聚焦和自动全选:
onMounted(() => {
if (inputElement.value) {
if (props.isAutoFocus) {
inputElement.value.focus();
}
if (props.isAutoSelect) {
updateSelectedRange([0, 9999]); // 全选文本
}
if (props.selectedRange) {
updateSelectedRange(props.selectedRange as [number, number]);
}
}
});
我们定义了一个 updateSelectedRange 函数,用于动态设置文本选中范围:
const updateSelectedRange = (range: [number, number] = [props.value.length, props.value.length]) => {
if (!inputElement.value) return;
inputElement.value.select();
inputElement.value.setSelectionRange(range[0], range[1]);
};
输入框的清除功能
通过监听 clearIcon 的点击事件,清空输入框内容并触发 clear 事件:
const handleClearInput = () => {
inputValue.value = '';
emits('clear');
};
clearIcon 的显示逻辑由计算属性控制:
const showClearIcon = computed(() => inputValue.value?.length > 0);
在模板中绑定点击事件实现清除功能:
<span
v-if="clearIcon"
v-show="showClearIcon"
class="input-clear-icon"
@click="handleClearInput"
>
<img :src="clearIcon" />
</span>
图标点击事件
组件支持附加图标(appendIcon)点击事件,并允许动态选择文本范围:
const handleClickAppendIcon = () => {
props.handleAppendIconFunc();
if (!props.disableSelectRangeOnAppendIcon) {
setTimeout(() => {
updateSelectedRange([inputValue.value.length, inputValue.value.length]);
}, 0);
}
};
模板中的点击绑定实现如下:
<span
v-if="appendIcon"
class="input-append-icon"
@click="handleClickAppendIcon"
>
<img :src="appendIcon" />
</span>
事件管理:输入、焦点和键盘事件
组件通过 defineEmits 管理各种事件,包括 clear、focus、blur、enter、change 和 inputChange:
const emits = defineEmits<{
(e: 'clear'): void;
(e: 'focus'): void;
(e: 'blur', v: string): void;
(e: 'enter', v: string): void;
(e: 'change', v: string): void;
(e: 'inputChange', v: string): void;
}>();
在模板中使用事件绑定:
<input
ref="inputElement"
v-model="inputValue"
class="input-field"
:type="type"
:placeholder="placeholder"
@change="emits('change', inputValue)"
@focus="emits('focus')"
@blur="emits('blur', inputValue)"
@input="emits('inputChange', inputValue)"
@keydown.enter.stop="emits('enter', inputValue)"
/>
样式实现
语义化 CSS 类名。如下:
css:
.input-container {
position: relative;
display: inline-flex;
align-items: center;
}
.input-field {
width: 100%;
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 8px;
transition: all 0.3s ease;
}
.input-field:focus {
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
.input-clear-icon,
.input-append-icon {
position: absolute;
right: 12px;
cursor: pointer;
}
.input-clear-icon {
color: #6c757d;
}
.input-error {
border-color: #dc3545;
}
.input-error:focus {
box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.25);
}
tailwind版:
const inputClasses = computed(() => ({
'h-8 px-3 py-2': props.size !== 'large',
'h-10 px-4 py-2': props.size === 'large',
'bg-light-input dark:bg-dark-input': true,
'border-red-800': props.isError,
// ...
}))
动画实现
@keyframes wiggle {
0%,
100% {
transform: translateX(0);
}
10%,
30%,
50%,
70%,
90% {
transform: translateX(-4px);
}
20%,
40%,
60%,
80% {
transform: translateX(4px);
}
}
.animate__wiggle {
animation: wiggle 1s;
}
Template结构:
<div
class="relative"
:class="{
'empty': !value,
'animate__wiggle': isError ,
'space-y-2': isError && errorMsg
}"
>
<BaseInputIcon
v-if="frontIcon"
:icon="frontIcon"
:style="frontIconStyle"
class="left-2"
/>
<BaseInputIcon
v-if="clearIcon && showClearIcon"
:icon="clearIcon"
class="right-2 cursor-pointer"
@click="handleClearInput"
/>
<BaseInputIcon
v-if="appendIcon"
:icon="appendIcon"
class="right-2 cursor-pointer"
@click="handleClickAppendIcon"
/>
<input
ref="inputElement"
v-model="inputValue"
:class="inputClasses"
:type="type"
:placeholder="placeholder"
:autocomplete="autocomplete"
:readonly="isReadonly"
:data-focus="dataFocus || null"
:data-blur="dataBlur || null"
:tabindex="tabIndex"
@change="emits('change', inputValue)"
@focus="emits('focus')"
@blur="emits('blur', inputValue)"
@input="emits('inputChange', inputValue)"
@keydown.enter.stop="emits('enter', inputValue); emits('validate', inputValue)"
/>
<p
v-if="isError && errorMsg"
class="text-sm text-red-800 dark:text-red-700"
>
{{ errorMsg }}
</p>
</div>
效果展示
总结
这个输入框组件展示了如何构建一个企业级的 Vue 组件:
- 类型安全
- 功能完整
- 样式灵活
- 交互友好
- 易于扩展
还有很多过程被我自动省略了: tailwind的引入配置、类型的设置、参数传递的过程、图标的配置导入,之后如果浏览量和需求上来的话,会做补充。可以作为开发类似组件的参考实现,其他实现或技术相关问题欢迎前往作者公众号交流~
完整源代码较长,可关注后联系作者获取(无套路,直接给)