展示
Timestamp 时间戳
一个日期或一个时间,按这套系统渲染它们的唯一方式渲染。
用法
什么时候用它
import { Timestamp } from '@misoto22/design'说明
一个日期或一个时间,按这套系统渲染它们的唯一那种方式渲染。
每一张记录列表都需要它,而在调用处写 new Date().toLocaleString(),正是一个产品最后在一屏上凑齐四种日期格式的办法。
关于水合。 一个格式化好的日期,两半都依赖运行环境:相对时间取决于它什么时候被读到,就连绝对时间也取决于读者的语言和时区,而这些静态构建一个都不知道。所以第一帧——服务端产出的那一帧,也是客户端必须原样复现的那一帧——是 UTC 下的 ISO 日历日期,直接从 ISO 字符串里切出来,全程不让 Intl 靠近。两边是从同一串字符算出来的,所以它们不可能不一致。带语言习惯的形式和相对形式在挂载之后、在 effect 里才套上去,那时候才真的有一个读者可以去贴合。这个包把每一个页面都静态导出,所以另一条路就是:任何一个带日期的页面都会水合不匹配。
datetime 属性从第一次渲染起就是完整的 ISO 时刻,并且从不改变,所以读屏软件、爬虫,或者任何解析这份标记的东西,拿到的都是准确的那一刻——不管那个 effect 跑没跑过。
它每次挂载只格式化一次。一百行各自挂一个定时器、好让“3 分钟前”保持诚实,是一笔没人要过的开销;真的需要走秒的列表,请从上面重新 key 或者重新渲染。
结构
| 部件 | 说明 |
|---|---|
| Element必填 | A <time> whose datetime is the full ISO instant from the very first render and never changes, so anything parsing the markup gets the exact moment whether or not the effect has run. |
| First paint必填 | The UTC calendar date, sliced straight out of the ISO string with no Intl anywhere near it. It is what the server renders and what the client renders while hydrating — both sides compute it from the same characters, so they cannot disagree. |
| Local reading必填 | Applied after mount, in an effect, where there is a reader to be local to: Intl.RelativeTimeFormat with numeric "auto" for the relative form, Intl.DateTimeFormat at dateStyle medium for the absolute one. |
| Missing value | A value nothing can parse renders an em dash at --ink-3-aa and no <time> at all, because an element whose datetime cannot be written is not a time. |
实践建议
推荐
- Leave format on auto for a record list. It reads relative while the gap is under relativeWithin — a week by default — and switches to the calendar date past it, which is both the more useful fact and the one that stops changing.
- Pass the instant, not a formatted string. A Date, an ISO string or epoch milliseconds all work, and all three end up as the same ISO datetime attribute.
- Re-render from above when a list has to tick. It formats once per mount on purpose: a hundred rows each holding an interval to keep "3 minutes ago" honest is a cost nobody asked for.
避免
- Do not expect the relative text in the server-rendered HTML. The first paint is deliberately the UTC date — a crawler, a static export and a test reading markup all see 2026-01-14, and only a mounted browser sees "3 hours ago".
- Do not format a date beside it with toLocaleString. The two would disagree the moment one page renders on a build server, which is exactly the hydration mismatch this component is built around.
- Do not use it for a duration. It renders an instant relative to now; "2m 14s of build time" is a length, not a moment, and belongs in a plain string.
示例
relative and absolute
The same instant, three ways. Every page here is statically exported, so the first paint — the one the build produces and the browser has to reproduce exactly while hydrating — is the UTC calendar date, sliced out of the ISO string with no Intl involved. The relative and locale-aware forms are applied after mount, where there is a reader to be local to. The datetime attribute is the full ISO instant from the first render and never changes.
- 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
The real use, and the reason the component exists rather than a call to toLocaleString at each site. auto is doing the work down this column: the recent rows read as a relative gap and the older ones as calendar dates, and the switch happens at a week, because past that the date is both the more useful fact and the one that stops changing. It formats once per mount — a hundred rows each holding a ticking interval to keep three hours ago honest is a cost nobody asked for, so a list that must tick re-renders from above.
| Commit | Finished | Outcome |
|---|---|---|
| a1b2c3d | Deployed | |
| 9f8e7d6 | Deployed | |
| 4c5b6a7 | Rolled back | |
| 77aa2b1 | Deployed |
what the markup carries
The same instant three times, beside the string every one of them is built from. This page is statically exported, so the first paint — the one the build produces and the browser has to reproduce exactly while hydrating — is the UTC calendar date sliced straight out of that string, with no Intl anywhere near it: both sides compute it from the same characters, so they cannot disagree. The locale-aware and relative forms are applied after mount, where there is a reader to be local to. The datetime attribute is the full instant from the first render onwards and never changes, which is what a screen reader or a crawler reads whether or not the effect has run.
- The value passed in
2026-01-14T09:30:00.000Z- auto, past its window
- absolute, with the time
- relative, however old
类型
export type TimestampFormat = 'auto' | 'relative' | 'absolute'无障碍
- datetime 属性从第一次渲染起就带着精确的 ISO 时刻,所以读机器值的辅助技术从不依赖某个 effect 跑没跑过。
- 看得见的文字在挂载后变一次,机器值从头到尾不变,播报出来的值和解析出来的值因此始终一致。
- 解析不了的值渲染成一个破折号,而不是浏览器那句原样的“Invalid Date”——那是工程的残渣,不是该摆到读者面前的东西。