useIntervalFn让定时调用更加简单

2,625 阅读3分钟

开发过程中经常遇到定时调用的场景,比如倒计时,比如每隔多少秒调用一次接口刷新数据,再比如实现一个轮播图片的功能,还有实现轨迹播放的时候也会用到。在JS中我们使用setInterval来开启定时器,使用clearInterval来清除一个定时器。在使用vue作为前端框架开发时,我们往往需要在组件或者页面卸载的时候清除定时器,在实现播放器功能时需要暂停和唤醒定时器。今天要学习的useIntervalFn真的很不错,把自动清除还有暂停和唤醒都帮我们实现了,我们一起看一下它是如何办到的吧!

1.示例

官方文档示例代码:

<script setup lang="ts">
import { ref } from 'vue'
import { useIntervalFn } from '@vueuse/core'
import { rand } from '@vueuse/shared'

const greetings = ['Hello', 'Hi', 'Yo!', 'Hey', 'Hola', 'こんにちは', 'Bonjour', 'Salut!', '你好']
const word = ref('Hello')
const interval = ref(500)

const { pause, resume, isActive } = useIntervalFn(() => {
  word.value = greetings[rand(0, greetings.length - 1)]
}, interval)
</script>

<template>
  <p>{{ word }}</p>
  <p>
    interval:
    <input v-model="interval" type="number" placeholder="interval">
  </p>
  <button v-if="isActive" class="orange" @click="pause">
    Pause
  </button>
  <button v-if="!isActive" @click="resume">
    Resume
  </button>
</template>

上述代码演示了借助useIntervalFn实现了每隔500ms实现一次问候,问候语为从greetings数组中取得随机值。

可以点击Pause暂停问候。

可以点击Resume唤起计时器继续问候。

2.源码分析

一张图片概览源码:

2.1 IntervalFnOptions接口说明

export interface IntervalFnOptions {
  immediate?: boolean
  immediateCallback?: boolean
}

immediate表示是否立即开启timer,默认值为true; immediateCallback表示是否立刻执行回调,默认值为false。

2.2 参数处理及初始化

const {
  immediate = true,
  immediateCallback = false,
} = options

let timer: any = null
const isActive = ref(false)

获取options参数的选项immediate和immediateCallback,如果用户没有传options参数或者相应的选项则使用默认值,分别是true和false。初始化timer为null,isActive为false。isActive表示timer是否是激活状态。

2.3 clean()清除timer

function clean() {
  if (timer) {
    clearInterval(timer)
    timer = null
  }
}

clean方法中对timer进行判断,然后调用clearInterval清除定时器,最后把timer赋值为null。

2.4 pause()暂停定时器

function pause() {
  isActive.value = false
  clean()
}

pause()方法用于暂停定时器,首选将isActive设置为false,然后调用clean()方法清空定时器。

2.5 resume() 唤醒定时器

function resume() {
  if (unref(interval) <= 0)
    return
  isActive.value = true
  if (immediateCallback)
    cb()
  clean()
  timer = setInterval(cb, unref(interval))
}

resume用于唤醒定时器。如果时间间隔interval小于零则返回;否则开始执行定时器,将isActive设置为true,然后根据immediateCallback判断是否立即执行一次回调;在执行之前要先调用一次clean,然后调用setInterval方法启动定时器。这里需要注意interval可能是一个ref的响应式变量,所以这里调用unref。

2.6 立即启动定时器

if (immediate && isClient)
    resume()

如果immediate为true并且isClient为true则调用resume()启动定时器。

2.7 监听interval和注册pause到当前活跃effect作用域

if (isRef(interval)) {
  const stopWatch = watch(interval, () => {
    if (immediate && isClient)
      resume()
  })
  tryOnScopeDispose(pause)
}

tryOnScopeDispose(pause)

如果interval是响应式的则需要监听interval的变化,在interval发生变化时调用resume方法重新启动定时器;watchAPI的返回值是stopWatch,把stopWatch()方法注册到当前活跃的effect作用域上。如果interval不是响应式的则将pause方法注册到当前活跃的effect作用域上。在笔者之前的文章useEventListener:一个减少vue开发者心智负担的事件监听方法 中有介绍过,像stopWatch或者pause这样的回调函数会在相关的effect作用域结束之后被调用,从而实现注销事件或者清除定时等功能。

2.8 返回值

 return {
   isActive,
   pause,
   resume,
 }

将定时器状态isActive以及停止和唤醒定时器的方法pause以及resume方法返回。

3.总结

useIntervalFn的实现中使用到vue3 的API有isRef, ref, unref, watch;使用JS原生的API有setInterval和clearInterval。在实现细节中需要注意的地方有:参数interval可能是一个ref,所以需要unref获取其原始值;如果interval是ref那么还需要使用watch对其监听;需要将pause或者把stopWatch注册到当前活跃的effect作用域上为的是自动调用实现清除定时器的功能。

4.留给读者

读完本文的朋友们可以留言回答如下问题:

1.在你的开发过程中都有哪些功能或者场景用到了定时调用?

2.你在使用完通过setInterval声明的定时器后会调用clearInterval清除定时器吗?

3.你认为useIntervalFn的源码中有哪些可以借鉴之处?