Vue: 深入理解 v-model 工作原理

Vue: 深入理解 v-model 工作原理

动态绑定 v-model

用过 vue 的人应该都知道 v-model 的用法,官方文档也告诉我们应该用 v-model 来绑定 inputtextarea 等标签的值。

然而当我们想要对 input 等输入控件进行封装的时候,就有必要学会如何在自定义组件上使用 v-model 来实现输入控件的绑定(也就是 React 官方文档中提到的 受控组件 的概念)

基础使用

首先我们先来看第一种,教程文档中教我们的用法

  • /src/components/BasicVModel.vue
<template>
  <div>
    <h1>Basic v-model Usage</h1>
    <input type="text" v-model="input" @input="onInput" @change="onChange" />
  </div>
</template>

<script>
export default {
  name: 'BasicVModel',
  data() {
    return {
      input: '',
    };
  },
  methods: {
    onInput(e) {
      console.log(`onInput: e.target.value=${e.target.value}`, e);
    },
    onChange(e) {
      console.log(`onChange: e.target.value=${e.target.value}`, e);
    },
  },
  watch: {
    input(input) {
      console.log(`[watch] input = ${input}`);
    },
  },
};
</script>

<style></style>
  • 运行截图

我们从输出截图可以看到,输入的过程中会不断触发 Input 事件,然后是 Vue 的 watch 钩子观测到 input 的修改后触发回调;最后在输入 Enter 或是取消 <input> 标签的焦点之后才会触发 change 事件

官方文档我们也能看到,实际上 Vue 利用的是 Input 事件,因此后续将不再讨论 Change 事件的产生

v-model 绑定自定义组件(推荐)

接下来我们要来介绍如何在自定义组件上使用 v-model,首先先给大家推荐标准做法

  • /src/components/CustomComponent.vue

首先我们的外部组件使用了一个自定义的 CustomComponentInput 组件,然后使用 v-model="input" 将数据绑定到自定义组件上

<template>
  <div>
    <h1>v-model on Custom Component</h1>
    <CustomComponentInput v-model="input" @input="onInput" />
  </div>
</template>

<script>
import CustomComponentInput from './CustomComponentInput.vue';

export default {
  components: { CustomComponentInput },
  data() {
    return {
      input: 'Hello',
    };
  },
  methods: {
    onInput(input) {
      console.log(`onInput: input = ${input}`);
    },
  },
  watch: {
    input(input) {
      console.log(`[watch] input = ${input}`);
    },
  },
  mounted() {
    console.log(`[mounted] input = ${this.input}`);
  },
};
</script>

<style></style>

接下来我们要怎么定义子组件来满足上述 v-model 的用法呢?

  • /src/components/CustomComponentInput.vue
<template>
  <input :value="value" @input="$emit('input', $event.target.value)" />
</template>

<script>
export default {
  name: 'CustomComponentInput',
  props: ['value'],
};
</script>

<style></style>

根据官方文档的描述,v-model 将主动利用组件的 prop: value 以及 Input 事件来满足 v-model 的绑定,而对于 Input 事件可以利用 Vue.prototype.$emit 来传递并对外抛出 Input 事件,同时我们的参数设置为 $event.target.value 来满足外面组件对于 value 的取值正确

  • 运行截图

观察上面运行截图可以看到,当我们在子组件正确的绑定 value 与 Input 事件时,外部组件就能正确的获取实时的 input 值(这里我们的外部组件将数据绑定在 input 数据字段上)

不使用 v-model 捕获组件 value 值

到这里其实就已经足够大家开发使用了,下面我们透过自定义联动 value 值的方式来尝试理解 Vue 的 v-model 进行双向绑定的逻辑

使用 input 事件

如果我们选择不使用 v-model 来绑定自定义组件数据的话,实际上我们还是可以透过 Input 事件获取数据修改的状态

  • /src/components/UnbindCustomComponent.vue
<template>
  <div>
    <h1>Unbind Custom Component</h1>
    <CustomComponentInput ref="input_ref" @input="onInput" />
  </div>
</template>

<script>
import CustomComponentInput from './CustomComponentInput.vue';
export default {
  components: { CustomComponentInput },
  methods: {
    onInput(input) {
      console.log(`onInput: input = ${input}`);
      console.log(`value = ${this.$refs.input_ref.value}`);
    },
  },
};
</script>

<style></style>
  • 运行截图

从上图能看到,Input 事件接到的 input 是正确的,然而使用 $refs 获取的组件 value 字段却是 undefined。实际上这是因为我们的输入框实际上将输入的字符串绑定在内部的 <input> 标签的 value 上,因此我们直接取组件的 value 当然是没东西的

绑定 value

那这时候如果我们在外部组件加一个 value 并使用 v-bind 绑定到子组件可不可行呢?

  • /src/components/UnbindCustomComponent.vue
<template>
  <div>
    <h1>Unbind Custom Component</h1>
    <CustomComponentInput ref="input_ref" :value="input" @input="onInput" />
  </div>
</template>

<script>
import CustomComponentInput from './CustomComponentInput.vue';
export default {
  components: { CustomComponentInput },
  data() {
    return {
      input: 'Init value',
    };
  },
  methods: {
    onInput(input) {
      console.group('onInput');
      console.log(`onInput: input = ${input}`);
      console.log(`this.input = ${this.input}`);
      console.log(`value = ${this.$refs.input_ref.value}`);
      console.groupEnd();
    },
  },
};
</script>

<style></style>
  • 运行截图

这时候我们可以看到,一开始组件的 value 确实与 input 字段相同,但是经过一些输入修改之后,组件的 value 属性依旧还是原本的值,并没有实现 v-model 的双向绑定效果。

这是因为这时候仅仅只是将 input 字段绑定到组件的 value 属性,但是却还是与内部的 <input> 标签是脱节的,也就是说是两份完全独立的数据字段

value 与 input 事件联动

这时候其实我们只需要在 Input 事件的回调,将新的数据值同步到 input 字段就可以了

  • /src/components/UnbindCustomComponent.vue
<!-- template ... -->

<script>
import CustomComponentInput from './CustomComponentInput.vue';
export default {
  // ...

  methods: {
    onInput(input) {
      // 同步到 input 字段上
      this.input = input;

      console.group('onInput');
      console.log(`onInput: input = ${input}`);
      console.log(`this.input = ${this.input}`);
      console.log(`value = ${this.$refs.input_ref.value}`);
      this.$nextTick(() => {
        console.log(`[$nextTick] value = ${this.$refs.input_ref.value}`);
        console.groupEnd();
      });
    },
  },
};
</script>
  • 运行截图

这时候我们就能看到,外部组件的 input 字段与自定义 Input 组件的 value 确实是与当前输入字符串同步了。

然而最后还有一个遗留问题是,由于我们利用 $refs 取出组件的 value 属性值,但是 onInput 回调却是发生在组件响应式更新之前,也就是说如上图 value 总会是"慢一拍"的感觉,这时候我们就可以利用 Vue.prototype.$nextTick,在 Vue 组件的下一跳中再去取组件的 value 就可以了

小结

本篇是作者在写 Vue 的时候遇到的疑问之一,由于作者相较之下更熟悉 React 的运行机制,回头来看 Vue 却是能够慢慢发觉更多内部机制运行规律,对于响应式的把握也更高一些。

其他资源

参考连接

TitleLink
自定义组件的 v-model - Vue 官方https://cn.vuejs.org/v2/guide/components-custom-events.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%84%E4%BB%B6%E7%9A%84-v-model

完整代码示例

https://github.com/superfreeeee/Blog-code/tree/main/front_end/vue/vue_vmodel_deep_dive

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值