仿 ElementPlus 组件库(八)—— Input 组件实现

在仿 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变量和handleFocushandleBlur函数,实现对输入框聚焦和失焦状态的管理。这可以用于在输入框聚焦时显示特定的样式或功能。
  • 清空图标显示控制 :使用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', '')
}

实现密码显示切换功能

  1. 密码显示状态切换 :通过 passwordVisible 变量和 togglePasswordVisible 函数,实现密码输入框中密码的明文和密文显示状态的切换。用户点击相应的图标时,密码的显示状态会在明文和密文之间切换,方便用户查看输入的密码。
  2. 图标显示控制 :使用 showPasswordArea 计算属性,根据输入框的状态动态控制密码切换图标的显示与隐藏。只有在满足特定条件时,密码切换图标才会显示。
  3. 功能与属性关联 :将密码显示切换功能与组件的 showPassworddisabled 属性关联起来,确保只有在开启该功能且输入框未禁用的情况下,才能使用密码显示切换功能。
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')等事件,让父组件能够更全面地监听和响应输入框的各种状态变化,提升了组件的交互性和可扩展性。
  • 日志输出辅助调试 :在 handleBlurclear 函数中添加日志输出,方便开发人员在调试过程中确认事件是否被正确触发。
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 输入元素的属性,包括 placeholderreadonlyautocomplete 等,方便开发者在使用组件时可以像使用原生输入元素一样进行配置。
  • 焦点管理 :添加 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
})
相关推荐
好_快41 分钟前
Lodash源码阅读-getSymbols
前端·javascript·源码阅读
好_快1 小时前
Lodash源码阅读-keys
前端·javascript·源码阅读
亿牛云爬虫专家1 小时前
Headless Chrome 优化:减少内存占用与提速技巧
前端·chrome·内存·爬虫代理·代理ip·headless·大规模数据采集
好_快1 小时前
Lodash源码阅读-arrayFilter
前端·javascript·源码阅读
若云止水7 小时前
ngx_conf_handler - root html
服务器·前端·算法
佚明zj7 小时前
【C++】内存模型分析
开发语言·前端·javascript
知否技术8 小时前
ES6 都用 3 年了,2024 新特性你敢不看?
前端·javascript
最初@9 小时前
el-table + el-pagination 前端实现分页操作
前端·javascript·vue.js·ajax·html
知否技术10 小时前
JavaScript中的闭包真的过时了?其实Vue和React中都有用到!
前端·javascript
Bruce_Liuxiaowei10 小时前
基于Flask的防火墙知识库Web应用技术解析
前端·python·flask