vue.js是以数据驱动和组件化的思想构建的,相比于其他库,Vue.js提供了更加简洁、更易于理解的API,使得我们能够快上手,所以获得更多开发者的青睐,但也踩了不少的坑~~
vue双向数据绑定原理
首先,我们先看官方是怎么解释vue双向数据绑定的
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data
选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty
把这些属性全部转为 getter/setter。Object.defineProperty
是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在属性被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
我们可以看出vue的双向数据绑定与Aangular双向数据绑定不同,data中的属性必须在vue实例化之前就该存在,因为vue实例化时对属性执行getter/setter处理,才能正确的进行转换,所以vue不能检测到实例化后data属性的添加和修改。
Vue.nextTick([callback,context])
在一个组件实例中,只有在data里初始化的数据才是响应的,vue不能检测到对象属性的添加或删除,没有在data里声明的属性不是响应的
一个回调函数,怎么回事呢?原来是在下次 DOM 更新循环结束之后执行延迟回调,也就是说在修改数据属性之后调用这个函数,能获取到更新好的DOM。开始做点小测试
模板
<template>
<div id="nexttick_demo">
<div ref="aa">{{message.a}}</div>
<div ref="bb">{{message.b}}</div>
<div ref="cc">{{message.c}}</div>
<button @click="msgupdate()">更新</button>
</div>
</template>
代码块
export default{
name:"nexttick_demo",
data(){
return {
message:{
a:"塞纳河畔 左岸的咖啡",
b:"",
c:""
}
}
},
created(){
},
methods:{
msgupdate:function(){
this.message.a = "我手一杯 品尝你的美";
this.$nextTick(()=>{
this.message.b = this.$refs.aa.innerHTML;
})
this.message.c = this.$refs.aa.innerHTML;
console.log(this.message);
}
}
}
运行前
运行后
message.b渲染为message.a的内容,而message.c的内容还是原始的数据,说明Vue中DOM更新是异步的
官方文档给出的解释是
Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0)代替。
Vue.nextTick用于延迟执行一段代码,它接受2个参数(回调函数和执行回调函数的上下文环境),如果没有提供回调函数,那么将返回promise对象。
因此,在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,我们都知道,mounted()钩子函数执行时所有的DOM挂载和渲染都已完成,在此函数中进行任何DOM操作都不会有问题
Vue.set()
向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 ,前面提到,只有在data里初始化的数据才是响应的
官方文档中特别强调
注意对象不能是Vue实例,或者Vue实例的根数据对象。
什么意思呢?就是Vue不允许在已经实例化的组件上添加新的动态根级响应属性(即直接挂载在data下的属性),但是可以使用$set方法将相应属性添加到嵌套的对象上
将上面的代码稍作修改
模板
<template>
<div id="nexttick_demo">
<div ref="aa">{{message.a}}</div>
<div ref="bb">{{message.b}}</div>
<button @click="msgupdate()">更新</button>
</div>
</template>
代码块
export default{
name:"nexttick_demo",
data(){
return {
message:{
a:"塞纳河畔 左岸的咖啡"
}
}
},
created(){
},
methods:{
msgupdate:function(){
this.message.b = "我手一杯 品尝你的美";
}
}
}
运行后结果
可以看到,message确实改变了,但是页面没有变化,再用vue.set(),模板不变,修改代码如下
export default{
name:"nexttick_demo",
data(){
return {
message:{
a:"塞纳河畔 左岸的咖啡"
}
}
},
created(){
},
methods:{
msgupdate:function(){
this.$set(this.message,'b',"我手一杯 品尝你的美");
// vm.$set()实例方法是Vue.set()全局方法的别名
}
}
}
运行后
Vue.set(object,key,value)方法一次只能添加一个属性,如果需要向嵌套对象上添加多个属性,可以用Object.assign或_.extend(),但是需要创建同时包含原属性、新属性的对象,从而有效触发watch()方法
export default{
name:"nexttick_demo",
data(){
return {
message:{
a:"塞纳河畔 左岸的咖啡"
}
}
},
created(){
},
methods:{
msgupdate:function(){
this.message = Object.assign({},this.message,{b:'我手一杯 品尝你的美',c:'留下唇印的嘴'})
}
}
}
结果
this.$set补充
往响应式对象this.$data中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 .
data () {
return {
user: {
name: '',
id: ''
}
}
}
如果直接给user增加属性
this.user.age = 30
虽然user的属性值age的值为30,但是页面上的视图是不发生任何刷新的,只能展示name/id的值,age值还是为空。
原因:由于Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。
此时要解决上述问题,给对象添加属性的时候,页面视图跟着刷新,可以用set的方法:
this.$set(this.user, "age", 30)
我在项目中遇到问题是数组的每一项都要动态添加响应式属性,我这样写也是有效果的:
for (let plan of this.formPayment.planList) {
this.$set(plan, 'socialInsuranceRanges', [{value: '1', label: '有', code: '', price: ''}, {value: '2', label: '无', code: '', price: ''}]);
}
还有一种情况是给已有对象添加多个属性,可能会用到Object.assgin(),这种情况,应该用两个对象创建一个新对象
// 不要这样写:
Object.assign( this.user, {
tel: 18888888888,
sex: 'Y'
})
// 而应该这样写:
this.user = Object.assign( {}, this.user, {
tel: 18888888888,
sex: 'Y'
})
修改数组对象的某一项
this.$set(this.arr, index, this.arr[index]);
本文转载自https://www.jianshu.com/p/e1e92965d4fe
https://www.jianshu.com/p/e24292fa6ec7