菜单

响应式系统

相关源文件

反应性系统是 Svelte 性能和开发者体验的核心。它能够在使用虚拟 DOM 差异化方法的情况下,在组件状态发生变化时自动更新 DOM。本页将从开发者使用和内部实现的角度解释 Svelte 的反应性模型的工作原理。

有关组件生命周期信息,请参阅组件生命周期

反应性概述

Svelte 的反应性系统使用细粒度的依赖项跟踪,可以在状态更新时高效地仅更新需要更改的 DOM 部分。在 Svelte 5 中,这主要通过“runes”来暴露——以 $ 开头的特殊函数,它们充当反应性原语。

来源:packages/svelte/src/internal/client/runtime.js1-150 packages/svelte/src/internal/client/reactivity/effects.js1-80 packages/svelte/src/internal/client/reactivity/sources.js1-80

核心概念

反应性原语

Svelte 的反应性系统构建在四个主要原语之上

原语目的内部表示
$state()创建反应性状态变量来源
$derived()从反应性依赖项计算值派生
$effect()在依赖项更改时运行副作用效果
$props()提供反应性组件属性来源

这些原语编译为一个细粒度的反应性系统,能够高效地跟踪依赖项和进行更新。

来源:packages/svelte/types/index.d.ts350-465

Signal 类型

在内部,Svelte 的反应性是构建在三种核心的 Signal 类型之上的

  • Source:通过 $state() 或其他方式创建的原始反应性值
  • Derived:依赖于其他反应性值的计算值
  • Effect:在依赖项更改时运行的副作用

来源:packages/svelte/src/internal/client/reactivity/sources.js30-100 packages/svelte/src/internal/client/reactivity/deriveds.js1-80 packages/svelte/src/internal/client/reactivity/effects.js60-150

依赖项跟踪系统

如何跟踪依赖项

当反应性值在 effect 或派生值计算执行期间被读取时,反应性系统会自动建立依赖关系。

涉及的关键机制

  1. 全局的 active_reactionactive_effect 变量会跟踪当前正在执行的反应性计算
  2. get() 函数在返回值的同事注册依赖项
  3. 每个 Signal 维护一个 reactions 数组,包含所有依赖的计算
  4. 当值发生变化时,所有依赖的计算都会被标记为脏,并安排重新执行

来源:packages/svelte/src/internal/client/runtime.js70-120 packages/svelte/src/internal/client/runtime.js870-950

Signal 状态跟踪

Signals 带有状态标志,用于确定何时需要更新它们

状态描述
CLEAN (干净)最新,无需更新
MAYBE_DIRTY (可能脏)依赖项已更改,需要评估
DIRTY (脏)肯定需要重新评估

当源值更改时,其依赖项会被标记为脏,并在依赖项图中传播。

来源:packages/svelte/src/internal/client/runtime.js160-230 packages/svelte/src/internal/client/constants.js10-25

更新调度与传播

Effect 树结构

Effects 被组织成一个树状结构,以实现高效的更新和清理。

  • Root Effects (根 Effect):作为 Effect 树入口的顶层 Effect
  • Branch Effects (分支 Effect):可以包含其他 Effect 的 Effect,例如组件根
  • Regular Effects (常规 Effect):更新 DOM 或执行其他操作的叶子 Effect

来源:packages/svelte/src/internal/client/reactivity/effects.js65-150 packages/svelte/src/internal/client/reactivity/effects.js380-460

更新过程

当一个反应性源发生变化时

  1. 源的版本号会递增
  2. 所有直接依赖的 reaction 会被标记为脏
  3. 更新会被调度为微任务
  4. 执行时,脏的 effects 会被运行,并可能将其他 effects 标记为脏
  5. 更新以拓扑顺序在 effect 树中传播

来源:packages/svelte/src/internal/client/runtime.js740-765 packages/svelte/src/internal/client/reactivity/sources.js130-220

使用 flushSync 进行批处理

flushSync 函数允许强制所有待处理的更新同步完成

来源:packages/svelte/src/internal/client/runtime.js835-855

特殊反应性功能

Untracked Operations (未跟踪的操作)

有时您可能希望读取一个反应性值而不创建依赖项。 untrack 函数允许这样做

来源:packages/svelte/src/internal/client/runtime.js1010-1035

代理对象和深度反应性

当使用 $state() 处理对象或数组时,Svelte 会自动代理它们,使所有嵌套属性都具有反应性

这允许像 user.settings.theme = 'light' 这样的直观嵌套更新来正确地触发反应性。

来源:packages/svelte/src/internal/client/proxy.js20-130 packages/svelte/src/internal/client/runtime.js1070-1140

Runes 与旧版反应性

Svelte 5 引入了 runes 作为表达反应性的新方法,取代了先前的反应性声明和 store 订阅。

旧版功能Runes 等效功能描述
let count = 0; + 反应性let count = $state(0);反应性状态变量
$: doubled = count * 2;let doubled = $derived(count * 2);派生值
$: { /* effect */ }$effect(() => { /* effect */ });副作用
export let prop;let { prop } = $props();组件属性
$store直接访问 store 值Store 订阅

runes 系统提供了更明确、更一致的反应性语义。

来源: packages/svelte/src/compiler/phases/2-analyze/index.js400-460 packages/svelte/src/compiler/phases/scope.js600-650

错误处理和边界

Svelte 的响应式系统包含强大的错误处理功能

  • 标记为 BOUNDARY_EFFECT 的 Effect(副作用)充当错误边界
  • Effect 中的错误会沿着 Effect 树向上传播,直到被边界捕获
  • 如果没有找到边界,则会正常抛出错误

这使得构建能够从错误中恢复的弹性响应式应用程序成为可能。

来源: packages/svelte/src/internal/client/runtime.js230-360

性能考量

Svelte 的响应式系统以高性能为设计目标

  1. 细粒度更新:仅更新受影响的 DOM 元素
  2. 惰性计算:派生值仅在需要时重新计算
  3. 批量更新:多个状态更改在单个更新周期中处理
  4. 内存效率:没有依赖关系的 Effect 可以被垃圾回收

来源: packages/svelte/src/internal/client/runtime.js560-630 packages/svelte/src/internal/client/reactivity/effects.js80-150

结论

Svelte 的响应式系统提供了一种强大而直观的方式来构建动态用户界面。通过自动跟踪依赖关系并仅高效地更新需要更改的部分,它在保持对开发者友好的 API 的同时,提供了出色的性能。

理解这些内部机制可以帮助您编写更高效的 Svelte 组件,并在出现响应式问题时进行有效调试。