Skip to content

输入框组件封装

作者:Yuan Tang
更新于:5 个月前
字数统计:1.1k 字
阅读时长:4 分钟

v-model的本质

v-model 的本质上是 :value 渲染变量、@input 输入框修改事件触发后通过 $emit 把新的值传给父组件的语法糖。父组件通过 v-model 直接实现双向绑定。

子组件:

vue
<script>
export default {
  props: {
    value: String | Number,
  },
  methods: {
    inputFn(e) {
      this.$emit('input', e.target.value)
    },
  },
}
</script>

<template>
  <div>
    <input :value="value" @input="inputFn">
  </div>
</template>

注意

$emit 子传父中的事件名也要是 input

父组件:

vue
<script>
import MyInput from '@/components/MyInput.vue'

export default {
  components: {
    MyInput
  },
  data() {
    return {
      value: '',
    }
  },
  methods: {
  },
}
</script>

<template>
  <div id="app">
    <MyInput v-model="value" />
    {{ value }}
  </div>
</template>

此时运行代码,封装的输入框组件可以通过 v-model 双向绑定 value 变量。

组件封装的思考

输入框组件的封装需要考虑到以下几点:

  • 输入框类型
  • 类名
  • 最大输入的长度或数量
  • ......

其中,输入框类型我们是可以确定用户只能输入什么才能生效的,如用户输入 textnumberpassword 就可以生效,输入其他字符串如 abc 则无法生效,因此可以用到 props 的校验属性 validator

作为一个封装的组件,我们无法估量父组件在使用时会用到什么场景功能,因此,原生结构有啥方法我们就向外 $emit 什么方法,提供给父组件使用。

vue
<script>
export default {
  props: {
    type: {
      type: String,
      validator(value) {
        return ['text', 'textarea', 'number', 'password'].includes(value)
      },
    },
    value: String | Number,
  },
  methods: {
    inputFn(e) {
      this.$emit('input', e.target.value)
    },
    changeFn(e) {
      this.$emit('change', e.target.value)
    },
    focusFn(e) {
      this.$emit('focus', e.target.value)
    },
    blurFn(e) {
      this.$emit('blur', e.target.value)
    },
  },
}
</script>

<template>
  <div class="input-wrap" :class="{ 'wrap-position': searchAction }">
    <input :value="value" :type="type" @input="inputFn" @focus="focusFn" @blur="blurFn" @change="changeFn">
  </div>
</template>

模糊搜索

搜索

在用户输入后可实现搜索功能,作为一个组件,要考虑两种情况:

  1. 用户只传一个布尔值表示需要输入搜索,就直接返回一个 $emit 方法,让父组件处理(不简便,但是能应对特殊情况)
  2. 用户给入一个 url ,然后自动取请求 url 地址,拿到模糊查询结果显示(无法应对特殊调整)

通过传入一个变量 searchAction 来辨别,默认给一个 false

vue
<script>
// 1.只需要给如url,然后自动取请求url地址,拿到模糊查询结果显示(无法应对特殊调整)
// 2.如果searchAction不传url而是布尔值,则返回一个$emit方法,让父组件处理(不简便,但是能应对特殊情况)

export default {
  props: {
    // ...
    searchAction: {
      type: Boolean | String,
      default: false,
    },
  },
  methods: {
    inputFn(e) {
      this.$emit('input', e.target.value)
      if (this.searchAction) {
        // 模糊查询
        this.search(e.target.value)
      }
    },
    search(e) {
      if (typeof this.searchAction === 'string') {
        // 如果是字符串,则子组件直接调接口
        axios.get(`${this.searchAction}?v=${e}`).then((res) => {
          this.localSearchData = res.data
          this.$emit('search', res.data) // 考虑万一父组件要使用的特殊情况
        })
      }
      else {
        // 如果是布尔值,则父组件自行处理
        this.$emit('search')
      }
    },
  },
}
</script>

<template>
  <div class="input-wrap" :class="{ 'wrap-position': searchAction }">
    <input :value="value" :type="type" @input="inputFn">
  </div>
</template>

<style scoped>
.wrap-position {
  position: relative;
}
</style>

防抖与节流的区别

像这种输入就查询的频繁操作,要考虑到添加防抖或节流,降低服务器的压力,此场景最合适的还是节流。具体步骤如下:

  1. 外部定义一个变量 timer
  2. 触发输入查询事件后判断 timer 是否有值
  3. 有值清空定时器,并把新的定时器赋值给 timer
vue
<script>
// 1.只需要给如url,然后自动取请求url地址,拿到模糊查询结果显示(无法应对特殊调整)
// 2.如果searchAction不传url而是布尔值,则返回一个$emit方法,让父组件处理(不简便,但是能应对特殊情况)

let timer
export default {
  props: {
    // ...
  },
  methods: {
    inputFn(e) {
      this.$emit('input', e.target.value)
      if (this.searchAction) {
        // 模糊查询
        if (timer) {
          clearTimeout(timer)
          timer = setTimeout(() => {
            this.search(e.target.value)
          }, 100)
        }
      }
    },
    // ...
  },
}
</script>

<template>
  <div class="input-wrap" :class="{ 'wrap-position': searchAction }">
    <input :value="value" :type="type" @input="inputFn">
  </div>
</template>

<style scoped>
.wrap-position {
  position: relative;
}
</style>

后续把查询出来的值渲染到页面上或者子传父给父组件做处理即可。

表单验证

总结

  1. 组件封装最大程度要让父组件使用便利,内部最大程度做一定的封装
  2. 要适时给予一定的拓展功能,让父组件在使用时也能自定义其他功能来适配不同的场合

Contributors

Yuan Tang