Vue 的 v-model 实现双向绑定

477 阅读3分钟

Vue 是不是双向绑定

首先先要来明确一个问题,Vue 这个框架是不是一种双向数据绑定的框架模型?答案是否定的,Vue 是一种单向数据流的框架。

为了理解 vue 为什么是单向数据流,就需要提到 prop 属性,prop 也就是父组件传递给子组件的数据,数据总是从父组件传到子组件,子组件没有权利修改父组件传过来的数据,也就是说父组件传递过来的数据是只读的。如果我们试图通过子组件去改变这个prop,也就是通过子组件直接去改变父组件的数据,程序不会执行且 vue 会抛出一个警告⚠。但日常需求中确实需要子组件去改变prop,那么应该通过子组件向父组件发送请求的方式,间接达成目的,曲线救国。这样的数据传递方式,决定了 Vue 是单向数据流,不是一种双向数据绑定的框架类型。

那么为什么不直接做成双向数据绑定的模式呢?这是因为双向绑定很容易引起数据流的混乱,不利于管理,也难以保证数据的安全性。可以设想一下常见情景,在开发中如果有多个子组件依赖与父组件的某个数据,万一子组件可以直接修改父组件的数据,那么一个子组件的变化将会引发所有依赖于这个数据的子组件的变化,所以vue不支持子组件直接修改父组件的数据。

v-mode 实现双向绑定

双向数据绑定很多时候确实是我们的需求,它在很多情况下对数据的处理非常便利,也是 Angular 框架最核心的特色机制。显然 Vue 也学习了 Angular 的优点,也可以在某种程度上实现双向绑定,那么具体 Vue 是如何实现呢,这就轮到 v-mode 登场了。

v-model 虽然很像双向数据绑定框架 Angular 的 ng-model,但是 Vue 是单项数据流,v-model 从本质上来说只是一种语法糖而已。

创建支持 v-model 的输入组件如input。形如:

<input v-model="searchText">

它就等价于:

<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>

所以第一种v-mode的写法就是下面一种写法的语法糖,这里的 searchText 就是传递给子组件的数据。 要理解这行代码,首先你要知道 input 元素本身有个 oninput 事件,这是 HTML5 新增的,类似 onchange ,每当输入框内容发生变化时,就会触发oninput,传递最新的 value 给 searchText。

为了进一步理解 v-mode 的工作原理,可以看另一种更普适情况下的 v-mode 用法,vue 中我们会大量使用自定义子组件。

当用在自定义组件上时,v-model 则会类似这样:

<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event">
</custom-input>

组件内的定义要如下:

Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)">
  `
})

给组件添加 v-model 属性时,默认会把 value 作为组件的属性,然后把 'input' 值作为给组件绑定事件时的事件名。 v-mode 实现的并不是真正的双向绑定,子组件实际上只能发送请求父组件对原始数据的修改,也就是说数据的流动还是单向的。

v-model 的缺点

v-model 给我们提供好了 value 属性和 oninput 事件,但是,如果我们需要的不是 value 属性,而是 其他属性,并且当你点击这个子元素的时候不会触发 oninput 事件,而是触发 onclick 事件等情况。v-mode就无法使用了。不过既然我们已经理解了 v-model 的语法糖本质,不使用语法糖直接写代码实现类似功能即可。

<Child
  :count="number"
  @change="number = $event">
</Child>

相应组件内的定义如下:

Vue.component('Child', {
  props: ['count'],
  template: `
    <div
      :count="count"
      @click="$emit('change', number1)">{{count}}</div>
  `
})