跳到正文
册页

展示

Timestamp - 时间戳

一个日期或一个时间,按这套系统渲染它们的唯一方式渲染。

用法

什么时候用它

屏幕上任何一个时刻。另一条路是在调用处写 toLocaleString(),一个产品的一屏上凑齐四种日期格式就是这么来的。
TSX
import { Timestamp } from '@misoto22/folio'

说明

一个日期或一个时间,按这套系统渲染它们的唯一那种方式渲染。

每一张记录列表都需要它,而在调用处写 new Date().toLocaleString(),正是一个产品最后在一屏上凑齐四种日期格式的办法。

关于水合。 一个格式化好的日期,两半都依赖运行环境:相对时间取决于它什么时候被读到,就连绝对时间也取决于读者的语言和时区,而这些静态构建一个都不知道。所以第一帧——服务端产出的那一帧,也是客户端必须原样复现的那一帧——是 UTC 下的 ISO 日历日期,直接从 ISO 字符串里切出来,全程不让 Intl 靠近。两边是从同一串字符算出来的,所以它们不可能不一致。带语言习惯的形式和相对形式在挂载之后、在 effect 里才套上去,那时候才真的有一个读者可以去贴合。这个包把每一个页面都静态导出,所以另一条路就是:任何一个带日期的页面都会水合不匹配。

datetime 属性从第一次渲染起就是完整的 ISO 时刻,并且从不改变,所以读屏软件、爬虫,或者任何解析这份标记的东西,拿到的都是准确的那一刻——不管那个 effect 跑没跑过。

它每次挂载只格式化一次。一百行各自挂一个定时器、好让“3 分钟前”保持诚实,是一笔没人要过的开销;真的需要走秒的列表,请从上面重新 key 或者重新渲染。

结构

Timestamp anatomy
部件说明
元素必填一个 <time>,它的 datetime 从第一次渲染起就是完整的 ISO 时刻,而且从不改变,所以任何解析这段标记的东西都能拿到精确的那一刻,无论那个 effect 跑没跑过。
首屏必填UTC 日历日期,直接从 ISO 字符串上切下来,Intl 连边都不沾。服务端渲染的是它,客户端注水时渲染的也是它——两边是从同一串字符算出来的,所以不可能对不上。
本地读法必填挂载之后在一个 effect 里应用,那时才有一个可以“本地”于他的读者:相对写法用 numeric 为 "auto" 的 Intl.RelativeTimeFormat,绝对写法用 dateStyle 为 medium 的 Intl.DateTimeFormat。
无效值任何东西都解析不了的值,渲染成一个 --ink-3-aa 的破折号,根本不出现 <time>,因为一个写不出 datetime 的元素不是一个时刻。

实践建议

推荐

  • 记录列表就把 format 留在 auto。间隔在 relativeWithin 以内时它读作相对时间——默认是一周——超出之后换成日历日期,而后者既是更有用的那个事实,也是那个不再变的事实。
  • 传那个时刻,不要传格式化好的字符串。Date、ISO 字符串或者纪元毫秒数都行,三者最后都变成同一个 ISO 的 datetime 属性。
  • 列表需要走秒时,从上面重新渲染。它每次挂载只格式化一次,这是故意的:一百行各自挂一个定时器、只为让“3 分钟前”始终属实,是一笔没人要求过的开销。

避免

  • 别指望服务端渲染出来的 HTML 里有相对时间。首屏特意画的是 UTC 日期——爬虫、静态导出和读标记的测试看到的都是 2026-01-14,只有一个挂载完的浏览器才看得到“3 小时前”。
  • 别在它旁边用 toLocaleString 另格式化一个日期。只要有一次页面渲染发生在构建服务器上,这两个就会对不上,而这正是这个组件所围绕的那种注水不一致。
  • 别拿它表示时长。它渲染的是相对此刻的一个时刻;“2 分 14 秒的构建耗时”是一段长度,不是一个时刻,它属于一个普通字符串。

示例

relative and absolute

同一个时刻,三种写法。这里每一页都是静态导出的,所以第一次绘制——构建产出、浏览器在水合时必须一模一样复现的那一次——是 UTC 的日历日期,直接从 ISO 字符串里切出来,完全不经过 Intl。相对形式和跟着地区走的形式是挂载之后才应用的,那时候才有一个读者可以「跟着他所在的地方」。datetime 属性从第一次渲染起就是完整的 ISO 时刻,此后永不改变。

Thirty seconds ago
Ninety seconds ago
Three hours ago
Yesterday
Past auto’s window
Forced absolute, with the time
A value nothing can parse
—

a column of records

真实的用法,也是这个组件之所以存在、而不是在每个调用处写一句 toLocaleString 的理由。这一列里干活的是 auto:靠近现在的那些行读起来是一段相对的间隔,更早的则是日历日期,切换点在一周,因为过了这个点,日期既是更有用的那个事实,也是那个不再变化的事实。它每次挂载只格式化一次——一百行各自握着一个定时器,只为了让「三小时前」保持诚实,是一笔没人要求过的开销,所以一份必须走秒的列表,应该从上面重新渲染。

Recent deploys
CommitFinishedOutcome
a1b2c3dDeployed
9f8e7d6Deployed
4c5b6a7Rolled back
77aa2b1Deployed

what the markup carries

同一个时刻三遍,旁边就是它们全都由之构建出来的那个字符串。这一页是静态导出的,所以第一次绘制——构建产出、浏览器在水合时必须一模一样复现的那一次——是直接从那个字符串里切出来的 UTC 日历日期,Intl 一点边都没沾:两边是从同样的字符算出来的,所以它们不可能不一致。跟着地区走的形式和相对形式是挂载之后才应用的,那时候才有一个读者可以「跟着他所在的地方」。datetime 属性从第一次渲染起就是那个完整时刻,此后永不改变,而无论那个 effect 有没有跑过,读屏软件或者爬虫读到的都是它。

The value passed in
2026-01-14T09:30:00.000Z
auto, past its window
absolute, with the time
relative, however old

类型

TSX
export type TimestampFormat = 'auto' | 'relative' | 'absolute'

无障碍

  • datetime 属性从第一次渲染起就带着精确的 ISO 时刻,所以读机器值的辅助技术从不依赖某个 effect 跑没跑过。
  • 看得见的文字在挂载后变一次,机器值从头到尾不变,播报出来的值和解析出来的值因此始终一致。
  • 解析不了的值渲染成一个破折号,而不是浏览器那句原样的“Invalid Date”——那是工程的残渣,不是该摆到读者面前的东西。