vue 由 clearTimeout无法清除定时器引发的vue 周期函数,事件代码执行顺序思考

最近做个移动的项目,遇到需求:首页无操作20秒,自动退出登录。其他页面20秒无操作,自动跳转首页。

所谓的无操作,包括点击,触摸,滑动等用户行为。
这需求其实也很简单,思路就是使用定时器setTimeout设定时间,监听页面是否有点击,触摸,滑动等事件在操作,如果没有,则时间一到,就跳转首页或者退出登录,如果有事件发生,则清除定时器,然后重启定时器

那我们先来实现下这个逻辑,先放首页试试

<template>
  <div class="home" @click="screenClick" @touchmove="touchmove" @touchstart="touchStart" @touchend="touchEnd">
    <div class="home-img-box"></div>
    <div class="login-box row-center">
      <strong class="login-text">轻触屏幕,继续操作</strong>
    </div>
  </div>
</template>
<script>
export default {
  created() {
    this.screenClick()
  },
  methods:{
    /**
     * 屏幕在点击
     */
    screenClick(){
      // console.log('屏幕点击')
      this.delayUserHandle()
    },
    /**
     * 延迟
     */
    delayUserHandle(){
      window.clearTimeout(this.timeOut)
      // console.log('开始延迟')
      // console.log(this.timeOut)
      this.timeOut = window.setTimeout(()=> {
        // console.log('延迟结束')
        const {name} = this.$route
         if(name==='Home'){
            // 首页,
            this.$store.dispatch('logout')
          }else{
            this.$store.dispatch('logout')
            this.$router.replace('/')
            }
      },20000)
    },
    /**
     * 触摸开始
     */
    touchStart(){
      window.clearTimeout(this.timeOut)
    },
    /**
     * 触摸滑动
     */
    touchmove(){
      window.clearTimeout(this.timeOut)
    },
    /**
     * 触摸结束
     */
    touchEnd(e){
      // console.log('touchEnd')
      // console.log(e)
      this.delayUserHandle()
    }
  },
}
</script>

ok,这个就已经实现了一个页面的20秒无操作退出。
但是我们需要的是任意页面,而不是一个,所以,这一块有很多公用的地方。所以我们可以将公共的部分抽出来,这里使用mixins 混入。
新建 clear-login-info.js文件,这里放在src下的mixins文件夹中,同时,我们得在页面离开,销毁后,将定时器销毁。可以销毁定时器的地方有beforeDestroy,destroyed,beforeRouteLeave。开始的时候,没考虑那么多,直接用beforeRouteLeave,就是页面离开前的路由守卫。
clear-login-info.js 代码如下:

//清除用户信息
import {mapState} from "vuex";

export default {
  data(){
    return{
      timeOut:null,//定时器
    }
  },
  computed:{
    ...mapState(['hasLogin'])
  },
  created() {
    this.timeOut = setTimeout(()=>{
      this.$store.dispatch('clearInfo')
    },30000)
    this.screenClick()
  },
  methods:{
    /**
     * 屏幕在点击
     */
    screenClick(){
      this.delayUserHandle()
    },
    /**
     * 延迟
     */
    delayUserHandle(){
      window.clearTimeout(this.timeOut)
      // console.log('开始延迟')
      // console.log(this.timeOut)
      this.timeOut = window.setTimeout(()=> {
        // console.log('延迟结束')
        const {name} = this.$route
      if(name==='Home'){
            // 首页,
            this.$store.dispatch('logout')
          }else{
           this.$store.dispatch('logout')
            this.$router.replace('/')
            }
      },20000)
    },
    /**
     * 触摸开始
     */
    touchStart(){
      clearTimeout(this.timeOut)
    },
    /**
     * 触摸滑动
     */
    touchmove(){
      clearTimeout(this.timeOut)
    },
    /**
     * 触摸结束
     */
    touchEnd(){
      this.delayUserHandle()
    }
  },
  beforeRouteLeave(to,from,next){
    clearTimeout(this.timeOut)
    next()
  }
}

这就完了吗?的确是达到效果了,但是,仅仅如此也就没有本文了。有Bug.
使用后发现,从首页跳到其他页面后,不管是否有操作,20秒后都会跳转到首页。而且没有使用该效果的页面也会跳转到首页。
这里有个需要注意的地方就是,定时器并不会在页面销毁后自动消除。需要手动清除定时器。但是这里已经在beforeRouteLeave里面写了。那么原因就只要一个,真正起作用的定时器并没有被清除!

前面说过页面离开前可以清除定时器的有三个地方,beforeDestroy,destroyed和beforeRouteLeave,那么如果是跳转下一页面前,定时器没有消除成功,那我们就尽量在最后的时机来触发试试。首先destroyed肯定在beforeDestroy之后触发,所以我们需要比较下destroyed和beforeRouteLeave到底谁最后触发。验证方式也很简单,直接console.log打印即可。
首先在app.vue弄个路由跳转,这里假设有两个页面 首页和关于

<template>
<div>
   <router-link to="/mkf">关于</router-link>
    <router-link to="/home">首页</router-link>
    <router-view></router-view>
</div>
</template>

然后我们在首页来检测打印

<!--Home-->
<template>
  <div>Home</div>
</template>
<script>
export default {
  destroyed() {
    console.log('页面销毁')
  },
  beforeRouteLeave(to,from,next){
    console.log('路由守卫beforeRouteLeave')
    next()
  }
}
</script>

运行项目,在首页点击 跳转到 "关于"页面,看看浏览器打印结果
在这里插入图片描述
结果是destroyed最后执行。好,那我们吧清除定时器放在destroyed里面,将clear-login-info.js的beforeRouteLeave替换为destroyed

  destroyed() {
    // console.log('销毁')
    window.clearTimeout(this.timeOut)
  },

继续测试,我们发现,定时器依然没有被清除。
因为destroyed执行的时候,页面其实已经切换到新页面了。这时候去清楚上页面的定时器,变量可能已经不存在,所以没有清除成功。

这个问题比较烦,就不再继续测试了。总之不能在destroyd清除定时器,这方案不得行,这样使用会发现,定时器经常没有被清除掉。

思考了一下,这个需求,使用定时器加vuex即可解决。

Logo

前往低代码交流专区

更多推荐