feat(ts): 实现 Polaris 可交互首页
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
"use client"
|
||||
|
||||
import { useSyncExternalStore } from "react"
|
||||
|
||||
import { getDelayUntilNextMinute } from "@/features/home/lib/date-time"
|
||||
|
||||
type Listener = () => void
|
||||
|
||||
const listeners = new Set<Listener>()
|
||||
let currentTime: Date | null = null
|
||||
let timeoutId: number | undefined
|
||||
|
||||
function emitCurrentTime(): Date {
|
||||
const nextTime = new Date()
|
||||
currentTime = nextTime
|
||||
listeners.forEach((listener) => listener())
|
||||
return nextTime
|
||||
}
|
||||
|
||||
function scheduleNextTick() {
|
||||
const observedTime = emitCurrentTime()
|
||||
|
||||
// 每次 tick 都重新对齐;标签页休眠或主线程阻塞后不会把延迟永久带入后续分钟。
|
||||
timeoutId = window.setTimeout(
|
||||
scheduleNextTick,
|
||||
getDelayUntilNextMinute(observedTime),
|
||||
)
|
||||
}
|
||||
|
||||
function startClock() {
|
||||
scheduleNextTick()
|
||||
}
|
||||
|
||||
function stopClock() {
|
||||
if (timeoutId !== undefined) window.clearTimeout(timeoutId)
|
||||
timeoutId = undefined
|
||||
currentTime = null
|
||||
}
|
||||
|
||||
function subscribe(listener: Listener) {
|
||||
listeners.add(listener)
|
||||
if (listeners.size === 1) startClock()
|
||||
|
||||
return () => {
|
||||
listeners.delete(listener)
|
||||
if (listeners.size === 0) stopClock()
|
||||
}
|
||||
}
|
||||
|
||||
function getSnapshot() {
|
||||
return currentTime
|
||||
}
|
||||
|
||||
function getServerSnapshot() {
|
||||
return null
|
||||
}
|
||||
|
||||
export function useClock(): Date | null {
|
||||
// 服务端与 hydration 首帧固定为空;订阅建立后再读取浏览器时间,避免首屏不一致。
|
||||
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
|
||||
}
|
||||
Reference in New Issue
Block a user