Skip to main content

svelte-runtime

Svelte 5 运行时技能。当用户需要使用 Stores、Context、生命周期钩子(onMount/onDestroy/tick)、命令式组件 API(mount/unmount/render/hydrate)、Hydratable Data、Best Practices 或 Vitest 测试时使用。

Ir para a instalação

Informações da origem

Repositório
full-stack-skills/svelte-skills
Última atividade na origem
11 de setembro de 2026 às 13:43
Idioma detectado do SKILL.md
chinês
Estrelas
3
Forks
2

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Explorador de arquivos
11 arquivos

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
svelte-runtime
license
Apache-2.0
description
Svelte 5 运行时技能。当用户需要使用 Stores、Context、生命周期钩子(onMount/onDestroy/tick)、命令式组件 API(mount/unmount/render/hydrate)、Hydratable Data、Best Practices 或 Vitest 测试时使用。
# Svelte Runtime Reference (Svelte 5) 本技能覆盖 Svelte 5 的运行时 API,包括跨组件状态(Stores/Context)、生命周期、命令式 API、SSR 和测试。 ## When to use this skill 当用户需要跨组件共享状态、处理组件生命周期、使用 `mount`/`render` 等命令式 API、或编写 Vitest 组件测试时使用本技能。 ## Critical: Stores (svelte/store) Store 是一种跨组件共享响应式状态的老方式(Svelte 5 推荐用 `.svelte.js` 中的 `$state` 对象替代)。 ### writable ```js import { writable } from 'svelte/store'; const count = writable(0); count.subscribe(v => console.log(v)); // 订阅 count.set(1); // 设置值 count.update(n => n + 1); // 更新 ``` ### readable(不可从外部设置) ```js import { readable } from 'svelte/store'; const time = readable(new Date(), (set) => { const interval = setInterval(() => set(new Date()), 1000); return () => clearInterval(interval); }); ``` ### derived ```js import { derived } from 'svelte/store'; const doubled = derived(count, $c => $c * 2); const combined = derived([a, b], ([$a, $b]) => $a + $b); ``` ### readonly ```js import { readonly, writable } from 'svelte/store'; const readOnlyStore = readonly(writableStore); ``` ### 在组件中使用 ```svelte <script> import { count } from './store.js'; console.log($count); // $ 前缀订阅 $count = 2; // 自动调用 .set() </script> ``` > Store 必须在组件顶层声明;不要在变量前加 `$` 前缀(那是 `$count`,不是 `count`)。 **Svelte 5 推荐替代方案**: ```js // state.svelte.js export const userState = $state({ name: '', count: 0 }); // 其他组件直接 import ``` ## Critical: Context (svelte) 跨组件层级传递数据,无需 props 层层透传(prop-drilling): ### setContext / getContext ```svelte // Parent.svelte <script> import { setContext } from 'svelte'; setContext('key', 'hello'); </script> // Child.svelte <script> import { getContext } from 'svelte'; const msg = getContext('key'); </script> ``` ### createContext(推荐,类型安全) ```ts // context.ts import { createContext } from 'svelte'; export const [getUserContext, setUserContext] = createContext<User>(); ``` ```svelte // Parent.svelte <script> setUserContext({ name: 'Ada' }); </script> // Child.svelte <script> const user = getUserContext(); </script> ``` ### 响应式 Context 将 `$state` 对象存入 Context: ```svelte setContext('counter', counterState); // ❌ 不要重新赋值整个对象 // counterState = { count: 0 } // ✅ 直接修改属性 counterState.count = 0; ``` ## Critical: Lifecycle Hooks > **Mental model**: Svelte 5 的生命周期只有两个阶段 —— **创建** 与 **销毁**。中间的"状态更新"由 **effect** 处理,因为响应式的最小单位是 effect,不是组件。所以没有 `beforeUpdate`/`afterUpdate` 钩子。 ### onMount 组件挂载到 DOM 后执行(SSR 时不运行): ```svelte <script> import { onMount } from 'svelte'; onMount(() => { console.log('mounted'); return () => console.log('unmounted'); // 返回清理函数 }); </script> ``` > 返回的函数在组件卸载时调用;如果传入 async 函数则清理函数不会被调用(async 函数总是返回 `Promise`)。需要异步任务 + 清理时,用 IIFE 包裹,外部箭头函数同步返回清理函数。 ### onDestroy 组件卸载前执行(SSR 时也运行): ```svelte <script> import { onDestroy } from 'svelte'; onDestroy(() => cleanup()); </script> ``` ### tick 等待 DOM 更新完成后再继续: ```svelte <script> import { tick } from 'svelte'; $effect.pre(() => { // DOM 更新前 tick().then(() => { // DOM 已更新完成 }); }); </script> ``` ### 生命周期与 mount/unmount/hydrate 的配合 | 阶段 | mount() | hydrate() | render() (SSR) | unmount() | |---|---|---|---|---| | 同步:组件 script、state、模板 | ✓ | ✓ | ✓ | — | | 同步:插入 DOM / 输出字符串 | ✓ 替换 target | ✓ 复用 SSR DOM | ✓ 返回 head+body | — | | 微任务:`$effect` / `onMount` 触发 | ✓ | ✓ | ✗ | — | | 同步:清理 effect + onDestroy | — | — | ✓(render 完成后) | ✓ | | 异步:过渡 (outro) | — | — | — | `outro:true` 时等待 | > `mount()` 返回时 effect 和 `onMount` 都还没跑;要立刻让它们跑就 `flushSync()`。`hydrate()` 同理。 > `tick()` 和 `flushSync()` 在服务端是 no-op(没有响应式调度)。 ## Critical: Imperative Component API ### mount(客户端挂载) ```js import { mount } from 'svelte'; import App from './App.svelte'; const app = mount(App, { target: document.querySelector('#app'), props: { name: 'world' } }); ``` ### unmount(卸载) ```js import { mount, unmount } from 'svelte'; unmount(app, { outro: true }); // 播放过渡后卸载 ``` ### render(服务端渲染) ```js import { render } from 'svelte/server'; const { head, body } = await render(App, { props: { data } }); ``` ### hydrate(客户端水合) 复用 SSR HTML 并激活为交互式: ```js import { hydrate } from 'svelte'; const app = hydrate(App, { target: document.querySelector('#app') }); ``` ## Critical: Hydratable Data SSR 时序列化数据,客户端水合时复用(避免重复请求): ```svelte <script> import { hydratable } from 'svelte'; const user = await hydratable('user', () => getUser()); </script> <h1>{user.name}</h1> ``` ### CSP 支持 `hydratable()` 会在 `render()` 返回的 `head` 中嵌入一段 inline `<script>`。CSP 默认会拦截它,需要用 nonce 或 hash 放行: ```js // 动态 SSR:每请求一个 nonce const nonce = crypto.randomUUID(); const { head, body } = await render(App, { csp: { nonce } }); // 响应头同步加上: // Content-Security-Policy: script-src 'self' 'nonce-${nonce}' ``` ```js // 静态 SSG:使用 hash const { head, body, hashes } = await render(App, { csp: { hash: true } }); // Content-Security-Policy: script-src ${hashes.script.map(h => `'${h}'`).join(' ')} ``` **重要**: - 没用 `hydratable()` 时 `render()` 不输出 inline `<script>`,CSP 严格模式不会拦截任何东西。 - inline 事件处理器(`<button onclick={...}>`)不是 `<script>` 标签,不需要 nonce。 - 推荐 nonce 模式,hash 模式将来会和 streaming SSR 冲突。 - 详见 [references/csp-reference.md](./references/csp-reference.md)。 ## Critical: Best Practices ### $state ```js // ✅ 只对需要响应式的变量用 $state let count = $state(0); // ✅ 大数组/不可变数据用 $state.raw let apiData = $state.raw([]); apiData = await fetchData(); // 重新赋值触发更新 ``` ### $derived ```js // ✅ 派生值用 $derived let doubled = $derived(count * 2); // ❌ 不要用 $effect 同步派生 let square; $effect(() => { square = num * num; }); // ❌ let square = $derived(num * num); // ✅ ``` ### $effect ```js // ✅ 仅用于副作用 $effect(() => { myChart.resize(); }); // ❌ 不要同步状态 $effect(() => { left = total - spent; }); // ❌ ``` ### Props ```js // ✅ 依赖 props 的值用 $derived let { type } = $props(); let color = $derived(type === 'danger' ? 'red' : 'green'); ``` ### 事件 ```svelte <!-- ✅ 现代事件属性 --> <button onclick={handleClick}>click</button> <!-- ❌ 旧式 on:click --> <button on:click={handleClick}>click</button> <!-- ✅ 监听 window 事件 --> <svelte:window onkeydown={handleKey} /> ``` ### Snippets ```svelte <!-- ✅ 推荐:使用 snippet --> {#snippet footer()} <p>footer</p> {/snippet} ``` ### Each blocks ```svelte <!-- ✅ 始终使用 key --> {#each items as item (item.id)} <li>{item.name}</li> {/each} ``` ### Context ```js // ✅ 用 createContext 提供类型安全 import { createContext } from 'svelte'; const [getCtx, setCtx] = createContext<MyCtx>(); ``` ### Avoid Legacy Features ```js // ✅ Runes mode 全是新项目的默认 let count = $state(0); // ✅ $: count++; // ❌ legacy export let name; // ❌ let { name } = $props(); // ✅ on:click={fn} // ❌ onclick={fn} // ✅ <slot /> // ❌ {@render children()} // ✅ <svelte:component this={x} /> // ❌ <svelte:element this={x} /> // ✅ ``` ## Critical: Testing (Vitest) ### 安装配置 ```bash npm install -D vitest ``` ```js // vite.config.js export default defineConfig({ resolve: process.env.VITEST ? { conditions: ['browser'] } : undefined }); ``` ### 单元测试 ```js // multiplier.svelte.js export function multiplier(initial, k) { let count = $state(initial); return { get value() { return count * k; }, set: (c) => { count = c; } }; } // test import { multiplier } from './multiplier.svelte.js'; test('multiplier', () => { let double = multiplier(0, 2); expect(double.value).toEqual(0); double.set(5); expect(double.value).toEqual(10); }); ``` ### 组件测试 ```js import { mount, unmount } from 'svelte'; import { expect, test } from 'vitest'; test('MyComponent', () => { const comp = mount(MyComponent, { target: document.body, props: { name: 'Ada' } }); expect(document.body.innerHTML).toContain('Ada'); unmount(comp); }); ``` ### flushSync 强制同步执行所有待处理效果(测试中常用): ```js import { flushSync } from 'svelte'; flushSync(); ``` ## Quick Fixes | 问题 | 解决方案 |
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub