跳转到内容

输入框组件封装

刀刀

4/8/2025

0 字

0 分钟

    学习明灯
  • UP主:三十的前端课,视频地址:前往学习

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>
vue
<template>
  <div id="app">
    <MyInput v-model="value" />
    {{ value }}
  </div>
</template>

<script>
import MyInput from "@/components/MyInput.vue";
export default {
  data() {
    return {
      value: "",
    };
  },
  components: {
    MyInput,
  },
  methods: {},
};
</script>

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

注意

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

组件封装的思考

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

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

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

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

vue
<script>
export default {
  props: {
    type: {
      type: String,
      validator: function (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': true, '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': true, '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': true, 'wrap-position': searchAction }">
    <input :value="value" :type="type" @input="inputFn" />
  </div>
</template>

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

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

表单验证

总结

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

总体效果