番茄钟也能很简单:Svelte + Vite的状态管理实践
一、背景:番茄钟是练前端响应式的绝佳小题
番茄工作法(25 分钟专注、5 分钟休息,循环)很多人都知道,但真用 App 计时,要么功能花哨、要么要账号。自己写一个最朴素的番茄钟——开始/暂停/重置、到点响一下、记录完成了几个——既能用,又能把"状态怎么驱动界面"这件前端基本功练一遍。
选 Svelte 是因为它在这类小工具上特别顺手:没有虚拟 DOM 那套、状态用 let 加 $: 响应式语句就能驱动界面,状态管理几乎没有额外概念,比 React 的 useState/useEffect 心智负担小。本文用 Svelte + Vite 写一个番茄钟,讲清响应式、计时器和持久化。具体 API 以 Svelte 官方文档为准。
二、Svelte 的响应式:赋值即更新
Svelte 的核心理念是"对状态的赋值,自动触发依赖它的界面更新"。不需要 setState、不需要 ref.value,就是普通的变量赋值:
<script>
let seconds = 0 // 已计秒数
let running = false
$: display = format(seconds) // $: 表示"响应式语句",seconds 变它就重算
function format(s) {
const m = String(Math.floor(s/60)).padStart(2,'0')
const ss = String(s%60).padStart(2,'0')
return `${m}:${ss}`
}
</script>
<h1>{display}</h1>
<button on:click={() => running = !running}>{running ? '暂停' : '开始'}</button>
let seconds = 0 就是状态;$: display = format(seconds) 是响应式派生——只要 seconds 变了,display 自动重算,<h1> 跟着变。点按钮 running = !running 也只是赋值,Svelte 自动重渲染用到 running 的地方。这套"赋值即更新"比 React 套 useState 直观很多,小工具尤其省事。
三、计时器:setInterval 驱动秒数
番茄钟的核心是每秒 +1。用 setInterval 在 running 为 true 时跑,暂停时清掉:
<script>
let seconds = 0, running = false, timer = null
const WORK = 25 * 60
function toggle() {
running = !running
if (running) {
timer = setInterval(() => {
seconds += 1
if (seconds >= WORK) { finish() }
}, 1000)
} else {
clearInterval(timer)
}
}
function finish() {
clearInterval(timer); running = false; seconds = 0
pomodoroCount += 1 // 完成一个番茄
alert('专注完成,休息一下')
}
</script>
toggle 在"开始"时建 setInterval、每秒 seconds += 1;"暂停"时 clearInterval 停表。注意 seconds += 1 是赋值,Svelte 会更新界面——这就是 Svelte 的妙处,不用包 setState。到 WORK(25 分钟)调 finish:清定时器、归零、pomodoroCount += 1 记录完成数、提示休息。pomodoroCount 也是普通变量,照样响应式。
四、持久化:今天完成了几个要留着
完成的番茄数、当前设置,刷新页面就丢不好。用 localStorage 落盘:
<script>
let pomodoroCount = Number(localStorage.getItem('pomo') || 0)
$: localStorage.setItem('pomo', pomodoroCount) // 响应式:一变就存
</script>
pomodoroCount 初始化从 localStorage 读;$: localStorage.setItem(...) 是响应式语句——只要 pomodoroCount 变,就写回磁盘。Svelte 的 $: 在这里既做派生显示、又做副作用(持久化),一个语法两种用途,很紧凑。注意 localStorage 只能存字符串,读用 Number(...) 转回数字。
五、几个要注意的点
第一,setInterval 的清理。组件卸载或暂停时必须 clearInterval,否则定时器还在跑、界面已不在,会报"更新已卸载组件"或计数偷偷涨。Svelte 里可以用 onDestroy(() => clearInterval(timer)) 兜底清,别只靠暂停按钮清。
第二,页面后台会节流。浏览器对后台标签页的 setInterval 有节流(可能降到 1 秒一次都不保证),前台切后台再切回,计时可能偏慢。严肃计时场景用"基于时间戳"算(Date.now() 差值)而非单纯累加秒数,更准。番茄钟这种容忍几秒误差的,累加也够用,知道有这限制即可。
第三,**: x = f(a)依赖它"读到的"变量(这里是a)。如果 f` 里偷偷读了别的状态却没出现在赋值右侧,Svelte 可能不重算。保持响应式依赖显式、纯函数,避免隐蔽 bug。
第四,状态别散太多。番茄钟状态就 seconds / running / pomodoroCount 几个,全在组件里。如果状态要跨多个组件共享,用 Svelte 的 writable store(import { writable } from 'svelte/store')集中管理,和前面 Vue 用 Pinia 思路一致,别各组件各一份。
第五,响铃体验。纯 alert 粗糙,真要做可以 <audio> 放一段提示音,或用 Notification API 弹系统通知。演示用 alert 足够说明流程。

六、小结
用 Svelte 写番茄钟,状态管理成本低到几乎"无感":普通 let 是状态、赋值即更新、$: 响应式语句既做派生显示(如 display)又做副作用(如持久化)。计时靠 setInterval 每秒 seconds += 1,到 25 分钟 finish 记一个完成数。比 React 的 useState/useEffect 心智负担小,适合这类小工具。注意定时器要清理、后台标签计时会节流、跨组件状态用 writable store。Svelte 语法和生命周期 API 以官方文档为准。
- 点赞
- 收藏
- 关注作者
评论(0)