news 2026/9/10 21:18:45

Airi 项目中的 VueUse useTimeout 实战指南:响应式延时、可控定时与源码级原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Airi 项目中的 VueUse useTimeout 实战指南:响应式延时、可控定时与源码级原理

Airi 项目中的 VueUse useTimeout 实战指南:响应式延时、可控定时与源码级原理

【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi

useTimeout 是 VueUse 中位于 Animation(动画)分类下的响应式组合式函数,它把"延时后置为true"这个需求抽象成一个可响应、可控制、可复用的定时器。本文以 .agents/skills/vueuse-functions/references/useTimeout.md 为核心骨架,结合 Airi 仓库中stage-tamagotchi桌面端与stage-ui的实际调用代码,讲解 useTimeout 的完整 API、与useTimeoutFn的差异,以及它在真实项目中的落地姿势。

读完本文,你将掌握:如何用 useTimeout 写出"延时后自动变为 true 的响应式状态",如何通过controls暴露start / stop / isPending手动控制定时器,如何传入响应式时长与完成回调,以及底层Stoppable接口如何保证组件卸载时自动清理定时器、避免内存泄漏。

一、useTimeout 是什么:把 setTimeout 变成响应式状态

VueUse 中的useTimeout是一个"响应式布尔值",它在经过指定时间后从false变为true。它与原生setTimeout的核心区别在于:

  • 原生setTimeout是一次性、命令式的回调调用,状态变化无法被模板或watch直接感知;
  • useTimeout返回的是ComputedRef<boolean>,可以像任何 Vue 响应式状态一样用于模板渲染、watchcomputed派生,甚至参与动画过渡。

在 SKILL.md 的函数清单中,useTimeout 被归入 Animation 分类,描述为 "Reactive value that becomestrueafter a given time",其调用规则(Invocation)为AUTO,即:只要适用就应优先使用 VueUse 组合式函数而非手写自定义代码,以提升可读性、可维护性与性能。

最简用法

import { useTimeout } from '@vueuse/core' const ready = useTimeout(1000)

1 秒之后,ready.value变为true。默认情况下定时器立即启动(immediate: true),所以你甚至不需要手动调用任何函数,只需在模板或逻辑里读取ready

<script setup lang="ts"> import { useTimeout } from '@vueuse/core' const ready = useTimeout(1000) </script> <template> <p>{{ ready ? '已就绪' : '等待中…' }}</p> </template>

二、开启 controls:start / stop / isPending 手动控制

很多场景下我们需要的不是一个"静默的倒计时",而是一个可暂停、可重启的定时器。传入{ controls: true }后,useTimeout 会额外返回startstopisPending三个控制项:

import { useTimeout } from '@vueuse/core' const { ready, start, stop, isPending } = useTimeout(1000, { controls: true }) // 检查定时器是否仍在计时 console.log(isPending.value) // true // 停止定时器(ready 将停留在 false) stop() // 重新开始/重启定时器 start()

注意区分:

返回值类型含义
readyComputedRef<boolean>到点后变为true的响应式状态
start() => void启动或重新启动定时器(非响应式版本,见下节)
stop() => void停止定时器
isPendingComputedRef<boolean>定时器是否仍在计时中

这组返回值的类型签名对应Stoppable接口——VueUse 中凡是实现Stoppable的组合式函数,都承诺在组件卸载时自动调用stop()完成清理,这正是用它替换手写setTimeout不会产生内存泄漏的底层保证。

三、Options 参数一览

useTimeout的第二个参数是UseTimeoutOptions,它继承自UseTimeoutFnOptions并追加了两个字段:

OptionTypeDefaultDescription
controlsbooleanfalse是否暴露startstopisPending控制项
immediatebooleantrue是否立即启动定时器
callback() => void定时器完成时触发的回调

三个选项的相互作用

  • controls只影响返回值形状:为false时返回单个ComputedRef<boolean>;为true时返回{ ready, start, stop, isPending }的组合对象(下文的类型声明会给出精确签名)。
  • immediate: false配合controls: true是"手动门闩"模式:定时器不会自动启动,只有显式调用start()才开始计时。这在"用户触发后才开始等待"的交互中非常有用。
  • callbackready并不冲突:到点时两者都会触发/置位,你可以用ready驱动 UI,用callback执行副作用(比如发出埋点、切换路由、释放资源)。

四、Callback:到点后的副作用钩子

import { useTimeout } from '@vueuse/core' useTimeout(1000, { callback: () => { console.log('Timeout completed!') }, })

callback的类型是() => void,也就是UseTimeoutOptionscallback?: Fn所指的通用函数类型。它是轻量的"完成通知"钩子,适合在倒计时结束瞬间执行一段与 UI 状态解耦的逻辑。

五、响应式时长:interval 可以是 ref 或 getter

useTimeout 的时长参数类型是MaybeRefOrGetter<number>,这意味着你不仅可以传字面量,还可以传ref或 getter 函数,让时长本身具备响应性:

import { useTimeout } from '@vueuse/core' const duration = ref(1000) const ready = useTimeout(duration) // 修改时长(在启用 controls 时,只影响未来启动的定时器) duration.value = 2000

注意原文档明确提示的行为细节:修改时长只影响"未来的"定时器。如果定时器已经在计时中,中途修改duration不会打断当前这一次计时;如果你需要"以新时长重新开始",正确做法是更新 ref 后显式调用start()重启。这也解释了为什么"响应式时长"通常与controls: true搭配使用才最有价值——单纯的ready布尔值拿到的是旧时长下的一次性结果。

六、Type Declarations 精读:重载签名与兼容别名

原文档给出了完整类型声明,这里逐段拆解其含义:

export interface UseTimeoutOptions< Controls extends boolean, > extends UseTimeoutFnOptions { /** * Expose more controls * * @default false */ controls?: Controls /** * Callback on timeout */ callback?: Fn }

UseTimeoutOptions通过泛型Controlscontrols的取值直接映射到返回类型上,从而实现"传了controls: true就自动得到完整控制对象"的编译期类型推断。

export type UseTimeoutReturn = | ComputedRef<boolean> | ({ readonly ready: ComputedRef<boolean> } & Stoppable)

UseTimeoutReturn是联合类型:要么是纯ComputedRef<boolean>,要么是"ready+Stoppable"的组合。Stoppable正是提供start/stop/isPending的来源。

/** * @deprecated use UseTimeoutReturn instead */ export type UseTimoutReturn = UseTimeoutReturn

注意一个历史遗留:UseTimoutReturn(少写了一个字母o)是官方标记@deprecated的旧别名,类型上与UseTimeoutReturn完全等价。如果你是 VueUse 早期版本的迁移用户,看到这个别名不必困惑,新代码应统一使用UseTimeoutReturn

两个重载的函数签名:

export declare function useTimeout( interval?: MaybeRefOrGetter<number>, options?: UseTimeoutOptions<false>, ): ComputedRef<boolean> export declare function useTimeout( interval: MaybeRefOrGetter<number>, options: UseTimeoutOptions<true>, ): { ready: ComputedRef<boolean> } & Stoppable

从重载可以看出:第一个签名中interval是可选的(省略时默认0,立即置true);第二个签名要求interval必填且controls: true。这一设计让"无参场景"(默认返回一个会立刻/按时变为 true 的 ref)和"可控场景"(需要 interval)在类型上被严格区分。

七、源码级原理:useTimeout 与 useTimeoutFn 的分工

useTimeout 本质上是useTimeoutFn(useTimeoutFn.md)之上的一个状态化封装。看useTimeoutFn的声明就能理解两者关系:

export declare function useTimeoutFn<CallbackFn extends AnyFn>( cb: CallbackFn, interval: MaybeRefOrGetter<number>, options?: UseTimeoutFnOptions, ): UseTimeoutFnReturn<CallbackFn>

其中UseTimeoutFnOptions提供:

OptionTypeDefaultDescription
immediatebooleantrue是否立即启动定时器
immediateCallbackbooleanfalse调用start()时是否立即执行回调

对比两条使用路径:

  • useTimeoutFn(cb, interval):核心职责是"到点执行回调",返回isPending/start/stop,不维护布尔状态;
  • useTimeout(interval, { callback }):在内部用useTimeoutFn把"到点"翻译成ready = true的响应式状态,callback则是透传给你的副作用。

因此,如果你的目标是"等待一段时间后执行一段代码",useTimeoutFn更直接;如果目标是"让某个布尔状态在延时后翻转并参与渲染/watch",useTimeout是更贴合语义的选择。本文聚焦的 useTimeout 属于前者之上的"状态化"抽象。

Airi 中的真实对照:controls 模式的完整范本

Airi 的桌面端舞台(stage-tamagotchi)在控制岛(Controls Island)角落搬迁动画中,用三个useTimeoutFn(同样基于Stoppable控制模型)串联了"离开旧角落 → 准备入场 → 抵达新角落"的阶段机:

  • controls-island-root.vue
const { start: finishArrival, stop: stopArrival } = useTimeoutFn(() => { motionPhase.value = 'idle' relocationTarget.value = undefined }, placementArrivalDurationMs, { immediate: false }) const { start: startArrival, stop: stopEnterPreparation } = useTimeoutFn(() => { motionPhase.value = 'arriving' finishArrival() }, placementEnterPreparationMs, { immediate: false }) const { start: finishLeave, stop: stopLeave } = useTimeoutFn(() => { if (!relocationTarget.value) { motionPhase.value = 'idle' return } dock.value = relocationTarget.value motionPhase.value = 'entering' startArrival() }, placementLeaveDurationMs, { immediate: false })

这段代码把immediate: falsestart()stop()用到了极致:三个定时器都不自动启动,由relocate()在用户拖动窗口触发搬迁时先stopRelocation()取消所有未完成任务,再按阶段逐个start()接力。这正是文档中"immediate: false配合controls实现手动门闩"的真实写照。

Airi 中的响应式时长范本:OIDC Token 定时刷新

stage-ui 的认证 Store 用useTimeoutFn实现了 OIDC access token 的"寿命 80% 时刻"刷新调度,完美示范了"响应式 ref 作为时长参数"的用法:

const refreshDelayMs = ref(0) const { start: startRefreshTimer, stop: stopRefreshTimer } = useTimeoutFn( () => { void useAuthStore().refreshTokenNow() }, refreshDelayMs, { immediate: false }, ) function scheduleTokenRefresh(expiresInSeconds: number): void { stopRefreshTimer() // Guard against missing/invalid lifetimes(例如 token 响应缺少 expires_in)。 // useTimeoutFn 在 NaN/<=0 延时下会立即触发并造成刷新死循环——这里直接跳过调度。 if (!Number.isFinite(expiresInSeconds) || expiresInSeconds <= 0) return // 在生命周期 80% 的时间点刷新 refreshDelayMs.value = expiresInSeconds * 0.8 * 1000 startRefreshTimer() }

三个要点值得对照前文吸收:

  1. immediate: false+start():定时器只在scheduleTokenRefresh里手动启动,避免 Store 初始化时误触发刷新;
  2. 响应式时长refreshDelayMsref(0),每次刷新成功后按新的expires_in更新再start(),同一套代码复用于不同寿命的 token;
  3. 边缘守卫:注释里明确提到 "useTimeoutFn with NaN/<=0 delay would fire immediately",即非有限/非正时长会立刻触发回调——这与前文"interval 省略时默认 0"的类型语义呼应,提醒开发者对动态时长必须做有效性校验。

这个案例也直接印证了 SKILL.md 的核心主张:优先用 VueUse 组合式函数而非手写setTimeout——useTimeoutFn在组件/Store 销毁时自动清理定时器,代码里不需要再手动clearTimeout

八、与相邻定时类函数的选型对比

在 VueUse 中与 useTimeout 相邻的定时/动画函数还有useIntervaluseIntervalFnuseRafFn,它们同属 Animation 分类(见 SKILL.md 中 Animation 一节):

函数语义典型场景
useTimeout延时后布尔值翻转为true一次性等待、就绪门闩、延时展示
useTimeoutFn延时后执行回调(Stoppable一次性副作用、节流式调度
useInterval每隔 N 毫秒递增的响应式计数器倒计时、轮询进度展示
useIntervalFnsetInterval的可控包装周期性任务
useRafFn每帧调用一次回调动画循环、渲染节流

Airi 中也能找到后者在用的实例:例如 use-chat-history-top-fade.ts 用useRafFn把"滚动到底部后淡出顶部历史"的更新推迟到下一帧,避免布局抖动;use-virtualizer-scroll.ts 也用useRafFn做滚动渲染节流。选择依据很简单:一次性延时看 useTimeout/useTimeoutFn,周期性任务看 useInterval/useIntervalFn,逐帧动画看 useRafFn。

九、实战要点与陷阱清单

综合原文档、类型声明与仓库证据,这里给出 useTimeout 的实战 checklist:

  1. 默认自动启动immediate默认为true,只想要"到点翻 true"就一行搞定;需要手动触发则必须controls: true+immediate: false
  2. controls 三件套start()可重启计时,stop()立即中止,isPending可读是否在计时;它们来自Stoppable接口,组件卸载时自动清理。
  3. 响应式时长只影响未来定时器:中途改 interval 不会打断当前计时,需要新时长生效就改 ref 后重新start()
  4. 动态时长要防非法值:参考 auth.ts 的做法,对来自外部(token 响应、接口数据)的时长先做Number.isFinite> 0校验,避免 NaN/负值导致定时器立即触发。
  5. callback 与 ready 分工:UI 用ready驱动,副作用放callback,保持状态与行为的解耦。
  6. 与 useTimeoutFn 的选择:需要的是"状态"选 useTimeout,需要的是"到点执行函数"选 useTimeoutFn;两者共享Stoppable控制模型,可互相替代实现。
  7. 旧别名注意UseTimoutReturn已废弃,新代码一律写UseTimeoutReturn

十、延伸阅读

  • 函数索引与调用规则:.agents/skills/vueuse-functions/SKILL.md
  • 本文主文档(含完整类型声明):useTimeout.md
  • 底层实现对照:useTimeoutFn.md
  • 仓库实战:控制岛动画阶段机 controls-island-root.vue、OIDC 定时刷新 auth.ts

【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 21:16:27

三星联手Mistral AI,本地大模型进入芯片制造

近日&#xff0c;三星电子与法国AI新贵Mistral AI达成重要合作&#xff1a;后者将向三星提供可本地化部署的AI大模型套件&#xff0c;用于半导体制造与工程业务&#xff0c;帮助三星构建定制化AI能力。协议在韩国与法国于巴黎举行的双边峰会期间宣布&#xff0c;被视为两国在高…

作者头像 李华
网站建设 2026/9/10 21:15:03

Keploy 几分钟装好:面向新手的完整安装指南

Keploy 几分钟装好&#xff1a;面向新手的完整安装指南 【免费下载链接】keploy Open-source platform for creating safe, isolated production sandboxes for API, integration, and E2E testing. 项目地址: https://gitcode.com/GitHub_Trending/ke/keploy 测试全靠手…

作者头像 李华