- 前端
【免费下载链接】vueuse
Collection of essential Vue Composition Utilities for Vue 3
useTimeAgoIntl是 VueUse 中面向国际化场景的相对时间工具函数:它基于浏览器原生的Intl.RelativeTimeFormatAPI,自动将过去或未来的时间戳格式化为“5 分钟前”“in 5 minutes”这类本地化文案,并在时间变化时自动刷新。本文以 packages/core/useTimeAgoIntl/index.md 为核心骨架,结合 packages/core/useTimeAgoIntl/index.ts 的完整实现、测试用例 与 交互 Demo,带你掌握响应式与非响应式两种用法、全部配置参数、底层单位选择算法,以及如何自定义输出文案,可直接在 Vue 3 项目中落地。
一、函数定位与适用场景
useTimeAgoIntl属于 VueUsecore包中 Time 分类下的工具,与同样位于 packages/core/useTimeAgo 的useTimeAgo互为补充:
useTimeAgo:面向自行维护文案字典(messages)的定制化场景;useTimeAgoIntl:完全交给Intl.RelativeTimeFormat处理,开箱即得 40+ 种语言的自然输出,无需手工维护语言包。
典型场景包括:评论/动态的时间戳展示(“刚刚/5 分钟前”)、版本发布时间、倒计时提示等需要随当前时间自动刷新的 UI 文案。从源码结构看,它的实现思路是「响应式时钟 + 计算属性」:内部通过useNow维护一个响应式的“当前时间”,再以computed计算相对时间字符串,因此一旦系统时间推进或传入的目标时间发生变化,输出会自动更新。
二、安装与引入
useTimeAgoIntl已从 packages/core/index.ts 统一导出,属于@vueuse/core标准工具集,无需额外安装第三方依赖(Intl.RelativeTimeFormat为运行环境原生能力)。
# 已有 Vue 3 项目安装 VueUse npm i @vueuse/core按需引入:
import { useTimeAgoIntl } from '@vueuse/core'三、基础用法:响应式版本
原文档给出的最小示例即完整可用:
import { useTimeAgoIntl } from '@vueuse/core' const timeAgoIntl = useTimeAgoIntl(new Date(2021, 0, 1))返回值是一个ComputedRef<string>,在模板中直接渲染即可:
<template> <span>{{ timeAgoIntl }}</span> </template>几点关键特性:
- 自动更新:内部使用
useIntervalFn(cb, 30_000)(30 秒一次)刷新“当前时间”,输出会随真实时间流逝自动从“x years ago”变化到更细粒度。源码见 packages/core/useTimeAgoIntl/index.ts。 - 目标时间可响应:第一个参数支持
MaybeRefOrGetter<Date | number | string>,即可以传ref或 getter 函数。例如 Demo 中拖动滑块实时改变目标时间戳:
import { timestamp, useTimeAgoIntl } from '@vueuse/core' import { computed, shallowRef } from 'vue' const slider = shallowRef(0) const value = computed(() => timestamp() + slider.value ** 3) const timeAgoIntl = useTimeAgoIntl(value, { locale: 'en' }) const timeAgoIntlZh = useTimeAgoIntl(value, { locale: 'zh' })- 多语言并存:同屏展示多种语言时,只需分别指定不同
locale(如上面的en与zh),互不干扰。
四、非响应式用法:formatTimeAgoIntl
当不需要响应式刷新、只想拿到一次性字符串时,用导出函数formatTimeAgoIntl替代,返回普通string而非Ref:
import { formatTimeAgoIntl } from '@vueuse/core' const timeAgoIntl = formatTimeAgoIntl(new Date(2021, 0, 1)) // string其函数签名(源码 packages/core/useTimeAgoIntl/index.ts)为:
formatTimeAgoIntl( from: Date, options: FormatTimeAgoIntlOptions = {}, now: Date | number = Date.now(), ): string第三个参数now允许显式指定“基准当前时间”,这在测试与固定时间快照场景下非常实用——测试用例正是通过传入固定now来断言输出:
const now = Date.now() const past = new Date(now - 1000 * 60 * 5) formatTimeAgoIntl(past, { locale: 'en' }, now) // => '5 minutes ago' formatTimeAgoIntl(past, { locale: 'zh' }, now) // => '5 分钟前' const future = new Date(now + 1000 * 60 * 5) formatTimeAgoIntl(future, { locale: 'en' }, now) // => 'in 5 minutes'可以看到:过去时间输出“5 minutes ago / 5 分钟前”,未来时间输出“in 5 minutes”,均为Intl.RelativeTimeFormat的本地化结果,且默认numeric: 'auto'模式下会自然选择“5 天前”而非“5 天之前”这类语气词形式。
五、配置参数全解
useTimeAgoIntl的选项由FormatTimeAgoIntlOptions(格式化选项)与ConfigurableScheduler(调度选项)组合而成,类型定义见 packages/core/useTimeAgoIntl/index.ts。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
locale | Intl.UnicodeBCP47LocaleIdentifier \| Intl.Locale | undefined(使用环境默认区域) | 指定格式化语言,如'en'、'zh'、'zh-CN' |
relativeTimeFormatOptions | Intl.RelativeTimeFormatOptions | { numeric: 'auto' } | 传给Intl.RelativeTimeFormat的选项,如numeric: 'always'、style: 'long' \| 'short' \| 'narrow' |
insertSpace | boolean | true | 是否在各 parts 片段之间插入空格;设置了joinParts时被忽略 |
joinParts | (parts, locale?) => string | 内置拼接逻辑 | 自定义Intl.RelativeTimeFormat.formatToParts结果的拼接方式 |
units | TimeAgoUnit[] | 内置 7 档单位 | 自定义相对时间单位及其毫秒阈值 |
controls | boolean | false | 是否额外暴露parts原始片段与暂停/恢复控制 |
scheduler | (cb: Fn) => Pausable | useIntervalFn(cb, 30_000) | 自定义刷新调度器(继承自 ConfigurableScheduler) |
5.1 locale 与 relativeTimeFormatOptions
两者最终合并传入new Intl.RelativeTimeFormat(locale, relativeTimeFormatOptions)(源码 packages/core/useTimeAgoIntl/index.ts)。Intl.RelativeTimeFormat的numeric选项直接决定文案风格:
// 默认 { numeric: 'auto' }:输出 "yesterday / 昨天" useTimeAgoIntl(yesterday, { locale: 'en' }) // numeric: 'always':输出 "1 day ago" useTimeAgoIntl(yesterday, { locale: 'en', relativeTimeFormatOptions: { numeric: 'always' }, })5.2 insertSpace 与中文/日文等无空格语言
Intl.RelativeTimeFormat.formatToParts会把结果拆成数值片段与字面量片段,例如中文的“5”与“天后”。默认insertSpace: true会在片段之间以空格连接('5 天后');对于中文、日文、韩文等本身不需要空格的语言,可关闭:
formatTimeAgoIntlParts( [{ type: 'integer', value: '5', unit: 'day' }, { type: 'literal', value: '天后' }], { insertSpace: false }, ) // => '5天后'对应行为由 测试用例 覆盖验证:开启时为'5 天后',关闭时为'5天后';而英文由于formatToParts的 literal 片段自带前导空格,默认与关闭结果一致('5 days')。
5.3 joinParts:完全自定义拼接
当内置的拼接规则无法满足需求(例如要在数值上包裹 HTML 标签做高亮),可传入joinParts接管拼接,其优先级高于insertSpace。实现中会把它拿到的parts数组原样交给回调,并传入解析后的locale供二次判断:
formatTimeAgoIntlParts(parts, { joinParts: p => p.map(x => `[${x.value}]`).join('|'), }) // => '[5]|[天后]'测试见 packages/core/useTimeAgoIntl/index.browser.test.ts。
5.4 units:自定义时间单位与阈值
units接受一组{ name, ms }(name为Intl.RelativeTimeFormatUnit,如'second'、'minute'、'hour'、'day'、'week'、'month'、'year';ms为该单位的最小毫秒阈值)。内置单位为:
const UNITS: TimeAgoUnit[] = [ { name: 'year', ms: 31_536_000_000 }, // 365 天 { name: 'month', ms: 2_592_000_000 }, // 30 天 { name: 'week', ms: 604_800_000 }, // 7 天 { name: 'day', ms: 86_400_000 }, // 1 天 { name: 'hour', ms: 3_600_000 }, // 1 小时 { name: 'minute', ms: 60_000 }, // 1 分钟 { name: 'second', ms: 1_000 }, // 1 秒 ]可见默认从“年”到“秒”共 7 档。如果你希望展示到“天”为止(超过 1 年也显示为天数),可以传入自定义units截断最大单位。
六、源码原理:单位选择与格式化流程
核心算法位于getTimeAgoIntlResult(源码 packages/core/useTimeAgoIntl/index.ts),流程如下:
- 以
locale与relativeTimeFormatOptions构造Intl.RelativeTimeFormat实例,并通过rtf.resolvedOptions()取得实际解析后的区域resolvedLocale(用于把精确区域回传给拼接逻辑); - 计算时间差
diff = +from - +now,取其绝对值absDiff; - 按
units(默认UNITS)的顺序遍历,返回第一个满足absDiff >= ms的单位; - 调用
rtf.formatToParts(Math.round(diff / ms), name)得到结构化片段,其中Math.round保证了“5.6 分钟”显示为“6 分钟”; - 若所有单位都不满足(即不足最小单位
second的 1 秒),则退化为rtf.formatToParts(0, 最后一个单位),即输出“0 秒”类文案。
随后formatTimeAgoIntlParts(源码 packages/core/useTimeAgoIntl/index.ts)负责把片段拼成字符串:
if (typeof joinParts === 'function') return joinParts(parts, locale) if (!insertSpace) return parts.map(part => part.value).join('') return parts .map(part => part.value.trim()) .join(' ')从实现可以看到,所谓“自动更新”并不依赖 Vue 的响应式订阅时间对象本身,而是依赖useNow内部以调度器驱动刷新now值,再通过computed链式触发重算——这是理解其性能特征的关键。
七、controls 模式:访问 parts 与暂停/恢复
设置options.controls = true后,返回值从ComputedRef<string>扩展为一个对象(类型定义见 packages/core/useTimeAgoIntl/index.ts):
const { timeAgoIntl, parts, isActive, pause, resume } = useTimeAgoIntl(past, { controls: true, }) timeAgoIntl.value // 格式化后的字符串 parts.value // Intl.RelativeTimeFormatPart[],如 [{ type: 'integer', value: '5', unit: 'minute' }, ...]parts:ComputedRef<Intl.RelativeTimeFormatPart[]>,每个片段含type(如'integer'、'literal'、'unit')与value等属性,适合做样式化渲染;isActive / pause / resume:继承自Pausable,本质来自内部useIntervalFn的控制句柄。需要暂停自动刷新(如页面隐藏或组件卸载前的空闲时段)时调用pause(),恢复则调用resume()。
对应的行为断言见 测试用例:parts.value为数组,且首元素具有type与value属性。
八、自定义调度器 scheduler
useTimeAgoIntl默认每 30 秒刷新一次(useIntervalFn(cb, 30_000),源码 packages/core/useTimeAgoIntl/index.ts),足以覆盖绝大多数相对时间场景。若需更高或更低的刷新频率,可通过scheduler覆盖:
import { useIntervalFn } from '@vueuse/core' // 每 60 秒刷新 useTimeAgoIntl(time, { scheduler: cb => useIntervalFn(cb, 60_000), })scheduler签名继承自 ConfigurableScheduler:接收一个回调函数,返回Pausable({ isActive, pause, resume })。底层useNow(源码 packages/core/useNow/index.ts)默认使用useRafFn,而useTimeAgoIntl主动将其替换为低频的useIntervalFn,从而避免每帧重算,兼顾性能与时效。
九、与 useTimeAgo 的选型对比
当多语言依赖原生Intl即可满足时,优先选择useTimeAgoIntl(代码更少、无需维护语言包);当你需要:
- 自定义“刚刚 / 刚刚xx”等非标准文案;
- 超过
max阈值后直接显示完整日期; - 精细控制
rounding(round/ceil/floor)与showSecond;
则选择 packages/core/useTimeAgo(其messages参数支持函数式格式化)。两者共享units与controls、scheduler等设计,迁移成本很低。
十、测试验证与兼容性说明
仓库为该函数配备了完整的浏览器测试 packages/core/useTimeAgoIntl/index.browser.test.ts,覆盖四类行为:
formatTimeAgoIntlParts默认空格拼接、insertSpace: false无空格拼接、joinParts自定义拼接;formatTimeAgoIntl过去/未来时间的 en/zh 输出;useTimeAgoIntl的响应式计算(配合shallowRef修改目标时间);controls: true下parts的结构正确性。
兼容性提示:Intl.RelativeTimeFormat为现代运行时原生 API(现代浏览器与 Node.js 均支持),在较老环境使用前建议先做能力检测,或回退到useTimeAgo。另外注意useTimeAgoIntl依赖时间基准now的周期性刷新,SSR 首屏渲染时会基于服务端时间生成一次快照,后续在客户端继续更新,符合常规同构渲染预期。
十一、快速参考小结
import { useTimeAgoIntl, formatTimeAgoIntl } from '@vueuse/core' // 1. 响应式(默认每 30s 自动刷新) const ago = useTimeAgoIntl(new Date(2021, 0, 1)) // 2. 指定语言 + 总是数字式文案 const agoZh = useTimeAgoIntl(time, { locale: 'zh-CN', relativeTimeFormatOptions: { numeric: 'always', style: 'short' }, }) // 3. 一次性字符串 const str = formatTimeAgoIntl(new Date(2021, 0, 1)) // 4. 高级:拿到 parts + 暂停/恢复 const { timeAgoIntl, parts, pause, resume } = useTimeAgoIntl(time, { controls: true })若需深入阅读,可继续查看:
- 完整实现:packages/core/useTimeAgoIntl/index.ts
- 测试用例:packages/core/useTimeAgoIntl/index.browser.test.ts
- 交互 Demo:packages/core/useTimeAgoIntl/demo.vue
- 时间基准实现:packages/core/useNow/index.ts
- 调度器类型:packages/core/_configurable.ts
- 定时器控制:packages/shared/useIntervalFn/index.ts
- 前端
【免费下载链接】vueuse
Collection of essential Vue Composition Utilities for Vue 3
相关推荐
QtScrcpy快速上手指南:三步搞定安卓投屏与低延迟键鼠控制
QtScrcpy快速上手指南:三步搞定安卓投屏与低延迟键鼠控制 把手机画面镜像到电脑,画面却总比手指慢半拍?多半是投屏链路的问题。QtScrcpy 是一款开源的
桌面应用音视频开源项目 NBTExplorer 亮点解析
开源项目 NBTExplorer 亮点解析 1. 项目的基础介绍 NBTExplorer 是一个用于探索和编辑 NBT(Named Binary Tag)文件的
前端SeaTunnel 精确一次语义(Exactly-Once)全解析:从两阶段提交到端到端容错实战
SeaTunnel 精确一次语义(Exactly Once)全解析:从两阶段提交到端到端容错实战 导读 本文以 Apache SeaTunnel 的容错架构文档
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考