数据更新了但视图未更新
vue3 解决数据更新而视图未更新问题
强制更新
js
import { getCurrentInstance } from 'vue';
setup(){
const instance = getCurrentInstance()
// 操作数据后更新视图
// instance.ctx.$forceUpdate();
instance.proxy.$forceUpdate()
return {
instance
}
}
通过 key 更新
html
<div :key="updateKey">{{data}}</div>
js
import { ref } from 'vue';
setup(){
const updateKey = ref(0);
// 操作数据后更新视图
updateKey.value += 1;
return {
updateKey
}
}
通过 v-if 让组件销毁之后重新创建
修改值之后将组件销毁,修改后下一次渲染将重新渲染
Vue2 数据更新了但页面没有更新的情况
vue 无法检测实例被创建时,不存在于 data 中的 property
原因: 由于 vue 会在初始化实例时,对 property 执行 getter/setter 转化,所以,property 必须在 data 对象上存在才能让 vue 将它转化为响应式的。
场景:
js
var vm = new Vue({
data: {},
// 页面不会变化
template: '<div>{{message}}</div>',
})
vm.message = 'Hello!' // `vm.message` 不是响应式的
解决办法:
js
var vm = new Vue({
data: {
// 声明为一个空值字符串
message: '',
},
template: '<div>{{ message }}</div>',
})
vm.message = 'Hello!'
vue 无法检测对象 property 的添加或移除
原因: 官方–由于 JavaScript(ES5)的限制,Vue.js 不能检测到对象属性的添加或删除。因为 vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 vue.js 转换它,才能让它使响应的。
场景:
js
var vm = new Vue({
data: {
obj: {
id: 001,
},
},
// 页面不会变化
template: '<div>{{ obj.message }}</div>',
})
vm.obj.message = 'hello' // 不是响应式的
delete vm.obj.id // 不是响应式的
解决办法:
js
//动态添加 -- vue.set
Vue.set(vm.obj, propertyName, newValue)
//动态添加 -- vm.$set
vm.$set(vm.obj, propertyName, newValue)
//动态添加多个
// 代替Object.assign(this.obj,{a:1,b:2})
this.obj = Object.assign({}, this.obj, { a: 1, b: 2 })
//动态移除--vm.$delect
Vue.delete(vm.obj, propertyName)
//动态移除 --vm.$delete
vm.$delete(vm.obj, propertyName)
vue 不能检测通过数组索引值,直接修改一个数组项
原因: 官方–由于 JavaScript 的限制,Vue 不能检测数组和对象的变化;性能代价和获得用户体验不成正比。
场景:
js
var vm = new Vue({
data: {
items: ['a', 'b', 'c'],
},
})
vm.items[1] = 'x' // 不是响应性的
解决办法:
js
//Vue.set
Vue.set(vm.items, indexOfItem, newValue)
//vm.$set
vm.$set(vm.items, indexOfItem, newValue)
//Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
扩展: Object.defineProperty()可以监测数组的变化
Object.defineProperty()可以监测到数组的变化。但对数组新增一个属性(index)不会监测到数据变化,因为无法监测到新增数组的小标(index),删除一个属性(index)也是。
场景:
js
var arr = [1, 2, 3, 4]
arr.forEach(function (item, index) {
Object.defineProperty(arr, index, {
set: function (value) {
console.log('触发 setter')
item = value
},
get: function () {
console.log('触发 getter')
return item
},
})
})
arr[1] = '123' // 触发 setter
arr[1] // 触发 getter 返回值为 "123"
arr[5] = 5 // 不会触发 setter 和 getter
vue 不能监测直接修改数组长度的变化
原因: 官方–由于 JavaScript 的限制,vue 不能检测数组和对象的变化;(性能代价和获得用户体验不成正比)。
场景:
js
var vm = new Vue({
data: {
items: ['a', 'b', 'c'],
},
})
vm.items.length = 2 // 不是响应性的
解决办法:
js
vm.items.splice(newLength)
在异步更新执行之前操作 DOM 数据不会变化
原因: Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,vue 将开启一个队列,并缓冲在同一个事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时,去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的时间循环"tick"中,vue 刷新队列并执行实际(已去重的)工作。vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn,0)代替。
场景:
html
<div id="example">{{message}}</div>
js
var vm = new Vue({
el: '#example',
data: {
message: '123',
},
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
vm.$el.style.color = 'red' // 页面没有变化
解决办法:
js
var vm = new Vue({
el: '#example',
data: {
message: '123',
},
})
vm.message = 'new message' // 更改数据
//使用Vue.nextTick(callback) callback将在DOM更新完成后被调用
Vue.nextTick(function () {
vm.$el.textContent === 'new message' //true
vm.$el.style.color = 'red' //文字颜色变成红色
})
扩展:异步更新带来的数据响应的误解
html
<!-- 页面显示:我更新啦! -->
<div id="example">{{message.text}}</div>
js
var vm = new Vue({
el: '#example',
data: {
message: {},
},
})
vm.$nextTick(function () {
this.message = {}
this.message.text = '我更新啦!'
})
上段代码中,我们在 data 对象中声明了一个 message 空对象,然后,在下次 DOM 更新循环结束之后,触发的异步回调中,执行了如下两段代码:
js
this.message = {}
this.message.text = '我更新啦!'
到这里,模板更新了,页面最后会显示我更新啦!。
模板更新了,应该具有响应式特性,如果这么想那么你就已经走入误区。 一开始,我们在 data 对象中,只是声明了一个 message 空对象,并不具有 text 属性,所以该 text 属性是不具有响应式特性的。
但模板切切实实已经更新了,这又是怎么回事呢? 那是因为 Vue.js 的 DOM 更新是异步的,即当 setter 操作发生后,指令并不会立马更新,指令的更新操作会有一个延迟,当指令更新执行的时候,此时 text 属性已经赋值,所以指令更新模板时,得到的是新值。
模板中每一个指令/数据绑定都有一个对应的 watcher 对象,在计算过程中,它把属性记录为依赖。之后当依赖的 setter 被调用时,会触发 watcher 重新计算,也就会导致它的关联指令更新 DOM。

具体流程如下所示:
- 执行
this.message = { };时,setter 被调用; - Vue.js 追踪到 message 依赖的 setter 被调用后,会触发 watcher 重新计算;
this.message.text = ‘我更新啦!’;对 text 属性进行赋值;- 异步回调逻辑执行结束之后,就会导致它的关联指令更新 DOM,指令更新开始执行。 所以,真正的触发模板更新的操作是
this.message = { };这一句引起的,因为触发了 setter,所以单看上述例子,具有响应式特性的数据只有 message 这一层,它的动态添加的属性是不具备的。
循环嵌套层级太深,视图不更新
js
vm.$forceUpdate()
扩展:路由参数变化时,页面不更新(数据不更新)
扩展一个因为路由参数变化,而导致页面不更新的问题,页面不更新本质上就是数据没有更新。
原因:路由视图组件引用了相同组件时,当路由参数变化时,会导致该组件无法更新,也就是我们常说的页面无法更新的问题。
场景:
html
<div id="app">
<ul>
<li><router-link to="/home/foo">To Foo</router-link></li>
<li><router-link to="/home/baz">To Baz</router-link></li>
<li><router-link to="/home/bar">To Bar</router-link></li>
</ul>
<router-view></router-view>
</div>
js
const Home = {
template: `<div>{{message}}</div>`,
data() {
return {
message: this.$route.params.name,
}
},
}
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/home', component: Home },
{ path: '/home/:name', component: Home },
],
})
new Vue({
el: '#app',
router,
})
我们在路由构建选项 routes 中配置了一个动态路由’/home/:name’,他们共用一个路由组件 Home,这代表他们复用 RouterView。 当进行路由切换时,页面只会渲染第一次路由匹配到的参数,之后再进行路由切换时,message 是没有变化的。
解决办法:
方式一:通过 watch 监听$route 的变化
js
const Home = {
template: `<div>{{message}}</div>`,
data() {
return {
message: this.$route.params.name,
}
},
watch: {
$route: function () {
this.message = this.$route.params.name
},
},
}
new Vue({
el: '#app',
router,
})
给 router-view 绑定 key 属性,这样 vue 就会认为这是不同的。
弊端:如果从/home 跳转到/user 等其他路由下,我们是不用担心组件更新问题的,所以这个时候 key 属性是多余的。
html
<div id="app">
...
<router-view :key="key"></router-view>
</div>