在仿 ElementPlus 组件库的进程中,我们已成功完成 Dropdown 组件与 Message 组件的开发,如今,我们将探究如何实现一个功能强大、体验出色的 Input 组件,为组件库增添新的亮点。
一、什么是 Input 组件
Input 组件是用户向应用程序传递数据的主要途径。无论是简单的文本,还是复杂格式的数据,它都能精准收集,是应用获取用户指令的 "接收器"。
- 登录注册场景:在几乎所有应用的登录与注册页面,Input 组件用于输入账号、密码、邮箱等关键信息,保障用户身份验证与账户创建流程的顺利进行。
- 搜索功能实现:搜索栏中的 Input 组件允许用户输入关键词,应用以此为依据在海量数据中筛选出相关结果,极大提升了信息查找效率。
- 表单填写场景:在各类表单中,如问卷调查、信息登记等,Input 组件为用户提供了输入姓名、年龄、地址等多样化信息的空间,助力数据收集与业务流程推进。
二、实现 Input 组件
(一)组件目录
目录
components
├── Input
├── Input.vue
├── types.ts
├── style.css
(二)实现 Input 组件基本功能
- 实现 Input 组件的基本结构和样式绑定,能够根据不同的属性值和插槽内容动态渲染输入框。
- 支持普通输入框和文本域两种类型的输入,满足不同的输入需求。
- 提供多个插槽,允许用户在输入框的不同位置插入自定义内容。
types.ts
export interface InputProps {
type?: string;
size?: 'large' | 'small';
disabled?: boolean;
clearable?: boolean;
showPassword?: boolean;
}
Input.vue
<template>
<div
class="yl-input"
:class="{
[`yl-input--${type}`]: type,
[`yl-input--${size}`]: size,
'is-disabled': disabled,
'is-prepend': $slots.prepend,
'is-append': $slots.append,
'is-prefix': $slots.prefix,
'is-suffix': $slots.suffix,
}"
>
<!-- input -->
<template v-if="type != 'textarea'">
<!-- prepend slot -->
<div v-if="$slots.prepend" class="yl-input__prepend">
<slot name="prepend"></slot>
</div>
<div class="yl-input__wrapper">
<!-- prefix slot -->
<span v-if="$slots.prefix" class="yl-input__prefix">
<slot name="prefix"></slot>
</span>
<input class="yl-input__inner" :type="type" :disabled="disabled" />
<!-- suffix slot -->
<span v-if="$slots.suffix" class="yl-input__suffix">
<slot name="suffix"></slot>
</span>
</div>
<!-- append slot -->
<div v-if="$slots.append" class="yl-input__append">
<slot name="append"></slot>
</div>
</template>
<!-- textarea -->
<template v-else>
<textarea class="yl-textarea__wrapper" :disabled="disabled"></textarea>
</template>
</div>
</template>
<script setup lang="ts">
import type { InputProps } from './types'
defineOptions({
name: 'YlInput',
})
withDefaults(defineProps<InputProps>(), { type: 'text' })
</script>
App.vue
import Input from './components/Input/Input.vue'
添加 v-model
- 双向数据绑定实现 :通过
v-model
指令和相关事件、逻辑代码,实现 Input 组件与父组件之间的双向数据绑定。父组件可以通过v-model
绑定一个值到 Input 组件,Input 组件内部输入内容变化时,能实时更新父组件绑定的值;反之,父组件绑定值变化时,Input 组件的显示内容也能同步更新。 - 事件与数据流转管理 :明确组件的事件触发类型和数据流向。
update:modelValue
事件的定义和触发,使数据在子组件和父组件之间的传递更加清晰和可控。
types.ts
export interface InputProps {
//...
modelValue: string;
}
export interface InputEmits {
(e: 'update:modelValue', value: string) : void;
}
xml
<!-- prefix slot -->
//...
<input class="yl-input__inner" :type="type" :disabled="disabled" v-model="innerValue" @input="handleInput"/>
//...
<!-- textarea -->
<template v-else>
<textarea class="yl-textarea__wrapper" :disabled="disabled" v-model="innerValue" @input="handleInput"></textarea>
</template>
import { ref, watch } from 'vue'
import type { InputProps, InputEmits } from './types'
const props = withDefaults(defineProps<InputProps>(), { type: 'text' })
const emits = defineEmits<InputEmits>()
const innerValue = ref(props.modelValue)
const handleInput=()=>{
emits('update:modelValue',innerValue.value)
}
watch(
() => props.modelValue,
(newValue) => {
innerValue.value = newValue
},
)
实现点击清空功能
- 聚焦状态管理 :通过
isFocus
变量和handleFocus
、handleBlur
函数,实现对输入框聚焦和失焦状态的管理。这可以用于在输入框聚焦时显示特定的样式或功能。 - 清空图标显示控制 :使用
showClear
计算属性,根据输入框的可清空状态、禁用状态、输入内容和聚焦状态,动态控制清空图标的显示与隐藏。只有在满足特定条件时,清空图标才会显示,提高用户体验。 - 点击清空功能实现 :通过
clear
函数,当用户点击清空图标时,输入框的内容会被清空,并且会通知父组件更新绑定的值,实现了输入框内容的清空功能。
Input.vue
<template>
<div
class="yl-input"
:class="{
//...
'is-focus': isFocus
}"
>
//...
<!-- prefix slot -->
//...
<input
class="yl-input__inner"
//...
@focus="handleFocus"
@blur="handleBlur"
/>
<!-- suffix slot -->
<span v-if="$slots.suffix || showClear" class="yl-input__suffix">
<slot name="suffix"></slot>
<Icon icon="circle-xmark" v-if="showClear" class="yl-input__clear" @click="clear"></Icon>
</span>
//...
<!-- textarea -->
<template v-else>
<textarea
//...
@focus="handleFocus"
@blur="handleBlur"
></textarea>
import { ref, watch, computed } from 'vue'
import Icon from '../Icon/Icon.vue';
const isFocus = ref(false)
const showClear = computed(
() => props.clearable && !props.disabled && !!innerValue.value && isFocus.value,
)
const handleFocus = () => {
isFocus.value = true
}
const handleBlur = () => {
isFocus.value = false
}
const clear = () => {
innerValue.value = ''
emits('update:modelValue', '')
}
实现密码显示切换功能
- 密码显示状态切换 :通过
passwordVisible
变量和togglePasswordVisible
函数,实现密码输入框中密码的明文和密文显示状态的切换。用户点击相应的图标时,密码的显示状态会在明文和密文之间切换,方便用户查看输入的密码。 - 图标显示控制 :使用
showPasswordArea
计算属性,根据输入框的状态动态控制密码切换图标的显示与隐藏。只有在满足特定条件时,密码切换图标才会显示。 - 功能与属性关联 :将密码显示切换功能与组件的
showPassword
和disabled
属性关联起来,确保只有在开启该功能且输入框未禁用的情况下,才能使用密码显示切换功能。
Input.vue
<input
class="yl-input__inner"
:type="showPassword ? (passwordVisible ? 'text' : 'password') : type"
//...
/>
<!-- suffix slot -->
//...
<Icon
icon="eye"
v-if="showPasswordArea && passwordVisible"
class="yl-input__password" @click="togglePasswordVisible"
/>
<Icon
icon="eye-slash"
v-if="showPasswordArea && !passwordVisible"
class="yl-input__password" @click="togglePasswordVisible"
/>
const passwordVisible = ref(false)
const togglePasswordVisible = () => {
passwordVisible.value = !passwordVisible.value
}
const showPasswordArea = computed(() => props.showPassword && !props.disabled && !!innerValue.value)
添加对应的事件
- 事件类型定义完善 :在
InputEmits
接口中定义多种事件类型,为组件与父组件之间的交互提供了更丰富的事件通知机制,使得父组件可以根据不同的事件做出相应的处理。 - 事件触发功能增强 :在
Input.vue
中实现多个事件的触发逻辑,包括输入内容实时变化('input'
)、输入内容实质性变化('change'
)、输入框获得焦点('focus'
)、输入框失去焦点('blur'
)以及输入框内容清空('clear'
)等事件,让父组件能够更全面地监听和响应输入框的各种状态变化,提升了组件的交互性和可扩展性。 - 日志输出辅助调试 :在
handleBlur
和clear
函数中添加日志输出,方便开发人员在调试过程中确认事件是否被正确触发。
types.ts
export interface InputEmits {
//...
(e: 'input', value: string): void;
(e: 'change', value: string): void;
(e: 'focus', value: FocusEvent): void;
(e: 'blur', value: FocusEvent): void;
(e: 'clear'): void;
}
Input.vue
<input
class="yl-input__inner"
//...
@change="handleChange"
/>
<!-- textarea -->
<template v-else>
<textarea
//...
@change="handleChange"
></textarea>
const handleInput = () => {
emits('update:modelValue', innerValue.value)
emits('input', innerValue.value)
}
const handleChange = () => {
emits('change', innerValue.value)
}
const handleFocus = (event: FocusEvent) => {
isFocus.value = true
emits('focus', event)
}
const handleBlur = (event: FocusEvent) => {
console.log('blur triggered')
isFocus.value = false
emits('blur', event)
}
const clear = () => {
console.log('clear triggered')
innerValue.value = ''
emits('update:modelValue', '')
emits('clear')
emits('input', '')
emits('change', '')
}
添加原生属性支持
- 属性类型扩展 :在
InputProps
接口中新增多个属性定义,使得组件可以接收更多的配置选项。 - 原生属性支持 :通过
v-bind="attrs"
指令和手动绑定props
中的属性,组件现在可以支持所有原生 HTML 输入元素的属性,包括placeholder
、readonly
、autocomplete
等,方便开发者在使用组件时可以像使用原生输入元素一样进行配置。 - 焦点管理 :添加
keepFocus
函数和NOOP
函数,确保在点击后缀内容或清空图标时,输入框能够保持焦点。 - 组件引用暴露 :使用
defineExpose
暴露inputRef
,让父组件可以直接访问输入框元素的引用,方便进行一些特定的操作,如手动触发焦点、获取输入框的值等。
types.ts
export interface InputProps {
//...
placeholder?: string;
readonly?: boolean;
autocomplete?: string;
autofocus?: boolean;
form?: string;
}
Input.vue
<input
class="yl-input__inner"
//...
ref="inputRef"
v-bind="attrs"
:readonly="readonly"
:autocomplete="autocomplete"
:placeholder="placeholder"
:autofocus="autofocus"
:form="form"
/>
<!-- suffix slot -->
<span v-if="$slots.suffix || showClear || showPasswordArea" class="yl-input__suffix" @click="keepFocus">
<Icon icon="circle-xmark" v-if="showClear" class="yl-input__clear" @click="clear" @mousedown.prevent="NOOP"></Icon>
<!-- textarea -->
<template v-else>
<textarea
class="yl-textarea__wrapper"
//...
ref="inputRef"
v-bind="attrs"
:readonly="readonly"
:autocomplete="autocomplete"
:placeholder="placeholder"
:autofocus="autofocus"
:form="form"
></textarea>
import { ref, watch, computed, useAttrs, nextTick } from 'vue'
import type { Ref } from 'vue'
defineOptions({
name: 'YlInput',
inheritAttrs: false,
})
const props = withDefaults(defineProps<InputProps>(), { type: 'text', autocomplete: 'off' })
const attrs = useAttrs()
const inputRef = ref() as Ref<HTMLInputElement>
const NOOP = () => {}
const keepFocus = async () => {
await nextTick()
inputRef.value.focus()
}
defineExpose({
ref: inputRef
})