$nextTick、$forceUpdate和$set
$nextTick
官方解释: 将回调延迟到下次 DOM 更新循环之后执行
要理解这句话,首先要了解一下vue的异步更新队列,Vue 异步执行 DOM 更新。只要观察到数据变化,不会立即更新DOM,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 数据被的多次 改变,只会被推入到队列中一次。例如,当你设置 vm.someData = ‘new value’ ,对应的DOM更新会被推到一个队列里,该组件不会立即重新渲染,会在当前tick完毕后,在下一个tick中渲染DOM。在事件循环中,每进行一次循环操作称为tick。而nextTick函数就是vue提供的一个实例方法,数据更新后等待下一个tick里Dom更新完后执行回调,回调的 this 自动绑定到调用它的实例上。
html:
<span class="test">{{egData}}</span>
<el-button @click="changeData">改变</el-button>
js:
new Vue({
data () {
return {
egData: 'old Message'
}
}
methods: {
changeData () {
this.egData = 'new Message'
console.log($('.test').html(), '-----------------------')
}
}
})
结果: 第一次点击输出 old Message -----------------------,第二次点击输出 new Message -----------------------
使用$nextTick::
js:
new Vue({
data () {
return {
egData: 'old Message'
}
}
methods: {
changeData () {
this.egData = 'new Message'
this.$nextTick(function () {
console.log($('.test').html(), '-----------------------')
})
}
}
})
结果:不管第几次点击,都输出 new Message -----------------------
$nextTick使用场景:
1、数据更新后想要马上操作新的DOM,需要把操作写在nextTick的回调里
2、在created钩子函数里需要操作DOM,也可以把操作写在nextTick的回调里,(created钩子函数里还没有挂载dom,所以直接操作会有问题)
$forceUpdate
迫使Vue实例重新(rander)渲染虚拟DOM,注意并不是重新加载组件。结合vue的生命周期,调用
f
o
r
c
e
U
p
d
a
t
e
后只会触发
b
e
f
o
r
e
U
p
d
a
t
e
和
u
p
d
a
t
e
d
这两个钩子函数,不会触发其他的钩子函数。它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
∗
∗
forceUpdate后只会触发beforeUpdate和updated这两个钩子函数,不会触发其他的钩子函数。它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。 **
forceUpdate后只会触发beforeUpdate和updated这两个钩子函数,不会触发其他的钩子函数。它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。∗∗forceUpdate()使用场景:**
1、当在data里没有显示的声明一个对象的属性,而是之后给该对象添加属性,这种情况vue是检测不到数据变化的,可以使用$forceUpdate()
html:
<span class="test">{{egData.value}}</span>
<el-button @click="changeData">改变</el-button>
js:
egData: {}
...
changeData () {
this.egData.value = 'oldValue'
this.$forceUpdate() // dom会更新
}
但是这种做法并不推荐,官方说如果你现在的场景需要用forceUpdate方法 ,那么99%是你的操作有问题,如上data里不显示声明对象的属性,之后添加属性时正确的做法时用 vm.$set() 方法,所以forceUpdate请慎用
$set
set 方法主要作用是向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,对象不能是 Vue 实例,或者 Vue 实例的根数据对象
Vue.set( target, key, value ) / this.$set( target, key, value )
target:要更改的数据源(可以是对象或者数组)
key:要更改的具体数据,或者新增的属性名
value :重新赋的值
change: function(index) {//增加性别属性
this.$set(this.list[index],'sex','男')
},
clear: function() {//清空数组
this.list=[];
}
Vue $set()方法实现原理:
首先 set 方法会对参数中的 target 进行类型判断
1、如果是 undefined 、null 、基本数据类型,直接报错。
2、如果为数组,取当前数组长度与 key 这两者的最大值作为数组的新长度,然后使用数组的 splice 方法将传入的索引 key 对应的 val 值添加进数组。target 在 observe 的时候,原型链被修改了, splice 方法也已经被重写了,触发之后会再次遍历数组,进行数据劫持,也就是说当使用 splice 方法向数组内添加元素时,该元素会自动被变成响应式的
3、如果为对象,会先判断 key 值是否存在于对象中,如果在,则直接替换 value。如果不在,就判断 target 是不是响应式对象(其实就是判断它是否有 ob 属性),接着判断如果它是不是 Vue 实例,或者是 Vue 实例的根数据对象,如果是则抛出警告并退出程序。如果 target 不是响应式对象,就直接给 target 的 key 赋值,如果 target 是响应式对象,就调用 defineReactive 将新属性的值添加到 target 上,并进行依赖收集,更新视频
function set(target: Array<any> | Object, key: any, val: any): any {
// isUndef 是判断 target 是不是等于 undefined 或者 null 。
// isPrimitive 是判断 target 的数据类型是不是 string、number、symbol、boolean 中的一种
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 数组的处理
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
// 对象,并且该属性原来已存在于对象中,则直接更新
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
// vue给响应式对象(比如 data 里定义的对象)都加了一个 __ob__ 属性,
// 如果一个对象有这个 __ob__ 属性,那么就说明这个对象是响应式对象,修改对象已有属性的时候就会触发页面渲染
// 非 data 里定义的就不是响应式对象。
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
// 不是响应式对象
if (!ob) {
target[key] = val
return val
}
// 是响应式对象,进行依赖收集
defineReactive(ob.value, key, val)
// 触发更新视图
ob.dep.notify()
return val
}
PS:
使用forceUpdate会有一些问题:
vue 使用v-if后 或 使用$forceUpdate()强制数据刷新之后导致表单rules校验失效问题
1、使用v-if失效
【1】使用 v-if:在对form表单中带有prop属性的子组件进行校验规则绑定时,是在vue声明周期mounted完成的。而v-if用来切换的元素是会被销毁的,导致了v-if内的表单项,由于在mounted时期没有进行渲染,所以规则也没有绑定上,因此初始化时不符合显示条件的不会生成规则,导致后面切换条件,显示的输入框的校验不会生效。
【2】使用 v-show:初始化时会生成所有的规则,即使隐藏了也会进行规则校验。
解决:在给设置v-if的el-form-item上添加一个key
2. 使用forceUpdate失效
forceUpdate一般使用在编辑表单时输入框无法输入值的时候用来强制刷新数据的,但用forceUpdate后可能会导致表单rules校验失效,那么可以$set来代替forceUpdate,如图所示:
// 为解决input框不能输入的问题
change(e, typeName) {
// console.log(typeName)// 获取的 name
// console.log(e);//获取的 id
// this.$forceUpdate(); // 强制数据刷新之后, 验证失效, 改用 $set
this.$delete(this.baseData, typeName) // set之前先删除
this.$set(this.baseData, typeName, e)
},