绑定的 value 值和元素的 value 值

今天在刷思否的时候遇到这样的一个问题:

<template>
  <input type="text" :value="inputValue" @input="handleInput" />
</template>

<script setup>
import { ref } from 'vue'
const inputValue = ref('')
const handleInput = (e) => {
  const val = e.target.value
  const reg = /^\d*$/
  if (reg.test(val)) {
    inputValue.value = val
  } else {
    inputValue.value = ''
  }
}
</script>

题主希望通过正则校验去限制输入框只能输入数字类型的值,但是只有在先输入数字再输入非数字的情况下才会被正确置空。如果一开始就是输入非数字的内容,后续即使继续输入非数字内容,输入框中的内容并不会清空。

我们可以在 Vue SFC Playground 中体验这个现象。

很明显问题是在给 inputValue 赋值为空之后出现了问题。所以我以为是更新时机导致的,就考虑使用强制更新看看。确实引入 $forceUpdate 之后问题解决了,但为什么强制该组件重新渲染才可以呢……

Vue 的仓库中有人发起过一个类似的问题讨论 👉 vue3中input的value和绑定的ref存在不同的问题 · vuejs/core · Discussion #7793
虽然问题描述稍有差别,但是本质是一样的,都是在函数处理中操作绑定的 inputValue 值。

只不过他的问题是没有修改绑定的 inputValue,在输入的内容长度超过 5 之后直接 return 掉了。但其实使用 slice() 截断了也是一样的结果的。
主要的原因出在了 inputValue 的值没有改变,值都没有改变 Vue 自然不会触发视图更新。

在 👆 上面的讨论贴中其他人解答了关于更新逻辑的解答:

chenxch Feb 26, 2023
你要知道这个整个更新逻辑
1、input输入了,domvlaue发生变化了
2、@input事件触发了
3、然后更新inputValue
4、当inputValue变化了,视图才会更新,inputvalue才会变成和inputValue一样

我们的问题也是类似的原因,在 inputValue 赋值为空之后,之后即使再有键入内容 inputValue一直都是空值,所以Vue并没有检测到改变,所以不会触发视图更新。所以使用强制更新就可以解决这个问题,但是我觉得这个解决方式并不合适。我们可以先给 inputValue 赋值,然后再做业务判断重新给赋值为空值。这样虽然修改了两次 inputValue 值,Vue 会合并两次操作为一次。


😕 那么为何React中的表现并不是这样?

这个情况会比较特殊,在Vue中和React中的表现并不一致:

  • Vue 中输入框的内容会随着用户键入内容被改变;
  • React 中输入框的内容并不会随着用户键入内容而改变。

例如以下的 🌰Demo,也可以通过在线Demo尝试(CodeSandbox)

import { useState } from 'react';
function MyInput() {
  const [value, setValue] = useState('')
  const handleInput = (e) => {
    const val = e.target.value
    const reg = /^\d*$/
    if (reg.test(val)) {
      setValue(val)
    } else {
      setValue('')
    }
  }
  return (
    <input value={value} onInput={handleInput} />
  )
}

export default function MyApp() {
  return (
    <MyInput />
  )
}

输入框的内容并不会随着用户键入内容而改变。会表现出类似 read-only 的状态,并不能继续输入错误内容。Vue 的表现有点类似于在 React 中使用 defaultvalue 的情况,但是又并不是完全相同。

我就很疑惑为什么会这样,怀疑 Vue 单独做了处理,但是在 Vue 文档中并没有找到关于这段的描写。转而怀疑是 React 做了处理,毕竟 defaultValue 是 React 提出的一个虚拟属性,随后就去翻了 React 的文档,就看到了这样一段提示:

Pitfall

If you pass value without onChange, it will be impossible to type into the input. When you control an input by passing some value to it, you force it to always have the value you passed. So if you pass a state variable as a value but forget to update that state variable synchronously during the onChange event handler, React will revert the input after every keystroke back to the value that you specified.

陷阱

如果传递了 value 但没有传递 onChange,那么将无法输入内容。当你通过传递 value 来控制输入框时,你需要保证输入框始终具有你传递的值。因此,如果你将一个 state 作为 value 传递,但在 onChange 事件处理程序中忘记同步更新该状态变量,React 将在每次输入后将输入框恢复到指定的 value

😂 所以确实是 React 它单独给给做了处理…… 并不是 Vue 表现得不一样,而是 React 不一样。Vue 维持了和原生HTML一样的情况。而 React 为了保持一致性操作了输入框的 Value 值。

并且如果你没有绑定 onInput 或者 onChange 事件,React 会在控制台抛出异常:

Warning: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.

React 引入了 受控组件非受控组件 的概念,而 Vue 并没有强调这个概念。但是大部分的UI库在实现表单组件的时候会通过受控组件来实现。比如说 Element UIInput 组件 的文档开头就说明了:

Input 为受控组件,它总会显示 Vue 绑定值

所以会出现我印象中某些情况下给输入框绑定了 value 值时会表现出只读的情况,有些时候又并不是这样。

至于受控组件和非受控组件就以后单独找一个时间来开坑了 😝


相关链接

vue3中input的value和绑定的ref存在不同的问题 · vuejs/core · Discussion #7793
#使用 state 控制输入框 | <input> – React
You Probably Don’t Need Derived State – React Blog