[踩坑] - 记录一个 Vue 组件封装与使用的坑点

630 阅读2分钟

首先,正如标题所说的那样,这是一篇记录 Vue.js 使用过程当中出现的一个骚问题的水文。

前提:

我司其中一个项目使用的技术栈是 2.x 版本的 Vue.js(但是其实 Vue 3.x 也是会有类似的问题,是一个封装逻辑问题)。其中有一个表单组件,这个表单组件大致主要是通过 value 和 emit input 事件实现 v-model 语法糖,并且在 value 修改时候会 emit change 通知外层,这个表单值有触发修改。

然后在别的地方有使用到该组件,使用 v-model 进行了表单值的双向数据绑定,然后对该表单组件进行 change 事件的监听,并在事件回调当中使用到对应的 v-model 双向绑定的数据值。

单纯描述来看好像没有什么问题?但是,我们接下来对封装的表单组件再进行仔细的参详一下这块:

  • 注:代码是经过精简处理;
<template>
  <input
    ref="input"
    :value="innerValue"
    :type="type"
    :disabled="disabled"
    :placeholder="placeholder"
    :name="name"
    @blur="onBlur"
    @input="onInput">
</template>

<script>
import { get } from 'lodash'

export default {
  name: 'ui-input',
  
  model: {
    prop: 'value',
    event: 'input',
  },
  
  props: {
    type: {
      type: String,
      default: 'text',
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    placeholder: {
      type: String,
      default: '',
    },
    name: {
      type: String,
      default: '',
    },
    value: {
      type: [String, Number],
      default: '',
    },
  },
  computed: {
    innerValue: {
      get() {
        return this.value
      },
      set(val) {
        this.onChange(val)
      },
    },
  },
  methods: {
    onChange(val) {
      this.$emit('change', val)
    },
    onBlur(event) {
      this.$emit('blur', event)
    },
    onInput(e) {
      this.onChange(e.target.value)
      this.$emit('input', e.target.value)
    },
  },
}
</script>

<style scoped>
/* .... */
</style>

开发经验较为丰富的开发工程师应该这时候就能从这段代码当中预感到这种表单组件封装存在的问题了(你是否已经想到了吗?)

是的,没错,问题就出在这个组件修改 value 时候抛出事件的顺序问题上面。

为什么说这里抛出事件的顺序是会存在问题呢?来看看下面这个使用这个双向数据绑定的使用例子应该就知道了。

<template>
  <div>
    <ui-input
      v-modal="inputModalValue"
      @change="handleInputChange"
    />
    <div> inputModalValue: {{ inputModalValue }} </div>
    <div> inputChangeValue: {{ inputChangeValue }} </div>
  </div>
</template>

<script>
  import { get } from 'lodash'

  export default {
    data() {
      return {
        inputModalValue: '',
        inputChangeValue: '',
      };
    },
    methods: {
      handleInputChange(val) {
        this.inputChangeValue = val;
      },
    },
  }
</script>

<style scoped>
  /* .... */
</style>
  • 解释一下代码的逻辑,我们通过这个 v-model 语法糖以及这个封装的 input 表单组件进行了一个响应式数据的双向数据绑定操作处理,并且监听了组件的 change 事件;
  • 其中监听事件当中通过 this 读取双向绑定的值,并使用该值做一些操作。

之所以说存在问题,是因为 v-model 只是个语法糖,其实本质还是 value 和 input 事件的组合使用,因此这种写法就会先触发了 change 事件再触发 input 双向数据绑定的逻辑更新 value 值,这就会导致在 @change 事件监听回调内的此时的双向绑定表单值并不是最新输入的值。

啊哈哈,这个 bug 还是有点隐晦的,不注意的话,像即时输入搜索这种场景就会因为获取到的表单值就不是最新输入关键字值;搜索的结果就自然不是想要的。