关于 svelte-core-bestpractices
为使用 Svelte 5 的开发者整理核心实践,涵盖响应式状态、组件结构与样式等主题,并强调优先采用 runes 模式及避免不必要的副作用。
主要功能
- 01选择合适的响应式状态
- 02用 $derived 计算派生值
- 03减少不必要的 $effect
- 04采用现代组件与事件语法
- 05优化列表渲染与上下文
适用场景
- 01编写新的 Svelte 5 组件
- 02排查响应式更新问题
- 03改进现有 Svelte 代码
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ sveltejs/ ai-tools --skill svelte-core-bestpractices安装后在 Claude Code 中输入/svelte-core-bestpractices确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/attach.mdawait-expressions.mdbind.mdeach.mdhydratable.mdinspect.mdrender.mdsnippet.mdsvelte-reactivity.md详细介绍
$state
仅对应该具有_响应性_的变量使用 $state 符文——换句话说,只有那些会导致 $effect、$derived 或模板表达式更新的变量才使用它。其他变量都可以是普通变量。
对象和数组($state({...}) 或 $state([...]))具有深层响应性,也就是说,修改它们会触发更新。这样做需要权衡:为了获得细粒度的响应性,对象必须使用代理,因此会带来性能开销。如果处理的是只会被重新赋值(而非修改)的大型对象,请改用 $state.raw。例如,API 响应通常就属于这种情况。
$derived
要根据状态计算值,请使用 $derived,而不是 $effect:
// do this
let square = $derived(num * num);
// don't do this
let square;
$effect(() => {
square = num * num;
});
说明
$derived接收的是表达式,_而不是_函数。如果需要使用函数(例如表达式很复杂),请使用$derived.by。
派生值是可写的——你可以像为 $state 赋值一样为它们赋值,但当其表达式发生变化时,它们会重新求值。
如果派生表达式是对象或数组,它将按原样返回——_不会_具有深层响应性。不过,在少数需要这种响应性的情况下,你可以在 $derived.by 中使用 $state。
$effect
Effect 是一种逃生舱门,通常应避免使用。尤其要避免在 effect 内更新状态。
- 如果需要将状态同步到 D3 等外部库,通常使用
{@attach ...}会更简洁 - 如果需要响应用户交互运行代码,请直接将代码放在事件处理器中,或酌情使用函数绑定
- 如果需要记录值以进行调试,请使用
$inspect - 如果需要观察 Svelte 外部的内容,请使用
createSubscriber
切勿在 effect 的内容外层包上 if (browser) {...} 或类似判断——effect 不会在服务器上运行。
$props
应假定 props 会发生变化。例如,依赖 props 的值通常应使用 $derived:
// @errors: 2451
let { type } = $props();
// do this
let color = $derived(type === 'danger' ? 'red' : 'green');
// don't do this — `color` will not update if `type` changes
let color = type === 'danger' ? 'red' : 'green';
$inspect.trace
$inspect.trace 是用于调试响应性的工具。如果某些内容没有正常更新,或运行次数超出预期,可以将 $inspect.trace(label) 添加为 $effect 或 $derived.by(或它们调用的任意函数)的第一行,以追踪其依赖项并找出触发更新的依赖项。
#key 块
当你希望某个表达式变化时运行过渡/动画,#key 块会很有用,但它的开销可能很大——每次都会卸载其中的所有元素/组件,因此应谨慎使用。例如,与其用 #key 让子组件 <script> 块中的表达式重新运行,不如考虑用 $derived 包装这些表达式。
事件
任何以 on 开头的元素属性都会被视为事件监听器:
<button onclick={() => {...}}>click me</button>
<button {onclick}>...</button>
<button {...props}>...</button>
如果需要为 window 或 document 添加监听器,可以使用 <svelte:window> 和 <svelte:document>:
<svelte:window onkeydown={...} />
<svelte:document onvisibilitychange={...} />
避免为此使用 onMount 或 $effect。
代码片段
代码片段是一种定义可复用标记块的方式,可通过 {@render ...} 标签实例化,也可作为 props 传递给组件。它们必须在模板内声明。
{#snippet greeting(name)}
<p>hello {name}!</p>
{/snippet}
{@render greeting('world')}
说明
在组件顶层声明的代码片段(即不在元素或块内)可在
<script>中引用。不引用组件状态的代码片段也可在<script module>中使用,此时可以将其导出,供其他组件使用。
Each 块
优先使用带键的 each 块——这样可以让 Svelte 精确插入或移除项目,而不是更新现有项目所属的 DOM,从而提升性能。
说明
键必须唯一标识该对象。不要使用索引作为键。
如果需要修改项目(例如使用 bind:value={item.count}),请避免解构。
在 CSS 中使用 JavaScript 变量
如果想在 CSS 中使用 JS 变量,可以使用 style: 指令设置 CSS 自定义属性。
<div style:--columns={columns}>...</div>
然后,你就可以在组件的 <style> 中引用 var(--columns)。
为子组件设置样式
组件 <style> 中的 CSS 仅作用于该组件。如果父组件需要控制子组件的样式,首选方式是使用 CSS 自定义属性:
<Child --color="red" />
<h1>Hello</h1>
<style>
h1 {
color: var(--color);
}
</style>
如果无法做到这一点(例如,子组件来自某个库),可以使用 :global 覆盖样式:
<div>
<Child />
</div>
<style>
div :global {
h1 {
color: red;
}
}
</style>
上下文
考虑使用上下文,而不是在共享模块中声明状态。这样可以将状态限定在应用中需要它的部分,并消除服务器端渲染时状态在用户之间泄漏的可能性。
使用 createContext,而不是 setContext 和 getContext,因为它能提供类型安全。
异步 Svelte
如果使用 5.36 或更高版本,可以使用 await 表达式和 hydratable,直接在组件中使用 promise。请注意,由于这些功能尚未被认为完全稳定,因此需要在 vite.config.js 或 svelte.config.js 的插件选项中启用 experimental.async 选项。
避免使用旧版功能
新代码一律使用 runes 模式,并避免使用已有更现代替代方案的功能:
- 使用
$state,而不是隐式响应性(例如let count = 0; count += 1) - 使用
$derived和$effect,而不是$:赋值和语句(但仅在没有更好方案时才使用 effects) - 使用
$props,而不是export let、$$props和$$restProps - 使用
onclick={...},而不是on:click={...} - 使用
{#snippet ...}和{@render ...},而不是<slot>、$$slots和<svelte:fragment> - 使用
<DynamicComponent>,而不是<svelte:component this={DynamicComponent}> - 使用
import Self from './ThisComponent.svelte'和<Self>,而不是<svelte:self> - 使用带有
$state字段的类在组件之间共享响应性,而不是使用 stores - 使用
{@attach ...},而不是use:action - 在
class属性中使用 clsx 风格的数组和对象,而不是class:指令