svelte-frontend

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Svelte 5 Best Practices

Svelte 5 最佳实践

This guide outlines best practices for developing with Svelte 5, incorporating the new Runes API and other modern Svelte features. These rules MUST NOT be applied on svelte 4 files unless explicitly asked to do so.
本指南概述了使用Svelte 5开发的最佳实践,涵盖了新的Runes API及其他现代Svelte特性。除非明确要求,否则这些规则不得应用于Svelte 4文件。

Reactivity with Runes

基于Runes的响应式处理

Svelte 5 introduces Runes for more explicit and flexible reactivity.
  1. Embrace Runes for State Management:
    • Use
      $state
      for reactive local component state.
      svelte
      <script>
        let count = $state(0);
      
        function increment() {
          count += 1;
        }
      </script>
      
      <button onclick={increment}>
        Clicked {count} {count === 1 ? 'time' : 'times'}
      </button>
    • Use
      $derived
      for computed values based on other reactive state.
      svelte
      <script>
        let count = $state(0);
        const doubled = $derived(count * 2);
      </script>
      
      <p>{count} * 2 = {doubled}</p>
    • Use
      $effect
      for side effects that need to run when reactive values change (e.g., logging, manual DOM manipulation, data fetching). Remember
      $effect
      does not run on the server.
      svelte
      <script>
        let count = $state(0);
      
        $effect(() => {
          console.log('The count is now', count);
          if (count > 5) {
            alert('Count is too high!');
          }
        });
      </script>
  2. Props with
    $props
    :
    • Declare component props using
      $props()
      . This offers better clarity and flexibility compared to
      export let
      .
      svelte
      <script>
        // ChildComponent.svelte
        let { name, age = $state(30) } = $props();
      </script>
      
      <p>Name: {name}</p>
      <p>Age: {age}</p>
    • For bindable props, use
      $bindable
      .
      svelte
      <script>
        // MyInput.svelte
        let { value = $bindable() } = $props();
      </script>
      
      <input bind:value />
Svelte 5引入了Runes,实现更明确、更灵活的响应式机制。
  1. 使用Runes进行状态管理:
    • 使用
      $state
      定义组件的响应式本地状态。
      svelte
      <script>
        let count = $state(0);
      
        function increment() {
          count += 1;
        }
      </script>
      
      <button onclick={increment}>
        Clicked {count} {count === 1 ? 'time' : 'times'}
      </button>
    • 使用
      $derived
      基于其他响应式状态计算衍生值。
      svelte
      <script>
        let count = $state(0);
        const doubled = $derived(count * 2);
      </script>
      
      <p>{count} * 2 = {doubled}</p>
    • 使用
      $effect
      处理响应式值变化时需要执行的副作用(例如日志记录、手动DOM操作、数据获取)。注意
      $effect
      不会在服务器端运行。
      svelte
      <script>
        let count = $state(0);
      
        $effect(() => {
          console.log('The count is now', count);
          if (count > 5) {
            alert('Count is too high!');
          }
        });
      </script>
  2. 使用
    $props
    定义属性
    :
    • 使用
      $props()
      声明组件属性,相比
      export let
      ,它更清晰、灵活。
      svelte
      <script>
        // ChildComponent.svelte
        let { name, age = $state(30) } = $props();
      </script>
      
      <p>Name: {name}</p>
      <p>Age: {age}</p>
    • 对于可绑定的属性,使用
      $bindable
      svelte
      <script>
        // MyInput.svelte
        let { value = $bindable() } = $props();
      </script>
      
      <input bind:value />

Event Handling

事件处理

  • Use direct event attributes: Svelte 5 moves away from
    on:
    directives for DOM events.
    • Do:
      <button onclick={handleClick}>...</button>
    • Don't:
      <button on:click={handleClick}>...</button>
  • For component events, prefer callback props: Instead of
    createEventDispatcher
    , pass functions as props.
    svelte
    <!-- Parent.svelte -->
    <script>
      import Child from './Child.svelte';
      let message = $state('');
      function handleChildEvent(detail) {
        message = detail;
      }
    </script>
    <Child onCustomEvent={handleChildEvent} />
    <p>Message from child: {message}</p>
    
    <!-- Child.svelte -->
    <script>
      let { onCustomEvent } = $props();
      function emitEvent() {
        onCustomEvent('Hello from child!');
      }
    </script>
    <button onclick={emitEvent}>Send Event</button>
  • 使用直接事件属性:Svelte 5不再推荐对DOM事件使用
    on:
    指令。
    • 推荐
      <button onclick={handleClick}>...</button>
    • 不推荐
      <button on:click={handleClick}>...</button>
  • 组件事件优先使用回调属性:替代
    createEventDispatcher
    ,将函数作为属性传递。
    svelte
    <!-- Parent.svelte -->
    <script>
      import Child from './Child.svelte';
      let message = $state('');
      function handleChildEvent(detail) {
        message = detail;
      }
    </script>
    <Child onCustomEvent={handleChildEvent} />
    <p>Message from child: {message}</p>
    
    <!-- Child.svelte -->
    <script>
      let { onCustomEvent } = $props();
      function emitEvent() {
        onCustomEvent('Hello from child!');
      }
    </script>
    <button onclick={emitEvent}>Send Event</button>

Snippets for Content Projection

内容投影使用Snippets

  • Use
    {#snippet ...}
    and
    {@render ...}
    instead of slots
    : Snippets are more powerful and flexible.
    svelte
    <!-- Parent.svelte -->
    <script>
      import Card from './Card.svelte';
    </script>
    
    <Card>
      {#snippet title()}
        My Awesome Title
      {/snippet}
      {#snippet content()}
        <p>Some interesting content here.</p>
      {/snippet}
    </Card>
    
    <!-- Card.svelte -->
    <script>
      let { title, content } = $props();
    </script>
    
    <article>
      <header>{@render title()}</header>
      <div>{@render content()}</div>
    </article>
  • Default content is passed via the
    children
    prop (which is a snippet).
    svelte
    <!-- Wrapper.svelte -->
    <script>
      let { children } = $props();
    </script>
    <div>
      {@render children?.()}
    </div>
  • 使用
    {#snippet ...}
    {@render ...}
    替代插槽
    :Snippets更强大、灵活。
    svelte
    <!-- Parent.svelte -->
    <script>
      import Card from './Card.svelte';
    </script>
    
    <Card>
      {#snippet title()}
        My Awesome Title
      {/snippet}
      {#snippet content()}
        <p>Some interesting content here.</p>
      {/snippet}
    </Card>
    
    <!-- Card.svelte -->
    <script>
      let { title, content } = $props();
    </script>
    
    <article>
      <header>{@render title()}</header>
      <div>{@render content()}</div>
    </article>
  • 默认内容通过
    children
    属性传递(它是一个snippet)。
    svelte
    <!-- Wrapper.svelte -->
    <script>
      let { children } = $props();
    </script>
    <div>
      {@render children?.()}
    </div>

Component Design

组件设计

  1. Create Small, Reusable Components: Break down complex UIs into smaller, focused components. Each component should have a single responsibility. This also aids performance by limiting the scope of reactivity updates.
  2. Descriptive Naming: Use clear and descriptive names for variables, functions, and components.
  3. Minimize Logic in Components: Move complex business logic to utility functions or services. Keep components focused on presentation and interaction.
  1. 创建小巧、可复用的组件:将复杂UI拆分为更小、专注单一职责的组件。这也有助于通过限制响应式更新的范围来提升性能。
  2. 命名清晰描述性:为变量、函数和组件使用清晰、具有描述性的名称。
  3. 减少组件内的逻辑:将复杂的业务逻辑移至工具函数或服务中,让组件专注于展示和交互。

State Management (Stores)

状态管理(Stores)

  1. Segment Stores: Avoid a single global store. Create multiple stores, each responsible for a specific piece of global state (e.g.,
    userStore.js
    ,
    themeStore.js
    ). This can help limit reactivity updates to only the parts of the UI that depend on specific state segments.
  2. Use Custom Stores for Complex Logic: For stores with related methods, create custom stores.
    javascript
    // counterStore.js
    import { writable } from 'svelte/store';
    
    function createCounter() {
      const { subscribe, set, update } = writable(0);
    
      return {
        subscribe,
        increment: () => update(n => n + 1),
        decrement: () => update(n => n - 1),
        reset: () => set(0)
      };
    }
    export const counter = createCounter();
  3. Use Context API for Localized State: For state shared within a component subtree, consider Svelte's context API (
    setContext
    ,
    getContext
    ) instead of global stores when the state doesn't need to be truly global.
  1. 拆分Store:避免单一全局Store,创建多个Store,每个Store负责特定的全局状态(例如
    userStore.js
    themeStore.js
    )。这有助于将响应式更新限制在仅依赖特定状态的UI部分。
  2. 复杂逻辑使用自定义Store:对于包含相关方法的Store,创建自定义Store。
    javascript
    // counterStore.js
    import { writable } from 'svelte/store';
    
    function createCounter() {
      const { subscribe, set, update } = writable(0);
    
      return {
        subscribe,
        increment: () => update(n => n + 1),
        decrement: () => update(n => n - 1),
        reset: () => set(0)
      };
    }
    export const counter = createCounter();
  3. 局部状态使用Context API:对于组件子树内共享的状态,当状态不需要真正全局时,考虑使用Svelte的Context API(
    setContext
    getContext
    )替代全局Store。

Performance Optimizations (Svelte 5)

性能优化(Svelte 5)

When generating Svelte 5 code, prioritize frontend performance by applying the following principles:
在编写Svelte 5代码时,应遵循以下原则优先提升前端性能:

General Svelte 5 Principles

通用Svelte 5原则

  • Leverage the Compiler: Trust Svelte's compiler to generate optimized JavaScript. Avoid manual DOM manipulation (
    document.querySelector
    , etc.) unless absolutely necessary for integrating third-party libraries that lack Svelte adapters.
  • Keep Components Small and Focused: Reinforcing from Component Design, smaller components lead to less complex reactivity graphs and more targeted, efficient updates.
  • 利用编译器优化:信任Svelte编译器生成优化后的JavaScript。除非绝对必要(例如集成缺乏Svelte适配器的第三方库),否则避免手动DOM操作(
    document.querySelector
    等)。
  • 保持组件小巧专注:与组件设计部分的建议一致,更小的组件会减少响应式图的复杂度,实现更精准、高效的更新。

Reactivity & State Management

响应式与状态管理

  • Optimize Computations with
    $derived
    :
    Always use
    $derived
    for computed values that depend on other state. This ensures the computation only runs when its specific dependencies change, avoiding unnecessary work compared to recomputing derived values in
    $effect
    or less efficient methods.
  • Minimize
    $effect
    Usage:
    Use
    $effect
    sparingly and only for true side effects that interact with the outside world or non-Svelte state. Avoid putting complex logic or state updates within an
    $effect
    unless those updates are explicitly intended as a reaction to external changes or non-Svelte state. Excessive or complex effects can impact rendering performance.
  • Structure State for Fine-Grained Updates: Design your
    $state
    objects or variables such that updates affect only the necessary parts of the UI. Avoid putting too much unrelated state into a single large object that gets frequently updated, as this can potentially trigger broader updates than necessary. Consider normalizing complex, nested state.
  • 使用
    $derived
    优化计算
    :对于依赖其他状态的衍生值,始终使用
    $derived
    。这确保计算仅在其特定依赖项变化时执行,相比在
    $effect
    中重新计算衍生值或其他低效方法,避免了不必要的工作。
  • 减少
    $effect
    的使用
    :谨慎使用
    $effect
    ,仅将其用于与外部世界或非Svelte状态交互的真正副作用。避免在
    $effect
    中放置复杂逻辑或状态更新,除非这些更新是为了响应外部变化或非Svelte状态而明确设计的。过多或复杂的effect会影响渲染性能。
  • 设计细粒度更新的状态:设计
    $state
    对象或变量时,确保更新仅影响UI的必要部分。避免将过多不相关的状态放入一个频繁更新的大对象中,因为这可能会触发超出必要范围的更新。考虑对复杂的嵌套状态进行规范化。

List Rendering (
{#each}
)

列表渲染(
{#each}

  • Mandate
    key
    Attribute:
    Always use a
    key
    attribute (
    {#each items as item (item.id)}
    ) that refers to a unique, stable identifier for each item in a list. This is critical for allowing Svelte to efficiently update, reorder, add, or remove list items without destroying and re-creating unnecessary DOM elements and component instances.
  • 必须使用
    key
    属性
    :始终为列表中的每个项使用
    key
    属性(例如
    {#each items as item (item.id)}
    ),该属性指向每个项唯一、稳定的标识符。这对于Svelte高效更新、重排、添加或删除列表项至关重要,无需销毁和重新创建不必要的DOM元素和组件实例。

Component Loading & Bundling

组件加载与打包

  • Implement Lazy Loading/Code Splitting: For routes, components, or modules that are not immediately needed on page load, use dynamic imports (
    import(...)
    ) to split the code bundle. SvelteKit handles this automatically for routes, but it can be applied manually to components using helper patterns if needed.
  • Be Mindful of Third-Party Libraries: When incorporating external libraries, import only the necessary functions or components to minimize the final bundle size. Prefer libraries designed to be tree-shakeable.
  • 实现懒加载/代码分割:对于页面加载时不需要的路由、组件或模块,使用动态导入(
    import(...)
    )拆分代码包。SvelteKit会自动为路由处理此操作,但也可以根据需要通过辅助模式手动应用于组件。
  • 谨慎引入第三方库:集成外部库时,仅导入必要的函数或组件,以最小化最终包的大小。优先选择支持tree-shaking的库。

Rendering & DOM

渲染与DOM

  • Use CSS for Animations/Transitions: Prefer CSS animations or transitions where possible for performance. Svelte's built-in
    transition:
    directive is also highly optimized and should be used for complex state-driven transitions, but simple cases can often use plain CSS.
  • Optimize Image Loading: Implement best practices for images: use optimized formats (WebP, AVIF), lazy loading (
    loading="lazy"
    ), and responsive images (
    <picture>
    ,
    srcset
    ) to avoid loading unnecessarily large images.
  • 使用CSS实现动画/过渡:尽可能使用CSS动画或过渡以提升性能。对于复杂的状态驱动过渡,应使用Svelte内置的
    transition:
    指令,它经过高度优化,但简单场景通常可以使用纯CSS实现。
  • 优化图片加载:遵循图片最佳实践:使用优化格式(WebP、AVIF)、懒加载(
    loading="lazy"
    )和响应式图片(
    <picture>
    srcset
    ),避免加载不必要的大尺寸图片。

Server-Side Rendering (SSR) & Hydration

服务器端渲染(SSR)与 hydration

  • Ensure SSR Compatibility: Write components that can be rendered on the server for faster initial page loads. Avoid relying on browser-specific APIs (like
    window
    or
    document
    ) in the main
    <script>
    context. If necessary, use
    $effect
    or check
    if (browser)
    inside effects to run browser-specific code only on the client.
  • Minimize Work During Hydration: Structure components and data fetching such that minimal complex setup or computation is required when the client-side Svelte code takes over from the server-rendered HTML. Heavy synchronous work during hydration can block the main thread.
  • 确保SSR兼容性:编写可在服务器端渲染的组件,以加快初始页面加载速度。避免在主
    <script>
    上下文依赖浏览器特定API(如
    window
    document
    )。如有必要,使用
    $effect
    或在effect内检查
    if (browser)
    ,仅在客户端运行浏览器特定代码。
  • 减少hydration期间的工作:设计组件和数据获取逻辑,使得客户端Svelte代码接管服务器渲染的HTML时所需的复杂设置或计算最少。hydration期间的大量同步工作会阻塞主线程。

General Clean Code Practices

通用整洁代码实践

  1. Organized File Structure: Group related files together. A common structure:
    /src
    |-- /routes      // Page components (if using a router like SvelteKit)
    |-- /lib         // Utility functions, services, constants (SvelteKit often uses this)
    |   |-- /stores
    |   |-- /utils
    |   |-- /services
    |   |-- /components  // Reusable UI components
    |-- App.svelte
    |-- main.js (or main.ts)
  2. Scoped Styles: Keep CSS scoped to components to avoid unintended side effects and improve maintainability. Avoid
    :global
    where possible.
  3. Immutability: With Svelte 5 and
    $state
    , direct assignments to properties of
    $state
    objects (
    obj.prop = value;
    ) are generally fine as Svelte's reactivity system handles updates. However, for non-rune state or when interacting with other systems, understanding and sometimes preferring immutable updates (creating new objects/arrays) can still be relevant.
  4. Use
    class:
    and
    style:
    directives
    : For dynamic classes and styles, use Svelte's built-in directives for cleaner templates and potentially optimized updates.
    svelte
    <script>
      let isActive = $state(true);
      let color = $state('blue');
    </script>
    
    <div class:active={isActive} style:color={color}>
      Hello
    </div>
  5. Stay Updated: Keep Svelte and its related packages up to date to benefit from the latest features, performance improvements, and security fixes.
  1. 有序的文件结构:将相关文件分组。常见结构如下:
    /src
    |-- /routes      // 页面组件(如果使用SvelteKit等路由)
    |-- /lib         // 工具函数、服务、常量(SvelteKit通常使用此目录)
    |   |-- /stores
    |   |-- /utils
    |   |-- /services
    |   |-- /components  // 可复用UI组件
    |-- App.svelte
    |-- main.js (or main.ts)
  2. 作用域样式:将CSS限定在组件范围内,避免意外副作用并提升可维护性。尽可能避免使用
    :global
  3. 不可变性:在Svelte 5和
    $state
    中,直接赋值给
    $state
    对象的属性(
    obj.prop = value;
    )通常是可行的,因为Svelte的响应式系统会处理更新。然而,对于非Rune状态或与其他系统交互时,理解并有时优先选择不可变更新(创建新对象/数组)仍然是有意义的。
  4. 使用
    class:
    style:
    指令
    :对于动态类和样式,使用Svelte的内置指令,以获得更简洁的模板和可能的优化更新。
    svelte
    <script>
      let isActive = $state(true);
      let color = $state('blue');
    </script>
    
    <div class:active={isActive} style:color={color}>
      Hello
    </div>
  5. 保持更新:及时更新Svelte及其相关包,以受益于最新的特性、性能改进和安全修复。