vue+组件库(elementUI、antd)使用v-model.trim后无法输入空格问题思考

1,550 阅读2分钟

说明:组件库以elementUI为例,elementUI使用vue框架;antd+vue存在相同问题。

标签:elementUI源码 el-input v-model

需求描述

单行文本框输入时,去除前后两端空格;输入时展示空格,输入完成移除两端空格。

使用场景

在进行搜索时,我们认为两端的空格是无意义的搜索内容。比如搜索"   a b "时,我们期望处理成“a b”。

el-input的.trim表现与原生input不一致

<el-input v-model.trim="input"></el-input>

添加.trim修饰符后,我们无法直接输入空格。如需添加空格,需要先完整输入内容,如myhairisblack,再回头添加空格成my hair is black。这在英文环境下是十分不友好的,且不满足需求。

<input type="test" v-model.trim="input" >

原生input.trim修饰符在input聚焦时正常输入,如“ a b ”,在失焦时input框内内容变为“a b”,是满足需求的。

问题描述

那为什么el-input使用.trim与原生input.trim表现不一致呢?

element-UI直接在官网提示input不再支持v-model修饰符,不再支持的原因是什么呢?

image-20230224142734996.png

问题原因分析

demo

  • 流程过长,图片可能不清晰,建议下载查看
  • 已提供模拟el-input此部分的demo,建议下载自测

源码

index.vue
<template>
  <div>
    testInput: <TestInput v-model.trim="value" @input="handleChange" />
    <br />
    testInput @change: <TestInput v-model="value4" @change="handleChange4" />
    <br />
    input: <input v-model.trim="value2" @input="handleChange2" />
    <br />
    ref input: <input ref="input3" @input="handleChange3" />
    <br />
    <button @click="handleClick">change value</button>
    <br />
  </div>
</template>
<script>
import TestInput from './testInput.vue';
export default {
  components: {
    TestInput
  },
  data() {
    return {
      value: '',
      value2: '',
      value4: ''
    }
  },
  watch: {
    value() {
      console.log("父组件监听到value变化", this.value, this.value.length)
    }
  },
  methods: {
    handleChange(value) {
      console.log("父组件:", value, value.length)
    },
    handleChange2({target}) {
      console.log("原生input:", target.value, this.value2, target.value.length, this.value2.length)
    },
    handleClick() {
      this.value = "6";
    },
    handleChange3({target}) {
      console.log("ref修改值")
      this.$refs.input3.value=target.value.trim();
    },
    handleChange4(value) {
      this.value4 = value.trim()
    }
  }
};
</script>
testInput.vue
<!-- 模拟el-input -->
<template>
  <input ref="input" @input="handleInput" @change="handleChange" />
</template>
<script>
import TestInput from "./testInput.vue";
export default {
  props: {
    value: [String, Number],
  },
  computed: {
    nativeInputValue() {
      console.log("testInput监听value变化: ", this.value)
      return this.value === null || this.value === undefined
        ? ""
        : String(this.value);
    },
  },
  watch: {
    nativeInputValue() {
      this.setNativeInputValue("nativeInputValue");
    },
  },
  methods: {
    setNativeInputValue(who) {
      console.log("setNativeInputValue触发人", who)
      const input = this.getInput();
      if (!input) return;
      console.log("nativeInputValue与input.value对比", this.nativeInputValue, input.value, this.nativeInputValue.length, input.value.length)
      if (input.value === this.nativeInputValue) return;
      console.log("testInput即将设置input.value");
      // debugger;
      input.value = this.nativeInputValue;
    },
    getInput() {
      return this.$refs.input || this.$refs.textarea;
    },
    handleInput(event) {
      if (this.isComposing) return;
      console.log("判断原始值是否与nativeInputValue相等", event.target.value === this.nativeInputValue, event.target.value, this.nativeInputValue)
      if (event.target.value === this.nativeInputValue) return;
      this.$emit("input", event.target.value);
      this.$nextTick(() => this.setNativeInputValue("handleInput"));
    },
    handleChange(event) {
      this.$emit("change", event.target.value);
    }
  },
};
</script>

testInput页面输入2

testInput页面输入2.png

实验结果

image-20230227182140873.png

testInput输入空格

testInput输入空格.png

实验结果

image-20230227182302856.png

testInput直接修改数据

testInput直接修改数据.png

实验结果

image-20230227182355834.png

原因分析

为什么el-input使用.trim与原生input.trim表现不一致呢?

el-input使用this.$refs.input.value=nativeInputValue方式更新节点,即nativeInputValue变动后拿到dom节点修改dom节点的值。

原始input使用v-model.trim更新值,v-model在blur触发更新。 即ref立即更新dom节点,v-model.trim失焦时更新节点。

v-model源码
// vue源码(v2.7.14)
// src\platforms\web\compiler\directives\model.ts
genDefaultModel() {
    const event = lazy ? 'change' : type === 'range' ? RANGE_TOKEN : 'input'
    ...
    if (trim) {
      valueExpression = `$event.target.value.trim()`
    }
    let code = genAssignmentCode(value, valueExpression)
    ...
    addHandler(el, event, code, null, true)
    if (trim || number) {
      addHandler(el, 'blur', '$forceUpdate()') // 失焦时强制更新节点
    }
}
element-UI直接在官网提示input不再支持v-model修饰符,不再支持的原因是什么呢?

即为什么在el-input中不使用v-model绑定原始input组件而使用ref方式更新?

个人理解:如果el-input中的input使用v-model绑定而非ref会导致:输入状态时,更新DOM后输入内容被重置。重置因为:DOM已更新,状态已失效,v-model的相关updateDOM事件没有触发过。由此得,为保证输入状态时,更新DOM后输入内容被渲染,需要使用ref立即更新dom节点。

关联issue: 14521

image-20230228122429516.png

issue问题解决方案
// el-input组件源码(v2.14.1):
// element-ui/blob/dev/packages/input/src/input.vue
watch: {
    // native input value is set explicitly
    // do not use v-model / :value in template
    // see: https://github.com/ElemeFE/element/issues/14521
    nativeInputValue() {
      this.setNativeInputValue();
    }
}

由此知道矛盾不可解,所以无法支持修饰符。

解决方案

使用组件库时,修改触发时机,在组件失焦时触发.trim()方法

<TestInput v-model="value4" @change="handleChange4" />

handleChange4(value) {
	this.value4 = value.trim()
}

参考资料

elementUI input

什么是组件库