报错代码
一般在mounted生命周期钩子中创建
ResizeObserver
实例
mounted(){
const container = this.$refs.container;
let containerObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
//使用$refs获取container的宽度并输出
console.log(this.$refs.container.clientWidth)
}
});
containerObserver.observe(container);
}
代码在第一次加载的时候没有任何问题,但是你修改代码并保存,在vue-cli会启动热重载,这时候大概率会出现一下报错:
Uncaught TypeError: Cannot read properties of undefined (reading ‘clientWidth’)
说找不到clientWidth这个属性,但是~~~报错的后面后紧接着出现了container的宽度值,也就是说代码运行是没有任何问题的,就是之只要保存一次,红色错误代码就会出现一遍,这样在开发中也是很烦躁的。
出现原因分析
如果是将代码改成
console.log(this.$refs.container)
你会发现,热重载之后,先会出现一个
undefine
,之后再出现vm的说有
$refs
。也就是说,我只用了一遍输出,但是却执行了两遍代码,斌且第一次是
undefine
,就是说某种dom元素还没有挂载的情况下执行了一遍,之后又执行了一遍…
一下方法是我所尝试的方法…
都无效!!
-
使用
$nextTick()
想到DOM找不到出现
undefine
,就想到使用
$nextTick()
在DOM元素都挂载的时候在执行函数。mounted(){ //添加了nextTick() this.nextTick(()=>{ const container = this.$refs.container; let containerObserver = new ResizeObserver((entries) => { for (let entry of entries) { //使用$refs获取container的宽度并输出 console.log(this.$refs.container.clientWidth) } }); containerObserver.observe(container); }) }
这个方法在热重载之后还是会出现问题,甚至多了一个nextTick的错误
-
创建实例时考虑到作用域问题,不适用let,使用var和this创建实例,
也还是不行
。var containerObserver = new resizeObserver(...) this.containerObserver = new resizeObserver(...)
-
判断
ResizeObserver
实例是否已经存在
如果执行了两次,是不是重载之后上一个
ResizeObserver
还遗留着被执行了??mounted(){ //添加了nextTick() if(!this.containerObserver){ this.nextTick(()=>{ const container = this.$refs.container; this.containerObserver = new ResizeObserver((entries) => { for (let entry of entries) { //使用$refs获取container的宽度并输出 console.log(this.$refs.container.clientWidth) } }); containerObserver.observe(container); }) } }
如果实例不存在,就执行。很可惜,还是不行。
问题解决
在折腾了很久之后…解决了,准确的说…AI解决了我的问题。
下面是我和
chatGPT
的几次对话:
-
问:
添加了
nextTick
之后还是有问题呢?
答:
可能是因为在组件的生命周期中,mounted钩子函数可能会被多次调用,导致多次创建Observer实例,进而造成了重复输出。你可以在mounted钩子函数中判断Observer实例是否已经被创建,如果已经被创建,就不再进行重复创建。
如果还有问题,你可以在beforeDestroy钩子函数中销毁Observer实例,来保证内存的释放。
没错~~它提到了
beforeDestroy
这个生命周期钩子(在组件销毁之前调用),在试了判断重复没用之后,就用了
beforeDestroy
,问题成功的解决了,如下:
mounted(){
//添加了nextTick()
const container = this.$refs.container;
this.containerObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
//使用$refs获取container的宽度并输出
console.log(this.$refs.container.clientWidth)
}
});
containerObserver.observe(container);
}
...
...
beforeDestroy(){
//使用disconnect()方法将containerObserver实例销毁
this.containerObserver.disconnect();
this.containerObserver = null;
}
具体的原因大致如下:
问:
为什么热重载的时候,mounted可能会被调用多次,导致ResizeObserver实例被调用了多次。
答:
热重载是在修改了代码之后,重新加载页面而不是重启整个应用,所以组件仍然会被保留在内存中,并且会继续执行它的生命周期钩子函数。所以如果你在mounted钩子函数中创建了Observer实例,那么在热重载的时候,这个钩子函数就会被调用多次,导致多次创建Observer实例。
总结
可能这个问题是一个简单的销毁实例就解决的问题,而且就算不解决也不影响代码的执行,但是红色的报错还是让我忍不住想去探究一下。作为刚接触vue不久的小白的我,虽然一直能大致知道问题的所在,但是却怎么也没想到需要在
beforeDestroy
中去解决。折腾了很久,另外也吐槽一下chatGPT,这个问题重复问了很多遍,把问题解决,但是还是感谢他hhh。
总之,虽然是小问题折腾了很久,但也从中学到了更多,更了解vue的dom渲染机制,以及生命周期函数等等…最后希望能帮助到有需要的人。