Svelte Cheatsheet - Svelte Framework Reference
For frontend developers who want less boilerplate and a minimal runtime. Svelte compiles reactivity directly into native DOM updates at build time, so the runtime is tiny and there is no virtual-DOM mental model; but it lives on assignment-driven updates, $: recomputation, and Store subscriptions that take getting used to. By the end you can declare reactive state with let and $:, communicate between components via events and dispatch, share cross-component state with Store, and hook into lifecycle and transitions.
Typical Use Case
For developers moving over from React or Vue, or working on projects where component weight is a real concern. The sheet follows the three main lines of Svelte: template block syntax handles structure, assignment-driven reactivity handles data, and actions plus transitions handle interaction. Above that sit lifecycle hooks for side effects, tick for waiting on DOM updates, and stores for pulling state out of components so it can be shared and torn down cleanly.
Template Syntax 6
{ expression }{@html rawHtmlString}{#if condition}...{:else if}...{:else}...{/if}{#each items as item, index}...{/each}{#await promise}...{:then value}...{:catch error}...{/await}{#key expression}...{/key}Reactive Declarations 5
let count = 0;$: doubled = count * 2;$: { console.log(count); }arr = [...arr, newItem];obj = { ...obj, key: value };Components & Props 7
export let name;export let name = "default";$$props$$restProps<slot /><slot name="header" /><slot let:item={item} />Events & Bindings 7
<button on:click={handler}><form on:submit|preventDefault={handler}>import { createEventDispatcher } from "svelte"; const dispatch = createEventDispatcher(); dispatch("event", data);<input bind:value={name}><input bind:this={inputEl}><input type="checkbox" bind:group={selected}><input type="radio" bind:group={choice}>Lifecycle 6
import { onMount } from "svelte"; onMount(() => { ... });import { beforeUpdate } from "svelte"; beforeUpdate(() => { ... });import { afterUpdate } from "svelte"; afterUpdate(() => { ... });import { onDestroy } from "svelte"; onDestroy(() => { ... });import { tick } from "svelte"; await tick();import { untrack } from "svelte"; untrack(() => { ... });Store 7
import { writable } from "svelte/store"; export const count = writable(0);import { readable } from "svelte/store"; export const time = readable(new Date(), (set) => { ... });import { derived } from "svelte/store"; export const doubled = derived(count, ($c) => $c * 2);$countcount.set(10);count.update(n => n + 1);import { get } from "svelte/store"; const val = get(count);Actions & Transitions 7
use:action={{ param }}transition:fadetransition:slidetransition:scalein:fly={{ x: 200, duration: 500 }}out:fly={{ x: -200 }}animate:flipAdvanced Features 5
import { setContext, getContext } from "svelte"; setContext("key", value); const val = getContext("key");import { hasContext } from "svelte"; hasContext("key");<script context="module">...</script>export const greet = (name) => `Hello ${name}`;svelte:optionsParameter matrix
| Parameter | Effect | Example |
|---|---|---|
{ expression } | 模板中插入 JavaScript 表达式 | <h1>{user.name}</h1> |
{@html raw} | 渲染原始 HTML 字符串,存在 XSS 风险 | {@html post.bodyHtml} |
{#if}...{:else if}...{:else} | 条件渲染分支 | {#if loading}<Spinner />{:else}<List />{/if} |
{#each items as item, i} | 遍历数组渲染,第二项为下标 | {#each todos as todo, i}{i}. {todo.text}{/each} |
{#await promise} | 按 pending/then/catch 三态渲染异步结果 | {#await loadUser()}<Spinner />{:then u}<Profile {u} />{/await} |
{#key expression} | 表达式变化时销毁并重建内部内容 | {#key activeTab}<Panel {tab} />{/key} |
$: doubled = count * 2 | 响应式声明,依赖变化时自动重算 | $: total = price * qty |
arr = [...arr, x] | 数组更新必须赋新引用才触发更新 | items = [...items, newItem] |
obj = { ...obj, k: v } | 对象更新同样要整体赋新引用 | state = { ...state, open: true } |
export let name | 声明组件接收的 prop | export let name; |
$$restProps | 读取未显式声明的其余 props | <input {...$$restProps} /> |
<slot name="header" /> | 具名插槽,供父组件填充指定区域 | <slot name="header" /> |
<slot let:item={item} /> | 作用域插槽,把子组件数据回传父组件 | <slot let:item={item}>{item.label}</slot> |
on:click|preventDefault | 绑定 DOM 事件并使用事件修饰符 | <form on:submit|preventDefault={save}> |
createEventDispatcher() | 子组件向父组件派发自定义事件 | dispatch('change', data) |
bind:value / bind:this | 输入值双向绑定 / 绑定 DOM 元素引用 | <input bind:value={name} bind:this={inputEl} /> |
bind:group | 复选框组绑定到数组、单选框组绑定到变量 | <input type="checkbox" bind:group={selected} value={id} /> |
onMount / onDestroy | 挂载后执行请求 / 销毁前清理定时器与订阅 | onDestroy(() => clearInterval(timer)) |
await tick() | 等待 DOM 更新完成后再读取元素状态 | await tick(); el.focus(); |
untrack(() => ...) | 在响应式声明中临时取消依赖追踪 | untrack(() => log(count)) |
writable / readable / derived | 创建可写、只读、派生三种 Store | const doubled = derived(count, (n) => n * 2) |
get(store) | 在非组件上下文同步读取 Store 当前值 | const val = get(count) |
use:action | 自定义动作函数操作 DOM 元素 | use:tooltip |
transition:fade / slide / scale | 元素出现与消失时播放过渡动画 | transition:slide |
in:fly={{ x: 200 }} | 进入动画(飞入),out: 对应离开动画 | in:fly={{ x: 200, duration: 500 }} |
animate:flip | 列表元素位置变化时补间动画(FLIP) | animate:flip={{ duration: 200 }} |
setContext / getContext | 跨层级组件通信,hasContext 判断是否存在 | setContext('theme', 'dark') |
svelte:options | 编译器选项,如 immutable 关闭深度代理 | <svelte:options immutable={true} /> |
context='module' | 模块级脚本,每个组件实例只执行一次 | <script context="module"> |
Common pitfalls
Symptompush 了数组元素、设置了对象属性,界面却不更新
CauseSvelte 的响应式基于赋值追踪,push 与 obj.x = v 这类原地修改不会触发重新赋值
Fix改用新引用写法:arr = [...arr, newItem]、obj = { ...obj, key: value }
Symptom异步数据回来后 DOM 还没更新就去读 input.value,结果拿到旧值
Cause赋值只排入了更新队列,DOM 渲染在下一个微任务才发生
Fix在赋值后 await tick(),等 DOM 更新完成再读取元素状态
Symptom组件销毁后仍在打接口,控制台刷请求或出现 setState 警告
Cause在 onMount 里发起的请求没有随组件销毁取消
Fix把 AbortController 或取消句柄存在变量里,在 onDestroy 里取消请求并清理定时器
Symptom响应式声明里只想读一次值,却被无关变量变化触发重算
Cause$: 块会自动收集块内所有读取的依赖,包含非响应式来源的读取
Fix把纯读取放进 untrack(() => ...),让它不参与依赖追踪
Symptom切换列表项状态后,动画错乱或复用了上一次的 DOM
Cause列表里缺少 key 语义,Svelte 按位置复用元素
Fix用 {#key expression} 包住需要重建的内容,或调整 {#each} 的来源结构
Symptom{@html} 渲染用户提交内容后出现脚本被执行
Cause{@html} 直接把字符串当 HTML 解析输出,不做转义
Fix对用户输入先做净化,或改用普通 { expression } 由 Svelte 自动转义
Symptom在组件外部或事件回调里读 Store 拿到 undefined
Cause$store 自动订阅语法只在组件顶层可用,脱离组件后没有订阅者
Fix在非组件上下文用 get(store) 同步取值,或改用 store.subscribe() 手动订阅
Troubleshooting
1确认某个状态是否真的触发了重新渲染
$: console.log(count);在响应式语句块里打印依赖变量,控制台出现新输出说明赋值已被追踪到。
2确认数组/对象更新是否走了新引用
arr = [...arr, newItem];把原地 push 改成整体赋值;若界面随即更新,问题就出在原来那行。
3在 DOM 更新后立即聚焦或测量元素
await tick();tick() 之后的 DOM 已是最新状态,适合 focus()、getBoundingClientRect() 等操作。
4把不该被追踪的读取排除出响应式声明
untrack(() => render(count));日志里若看到 render 因无关变量被重复调用,就把该读取包进 untrack。
5强制重建一段内容而不是复用
{#key activeId}<Panel data={activeId} />{/key}内容切到别的数据源后仍显示旧内容时,用 {#key} 显式声明重建边界。
6在组件之外读取 Store 的当前值
const val = get(count);get() 同步读出当前值,适用于事件回调、定时器、模块顶层等无组件上下文处。
7给子组件派发事件并在父组件接收
dispatch('change', data);父组件用 on:change={handler} 接收,detail 里就是 dispatch 传出的 data。
Command Examples
Render an indexed list with each and the index
{#each items as item, index}
<li>{index + 1}. {item}</li>
{/each}index is the second binding of {#each} and can be used directly in markup. Supply a stable key with the (item.id) form; keying on the object itself rebuilds the whole block whenever the data is recreated, which is slower, not faster.
The await block covers loading, success, and failure states
{#await promise}
<p>加载中…</p>
{:then user}
<p>{user.name}</p>
{:catch error}
<p>{error.message}</p>
{/await}If you write only {#await promise}…{/await} with no :then and :catch, a rejected promise shows no feedback at all — the UI just keeps looking like it is loading. Writing all three branches is the cheapest way to avoid silent failure.
Use get() outside components instead of the store-value sugar
export const count = writable(0);
count.update((n) => n + 1);
// 组件外读值:
import { get } from "svelte/store";
const val = get(count);$count is compile-time sugar and only works inside a component or .svelte file. Module-level event handlers and utility functions must use get(). Writing $count there is a compile error whose stack often points at a distant, seemingly unrelated file.
Fetch on mount, clean up on destroy
import { onMount, onDestroy } from "svelte";
let timer;
onMount(() => {
timer = setInterval(refresh, 5000);
});
onDestroy(() => {
clearInterval(timer);
});onMount runs on every mount, and conditional rendering unmounts the component while the timer, subscription, or socket keeps running. Teardown belongs in onDestroy; otherwise toggling the component in and out of the page stacks up duplicate timers.
derived computes from an existing store instead of duplicating state
export const doubled = derived(count, (value) => value * 2);A common mistake is copying a derived value into a component-local variable, which creates two sources of truth. derived makes it a store: it recomputes automatically and every subscriber sees the same value.
Common Pitfalls
- {@html rawHtmlString} injects raw markup, so anything from a user or an API is an open XSS hole. Use a plain interpolation unless you truly know the content is trusted.
- Assigning to a child component's export let prop silently mutates the parent's state. Props are a one-way source; use events or bind to change state across a boundary.
- A $: reactive statement only tracks variables that appear directly in it. Hide the value inside an object or pass it through a function and the dependency is never registered, so the UI stops updating.
- A request started in onMount with no check for an unmounted component throws or leaks when the response lands. Guard it with a flag in the returned cleanup function.
- {#key} destroys and rebuilds everything inside it every time the expression changes. Placed outside an #each, or bound to something that changes often, it rebuilds whole DOM subtrees repeatedly.
- Outside SSR-safe paths, code touching window or document crashes on the server. Guard the type check, or access them only after mount.
Tips
- Svelte is a compiled framework with no runtime, producing minimal code
- Reactivity is triggered by assignment; arrays and objects need new assignments
- $: reactive declarations track any dependency changes
- Store manages state outside components, $store syntax auto-subscribes
- Svelte 5 introduces Runes ($state/$derived/$effect) new reactivity system
FAQ
Why does the view not update when I push an item onto an array?
Svelte reactivity is assignment-driven, so arrays and objects must be replaced with a new reference to trigger an update. Use arr = [...arr, newItem] to append and obj = { ...obj, key: value } to change a field, never arr.push(x) or obj.k = v, which mutate in place. With Svelte 5 you can wrap the value in a $state rune for an explicitly reactive container, which follows the same replace-the-reference rule. Change those two spots and the template re-renders.
How does a $: reactive statement differ from a plain computed variable?
$: doubled = count * 2; is reactive: the compiler inspects the right-hand side, records which variables it depends on, and recomputes automatically when count changes — the same applies to a statement block such as $: { console.log(count); }. A plain const or let is evaluated once at declaration and never reruns. Reactive declarations must live at the top level of the component script and are ordered by their dependency graph. The common trap is a variable introduced inside the block becoming a dependency itself and looping; when that happens, wrap the part in question with untrack(() => { ... }) to stop dependency tracking.
When should shared state live in a Store instead of a component variable?
Reach for a store when state is shared across components or should be managed outside the component tree. Create one with import { writable } from "svelte/store", read it in components as $count — the subscription is automatic and torn down on destroy — and write with count.set(10) or count.update(n => n + 1) for an update based on the current value. Use readable for write-protected state and derived(count, ($c) => $c * 2) for values computed from other stores. Outside a component the $ prefix does not exist, so read the value with get(count).
How do slots and props divide the job of passing content into a child component?
Structured values travel as props: export let name declares what the component receives, export let name = "default" supplies a default, $$props exposes everything passed in and $$restProps only the ones you did not declare. Template content travels through <slot />, and when a child needs several insertion points use named slots such as <slot name="header" />. When the child needs to hand data back up to the parent, use a scoped slot: <slot let:item={item} />.
When do I need a lifecycle hook instead of just calling a function in the component?
Anything touching the DOM or a browser resource belongs in a hook. onMount(() => { ... }) runs after the component mounts, which is where an API request belongs; onDestroy(() => { ... }) runs before teardown and is where you clear timers and unsubscribe, preventing leaks; beforeUpdate and afterUpdate fire either side of a DOM update. await tick() lets you wait until the DOM has actually updated, e.g. to measure an input right after changing it. Pure computation needs none of this — a $: declaration is enough.
Official References
Each command links to its official documentation below, so you can verify the latest usage and read deeper.
Maintained by LaoHand
Publicly updated on Jul 21, 2026, continuously proofread against official docs.
Contact Us
Wrong command or description? Send us corrections, business inquiries or product feedback by email.
Contact Us