如何在 React Router v7 中格式化相对时间
将时间戳格式化为“2 天前”这样的短语
问题
将时间戳显示为相对时间短语,例如“2 天前”或“3 小时后”,可以让内容显得更即时且具有上下文关联。然而,这些短语遵循的语法规则在不同语言中差异很大。英语中,“ago”放在过去时间量之后,“in”放在未来时间量之前,但其他语言可能会对时间单位本身进行词形变化、调整单词顺序,或者使用完全不同的语法结构。通过字符串拼接手动构建这些短语会导致除硬编码语言外的所有语言输出错误,从而破坏国际用户的体验。
解决方案
使用支持区域设置的相对时间格式,将时间戳差异转换为语法正确的短语。计算给定时间戳与当前时刻之间的时间差,然后根据用户的区域设置规则格式化该差异。这确保了过去和未来的时间表达遵循每种语言的正确语法、单词顺序和词形变化模式,而无需手动操作字符串。
步骤
1. 创建一个助手函数来计算相对时间值
FormattedRelativeTime 组件需要一个数值型的 value 和一个 unit 属性。构建一个助手函数来计算时间差并选择合适的单位。
export function getRelativeTimeValue(date: Date | number) {
const now = Date.now();
const timestamp = typeof date === "number" ? date : date.getTime();
const diffInSeconds = Math.round((timestamp - now) / 1000);
const minute = 60;
const hour = minute * 60;
const day = hour * 24;
const week = day * 7;
const month = day * 30;
const year = day * 365;
const absDiff = Math.abs(diffInSeconds);
if (absDiff < minute) {
return { value: diffInSeconds, unit: "second" as const };
} else if (absDiff < hour) {
return {
value: Math.round(diffInSeconds / minute),
unit: "minute" as const,
};
} else if (absDiff < day) {
return { value: Math.round(diffInSeconds / hour), unit: "hour" as const };
} else if (absDiff < week) {
return { value: Math.round(diffInSeconds / day), unit: "day" as const };
} else if (absDiff < month) {
return { value: Math.round(diffInSeconds / week), unit: "week" as const };
} else if (absDiff < year) {
return { value: Math.round(diffInSeconds / month), unit: "month" as const };
} else {
return { value: Math.round(diffInSeconds / year), unit: "year" as const };
}
}
此函数将时间戳转换为带符号的数值和基于差异大小的最合适时间单位。
2. 使用 FormattedRelativeTime 创建一个相对时间组件
使用来自 react-intl 的 FormattedRelativeTime 组件,该组件可以渲染格式化的相对时间,并可选择设置更新间隔。
import { FormattedRelativeTime } from "react-intl";
import { getRelativeTimeValue } from "./getRelativeTimeValue";
interface RelativeTimeProps {
date: Date | number;
updateIntervalInSeconds?: number;
}
export function RelativeTime({
date,
updateIntervalInSeconds,
}: RelativeTimeProps) {
const { value, unit } = getRelativeTimeValue(date);
return (
<FormattedRelativeTime
value={value}
unit={unit}
numeric="auto"
updateIntervalInSeconds={updateIntervalInSeconds}
/>
);
}
numeric="auto" 选项允许格式化器在适当情况下使用诸如“昨天”这样的短语,而不是“1 天前”。可选的 updateIntervalInSeconds 属性控制组件重新渲染的频率,以保持相对时间的实时性。
3. 在 React Router 路由中使用该组件
在需要显示时间戳的任何路由组件中渲染相对时间组件。
import { RelativeTime } from "./RelativeTime";
interface Post {
id: string;
title: string;
content: string;
createdAt: number;
}
export function PostDetail({ post }: { post: Post }) {
return (
<article>
<h1>{post.title}</h1>
<time dateTime={new Date(post.createdAt).toISOString()}>
<RelativeTime date={post.createdAt} />
</time>
<p>{post.content}</p>
</article>
);
}
该组件会根据用户的语言环境自动格式化时间戳,生成诸如英文的“2 days ago”、法文的“il y a 2 jours”或西班牙文的“hace 2 días”这样的短语。
4. 使用 useIntl 以命令式方式格式化相对时间
对于需要直接获取格式化字符串的情况(例如设置元素属性),可以使用 useIntl 钩子中的 formatRelativeTime 函数。
import { useIntl } from "react-intl";
import { getRelativeTimeValue } from "./getRelativeTimeValue";
interface CommentProps {
author: string;
text: string;
timestamp: number;
}
export function Comment({ author, text, timestamp }: CommentProps) {
const intl = useIntl();
const { value, unit } = getRelativeTimeValue(timestamp);
const relativeTime = intl.formatRelativeTime(value, unit, {
numeric: "auto",
});
return (
<div aria-label={`评论者:${author},发布于 ${relativeTime}`}>
<strong>{author}</strong>
<p>{text}</p>
</div>
);
}
这种方法为您提供了一个格式化的字符串,您可以将其用于属性,与其他文本拼接,或传递给非 React API,同时保持完整的语言环境支持。