说明:组件库以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修饰符,不再支持的原因是什么呢?
问题原因分析
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输入空格
实验结果
testInput直接修改数据
实验结果
原因分析
为什么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
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()
}