Skip to main content

svelte-runes

Svelte 5 Runes 响应式系统技能。当用户需要使用 $state/$derived/$effect/$props/$bindable/$inspect/$host 等符文,或理解 Svelte 5 显式响应式与 Svelte 4 隐式响应式的区别时使用。

Jump to install

Source facts

Repository
full-stack-skills/svelte-skills
Last source activity
September 11, 2026 at 13:43
Detected SKILL.md language
Chinese
Stars
3
Forks
2

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
18 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
svelte-runes
license
Apache-2.0
description
Svelte 5 Runes 响应式系统技能。当用户需要使用 $state/$derived/$effect/$props/$bindable/$inspect/$host 等符文,或理解 Svelte 5 显式响应式与 Svelte 4 隐式响应式的区别时使用。
# Svelte Runes Reference (Svelte 5) 本技能覆盖 Svelte 5 的 Runes(符文)系统。Runes 是 Svelte 5 引入的显式响应式语法,取代了 Svelte 4 的隐式 `let` 声明和 `$:` 语句。 ## Capability Boundaries ### ✅ 强项 1. Svelte 5 显式响应式系统(Runes)的完整参考:$state / $derived / $effect / $props / $bindable / $inspect / $host 2. 详细的子段(Proxy 行为、依赖追踪、cleanup、pre/tracking/pending/root 等) 3. 跨模块共享状态模式 + `.svelte.js` 文件约定 4. Svelte 4 → Svelte 5 迁移提示(隐式 vs 显式响应式) 5. 完整的 Quick Fixes / Gotchas / FAQ 速查 ### ⚠️ 限制 1. 本技能专注于 Svelte 5 Runes 体系,不覆盖 Svelte 4 隐式响应式(`let` + `$:`) 2. 不包含动画 / 过渡 / 模板语法的深入内容(改用 `svelte-template-syntax`) 3. 不包含组件生命周期 / stores / context 的高级用法(仅在 Svelte Runes 相关处提及) ### ❌ Out of Scope(不该用本技能的场景) 1. **Svelte 4 隐式响应式** → 不适用,改用 Svelte 4 文档 2. **模板块语法({#if}/{#each}/{#await}/{#snippet})** → 改用 `svelte-template-syntax` 3. **transition/animate** → 改用 `svelte-template-syntax` 4. **Stores(writable/derived)** → 改用 Svelte 5 文档对应章节(与 Runes 互补) ## Data Privacy 本技能不收集、存储或传输任何用户数据。所有代码示例仅用于本地开发参考。 ## Workflow Step 1. **识别用户需要的 Rune** — `$state` / `$derived` / `$effect` / `$props` / `$bindable` / `$inspect` / `$host` Step 2. **查对应章节** — SKILL.md 给概览,深度细节见 `references/$X-deep.md` Step 3. **必要时看示例** — `examples/$X-patterns.md` 提供可复制代码 Step 4. **查 Quick Fixes / Gotchas / FAQ** — 三个速查表覆盖常见错误 Step 5. **Svelte 4 迁移场景** — 关注"Runes Overview"对比段 ## When to use this skill 当用户需要理解或使用 `$state`、`$derived`、`$effect`、`$props`、`$bindable`、`$inspect`、`$host` 等符文,或需要将 Svelte 4 代码迁移到 Svelte 5 时使用本技能。 ## Critical: Runes Overview Runes 是以 `$` 为前缀的符号,类似于函数调用语法: ```js let message = $state('hello'); ``` 关键区别: - **无需导入** — Runes 是语言内置关键字 - **不是值** — 不能赋值给变量或作为函数参数 - **位置敏感** — 仅在特定位置有效(编译器会报错) | Rune | 用途 | |------|------| | `$state` | 创建响应式状态 | | `$derived` | 声明派生计算值 | | `$effect` | 声明副作用 | | `$props` | 声明组件属性 | | `$bindable` | 可绑定 prop | | `$inspect` | 开发调试 | | `$host` | 自定义元素访问 | ## Critical: $state 创建响应式状态,UI 在状态变化时自动更新。 ```svelte let count = $state(0); let user = $state({ name: 'Ada', age: 30 }); ``` ### 深层响应式代理 `$state` 对数组和简单对象自动创建深层 Proxy,属性变更自动触发更新: ```js let todos = $state([ { done: false, text: 'task' } ]); todos[0].done = true; // ✅ 触发更新 todos.push({ done: false }); // ✅ 触发更新 ``` ### $state.raw(避免深层代理) 适用于大数组和无需深层变更的场景: ```js let list = $state.raw([]); // 只能重新赋值,不能 .push() list = [...list, newItem]; // ✅ list.push(newItem); // ❌ 无效 ``` > `$state.raw` 内部仍可包含响应式状态(如原始数组里放代理对象)。 ### $state.snapshot(静态快照) 获取深层代理的只读快照(用于传给外部 API): ```svelte console.log($state.snapshot(proxyObject)); ``` > 若值有 `toJSON()` 方法,snapshot 会克隆 `toJSON()` 的返回值。 ### $state.eager(即时 UI 更新) 用于 `await` 表达式中立即更新 UI: ```svelte <nav> <a href="/" aria-current={$state.eager(pathname) === '/' ? 'page' : null}>home</a> </nav> ``` ### 类字段中的 $state ```js class Counter { count = $state(0); // 公共字段 #value = $state(0); // 私有字段 constructor(start = 0) { this.value = $state(start); // constructor 中初始化 } // 箭头函数字段 → this 自动绑定 reset = () => { this.count = 0; }; } ``` > 编译器将 `$state` 字段转换为原型上的 `get`/`set` 方法,指向私有字段。**这些属性不可枚举**。注意 `this` 绑定:方法直接传给事件处理器会丢失 `this`,用箭头函数字段或内联 `() => todo.reset()`。 ### 内置响应式类(svelte/reactivity) 普通 `Map`/`Set`/`Date`/`URL` 不响应 —— 改用响应式版本: ```js import { SvelteSet, SvelteMap, SvelteDate, SvelteURL } from 'svelte/reactivity'; const tags = new SvelteSet(['a', 'b']); const cache = new SvelteMap(); tags.add('c'); // ✅ 触发更新 cache.set('a', 1); // ✅ 触发更新 ``` ### 解构陷阱 解构后丢失响应式(与普通 JS 行为一致): ```js let { name, age } = $state({ name: 'Ada', age: 30 }); name = 'Bob'; // ❌ 不会触发更新,原对象不变 ``` ### 跨模块传递状态 `.svelte.js/.svelte.ts` 文件中可使用 Runes,但不能直接 `export let` 重新赋值的 `$state`: ```js // ❌ 不可行:另一文件读到的会是 Signal 对象 export let count = $state(0); // ✅ 方案1:不重新赋值整个对象 export const counter = $state({ count: 0 }); export function increment() { counter.count += 1; } // ✅ 方案2:模块内私有 + 函数导出 let _count = $state(0); export function getCount() { return _count; } export function setCount(n) { _count = n; } ``` ### 将 state 传入函数 —— getter 模式 JavaScript 是**按值传递**,`$state` 同理。若函数需读取最新值,传入 getter: ```js /** @param {() => number} getA @param {() => number} getB */ function add(getA, getB) { return () => getA() + getB(); } let a = $state(1); let b = $state(2); const total = add(() => a, () => b); console.log(total()); // 3 a = 3; b = 4; console.log(total()); // 7 ``` > 也可借助 proxy 属性或 get/set 属性实现"实时读取"。文档:"Note that 'functions' is broad — it encompasses properties of proxies and get/set properties." ## Critical: $derived 声明派生值——基于已有状态的只读计算值。 ```svelte let count = $state(0); let doubled = $derived(count * 2); ``` > 表达式**必须纯净** —— 内部不能修改 `$state`(编译器报错)。 ### $derived.by(复杂派生) ```svelte let total = $derived.by(() => { let sum = 0; for (const item of items) sum += item.price; return sum; }); ``` ### 派生值覆盖(Svelte 5.25+,乐观 UI) ```svelte let likes = $derived(post.likes); async function onclick() { likes += 1; // 即时乐观更新 try { await like(); } catch { likes -= 1; // 回滚 } } ``` > `const` 派生只读;用 `let` 才能重新赋值。文档:"Prior to Svelte 5.25, deriveds were read-only." ### 理解依赖追踪 `$derived` 内部同步读取的所有 `$state`/`$derived` 都是依赖: ```js let a = Promise.resolve(1); let b = 2; let sum = $derived(await a + b); // a 和 b 都是依赖(await 之后的同步读取也会追踪) ``` > 仅**表达式自身**的 `await` 后的同步读才算依赖;**调用函数**内部的 `await` 不计入。 用 `untrack` 排除非依赖值。 ### Push-pull 响应 派生值**只在被读取时**重新计算(pull),但状态变化时**立即通知**所有依赖(push)。若派生返回的引用未变,下游不更新: ```svelte let count = $state(0); let large = $derived(count > 10); // 布尔 // 大文本节点只在 large 变化时重渲染,而非 count ``` > 派生内避免 `() => ({})`、`[...].map(...)` 这类返回新引用的写法 —— 会让下游始终重算。 ### 派生不解构代理 `$derived` 不会把返回值包成 Proxy —— 修改派生返回对象的属性会影响到底层 `$state`: ```js let items = $state([...]); let selected = $derived(items[0]); selected.name = 'new'; // ✅ 影响 items[0].name ``` ### 派生解构 ```js let { first, last } = $derived(user); // 等价于: // let first = $derived(user.first); // let last = $derived(user.last); ``` ## Critical: $effect 声明副作用——DOM 操作、第三方库调用、网络请求等。**不应**用 `$effect` 同步状态。 ```svelte $effect(() => { document.title = `count: ${count}`; return () => { /* 清理函数 */ }; }); ``` ### 生命周期 - 挂载后**首次执行** - 状态变化时 **microtask 调度**(批量) - DOM 更新**之后**执行 - 仅浏览器执行(SSR 自动跳过) - 可在组件任意位置调用,只要在父 effect 运行期间 ### 依赖追踪 自动追踪 `$state`/`$derived` 的同步读取: ```svelte $effect(() => { // color 和 size 是依赖 ctx.fillStyle = color; ctx.fillRect(0, 0, size, size); }); ``` ### 异步不追踪 `await` 之后和 `setTimeout` 内部的读取**不**追踪: ```js $effect(() => { ctx.fillStyle = color; // ✅ 追踪 setTimeout(() => { ctx.fillRect(0, 0, size, size); // ❌ size 不追踪 }, 0); }); ``` ### 条件分支决定依赖 effect **只追踪上次运行时实际读取的状态**: ```ts $effect(() => { if (condition) { confetti({ colors: [color] }); } else { confetti(); } }); // condition=false 时 color 不再是依赖 ``` ### $effect.pre(DOM 更新前运行) ```svelte $effect.pre(() => { if (!div) return; // 挂载前跳过 messages.length; // 显式依赖追踪 // DOM 更新前执行(如滚动位置计算) }); ``` ### $effect.tracking(判断追踪上下文) ```svelte console.log($effect.tracking()); // false(组件初始化) $effect(() => { console.log($effect.tracking()); // true(effect 内) }); ``` > 用于 `createSubscriber` 这类工具:仅在状态被追踪时订阅,事件处理器中不订阅。 ### $effect.pending()(待定 Promise 数量) 返回**当前 boundary**(不含子 boundary)中待定的 Promise 数量: ```svelte <script> let a = $state(1); let b = $state(2); async function add(a, b) { await new Promise(f => setTimeout(f, 500)); return a + b; } </script> <button onclick={() => a++}>a++</button> <button onclick={() => b++}>b++</button> <p>{a} + {b} = {await add(a, b)}</p> {#if $effect.pending()} <span>pending: {$effect.pending()}</span> {/if} ``` ### $effect.root()(独立作用域) 创建**非追踪**作用域,不随组件销毁自动清理: ```js import { flushSync } from 'svelte'; const destroy = $effect.root(() => { let count = $state(0); $effect(() => console.log(count)); return () => { /* cleanup */ }; }); count = 1; flushSync(); // 立即跑 pending effects // later... destroy(); ```
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub