vue 中 setInterval 创建和销毁

问题

setInterval 是间隔调用,与之类似的还有 setTimeout。这两个 API 通常用来做 ajax 短连接轮询数据。

比如有一个 logs.vue 是用来展示某个正在执行的进程产生的日志:

<template>
	<div>
		<p v-for="item in logList" :key="item.time">
			<span>{{"[" + item.time + "]"}}</span>
			<span>{{ item.log }}</span>
		</p>
	</div>
</template>
<script>
	import { Component, Vue, Watch, Prop, Emit } from 'vue-property-decorator'
	import { getLogList } from './api'
	@Component({})
	export default class extends Vue {
		logList = []
		timer = null
		mounted(){
			this.getData()
		}
		async getData(){
			let r = await getLogList()
			if(r && r.logList){
				this.logList = r.logList
			}
			this.timer = setTimeout(()=>{
				console.log(this.timer);
				this.getData()
			}, 1000)
		}
		beforeDestory(){
			clearTimeout(this.timer)
			this.timer = null;
		}
	}
</script>

这段代码看上去没啥问题,但是测试的时候你会发现,有时候路由已经跳转了,获取进程日志的接口依然在不断调用,甚至,有时候接口调用速度非常快,一秒可能有好几个请求。

分析

beforeDestory 是组件销毁前的生命周期的钩子,这个钩子函数一定会调用,但是能不能彻底销毁 setTimeout 呢?答案是不能。

打开控制台就能看到不断打印出来的 id
在这里插入图片描述

这是因为,每次使用 clearTimeout 清除掉的是上一次的 id, 而不是本次正要执行的,这种情况,对于使用 setInterval 也是一样的。

根本原因在于,每次调用 getData, this.timer 是在不断的被赋予新的值,而不是一成不变的。

在以前的原生 js 中,我们通常这样写:

var timer = null
function init(){
	timer = setInterval(function(){
		getData()
	})
}
function getData(){}
window.onload = init
window.onunload = function(){
	clearInterval(timer)
}

由于上面的 timer 始终保持一个值,所以这里的清除是有效的

解决

vue 提供了 程序化的事件侦听器 来处理这类边界情况

按照文档的说法,我们的代码可以这样来更改

<script>
	import { Component, Vue, Watch, Prop, Emit } from 'vue-property-decorator'
	import { getLogList } from './api'
	@Component({})
	export default class extends Vue {
		logList = []
		// timer = null
		mounted(){
			this.getData()
		}
		async getData(){
			let r = await getLogList()
			if(r && r.logList){
				this.logList = r.logList
			}
			const timer = setTimeout(()=>{
				this.getData()
			}, 1000)
			this.$once('hook:beforeDestroy', function () {
			    clearTimeout(timer)
			})
		}
	}
</script>

这样写,还解决了两个潜在问题

  1. 在组件实例中保存这个 timer,最好只有生命周期钩子有访问它的权限。但是实例中的 timer 会视为杂物
  2. 如果建立代码独立于清理代码,会使得我们比较难于程序化地清理所建立的东西

结论

我们可以通过 程序化的事件侦听器 来监听销毁我们创建的任何代码示例
除了 setTimeout 和 setInterval ,通常还有一些第三方库的对象示例,如 timePicker,datePicker,echarts图表等。

mounted: function () {
	// Pikaday 是一个第三方日期选择器的库
  	var picker = new Pikaday({
    	field: this.$refs.input,
    	format: 'YYYY-MM-DD'
  	})
	// 在组件被销毁之前,也销毁这个日期选择器。
  	this.$once('hook:beforeDestroy', function () {
    	picker.destroy()
  	})
}

猜你喜欢

转载自blog.csdn.net/zhai_865327/article/details/106457196