了解过vue的基本都知道vue的v-model是双向绑定,但不可避免我们会误解v-model是双向数据流,其实不然,vue是单项数据流的,这时候可能有点疑惑,那接下来我们来了解下双向绑定和单向数据流
双向绑定
首先我们看下什么是双向绑定?
简单来说,双向绑定就是model的更新会触发view的更新,view的更新会触发model更新
其实关键点在于data如何更新view,因为view更新data其实可以通过事件监听即可,比如input监听oninput
事件就可以实现了。那么当data改变,如何更新view的,其实就是通过Object.defineProperty()对属性设置一个set函数,当数据改变时就会触发set函数,所以我们只要将一些需要更新的方法放在set函数里就可以实现data更新view了。
Object.defineProperty
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,数据劫持主要通过Object.defineProperty来实现。
那么我们可以通过下面例子来验证下
代码:
1 | var vm = new Vue({ |
我们可以看到属性a有两个相对应的get和set方法,为什么会多出这两个方法呢?
因为vue是通过Object.defineProperty()来实现数据劫持的,不过在vue3.x使用Proxy来实现数据劫持,
Object.defineProperty()是用来做什么的?
它可以来控制一个对象属性的一些特有操作,比如读写权、是否可以枚举,这里我们主要先来研究下它对应的两个描述属性get和set,如果 还不熟悉其用法,查看这里:Object.defineProperty()使用。
在平常,我们很容易就可以打印出一个对象的属性数据:
1 | var Book = { |
如果想要在执行console.log(book.name)
的同时,直接给书名加个书名号,那要怎么处理呢?或者说要通过什么监听对象Book的属性值。这时候Object.defineProperty()
就派上用场了,代码如下:
1 | var Book = {} |
我们通过Object.defineProperty()
设置了对象Book的name属性,对其get和set进行重写操作,顾名思义,我们访问name属性时会自动执行get函数,设置name属性时,会自动执行 set 函数,所以当执行 Book.name = ‘vue权威指南’ 这个语句时,控制台会打印出 “你取了一个书名叫做vue权威指南”,紧接着,当读取这个属性时,就会输出 “《vue权威指南》”,因为我们在get函数里面对该值做了加工了。如果这个时候我们执行下下面的语句,控制台会输出什么?
1 | console.log(Book); |
通过打印结果可以看出,和上面例子结果相似,说明vue确实是通过这种方法来进行数据劫持的。
单向数据流
简而言之,单向数据流就是model的更新会触发view的更新,view的更新不会触发model的更新,它们的作用是单向的
其实所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
到这里是否存在一个疑惑,v-model不是双向绑定吗?,那接下来我们就来了解下v-model
v-model 用在 input 元素上
v-model在使用的时候很像双向绑定的,但是Vue是单项数据流,v-model 只是语法糖而已:
1 | <input v-model="something" /> |
第一行的代码其实只是第二行的语法糖。然后第二行代码还能简写成这样:
1 | <input :value="something" @input="something = $event.target.value" /> |
要理解这行代码,首先你要知道input
元素本身有个oninput
事件,这是HTML5
新增加的,类似onchange
,每当输入框内容发生变化,就会触发oninput
,通过$event
把最新的value
传递给something
。
我们仔细观察语法糖和原始语法那两行代码,可以得出一个结论:
在给 input 元素添加 v-model 属性时,默认会把 value 作为元素的属性,然后把input事件作为实时传递 value 的触发事件
语法糖是指在不影响功能的情况下 添加某种方法实现同样的效果 从而方便程序开发。
v-model 用在组件上
v-model 不仅仅能在 input 上用,在组件上也能使用,拿官网上的demo看。
1 | <currency-input v-model="price"></currency-input> |
如果你知道这两个问题的答案,那么恭喜你真正掌握了 v-model,如果你没明白,那么可以看下这段代码:
1 | <currency-input v-model="price"></currency-input> |
所以,给组件添加 v-model 属性时,默认会把 value 作为组件的属性,然后把 ‘input’ 值作为给组件绑定事件时的事件名。这在写组件时特别有用。
vue 组件数据流
从上面 v-model 的分析我们可以这么理解,双向数据绑定就是在单向绑定的基础上给可输入元素(input、textare等)添加了 change(input) 事件,来动态修改 model 和 view ,即通过触发($emit)父组件的事件来修改mv来达到 mvvm 的效果。而 vue 组件间传递数据是单向的,即数据总是由父组件传递到子组件,子组件在其内部可以有自己维护的数据,但它无权修改父组件传递给它的数据,当开发者尝试这样做的时候,vue 将会报错。这样做是为了组件间更好的解耦,在开发中可能有多个子组件依赖于父组件的某个数据,假如子组件可以修改父组件数据的话,一个子组件变化会引发所有依赖这个数据的子组件发生变化,所以 vue 不推荐子组件修改父组件的数据,直接修改 props 会抛出警告。流程图如下:
所以,当你想要在子组件去修改 props 时,把这个子组件当成父组件那样用,所以就有了
1、定义一个局部变量,并用 prop 的值初始化它。
2、定义一个计算属性,处理 prop 的值并返回。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。
有两种常见的试图改变一个 prop 的情形 :
这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。 在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:
1 | props: ['initialCounter'], |
这个 prop 以一种原始的值传入且需要进行转换。 在这种情况下,最好使用这个 prop 的值来定义一个计算属性
1 | props: ['size'], |