尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Svelte 遗留模式深度解析:响应式 `let/var` 声明的运作原理、编译机制与向 Runes 迁移

Svelte 遗留模式深度解析:响应式 `let/var` 声明的运作原理、编译机制与向 Runes 迁移 Svelte 遗留模式深度解析响应式let/var声明的运作原理、编译机制与向 Runes 迁移【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte本文聚焦 Svelte 5 中遗留legacy模式下「顶层let/var声明自动响应式」这一机制它为何能触发 UI 更新、为什么.push()这类数组方法不能触发更新而必须自赋值numbers numbers、编译器在底层如何把普通变量转换为响应式信号以及如何平滑迁移到 runes 模式的$state。读完后你能够解释遗留响应式的完整链路从script中的声明到$.mutable_source信号再到组件更新并准确判断哪些写法会导致「UI 不更新」的坑。遗留模式与 runes 模式两种响应式范式的分界Svelte 5 引入了 runes$state、$derived、$effect等作为新的 API 体系Svelte 3/4 的部分语法因此被标记为 deprecated当前仍可用最终会移除。官方建议增量式迁移现有代码参见 v5 迁移指南。按照 遗留特性总览 的说法遗留模式的文档面向两类读者仍在用 Svelte 3/4 的人以及已在 Svelte 5 中但部分组件尚未迁移的人Svelte 3/4 的语法在 Svelte 5 中仍然可用因此官方刻意区分legacy mode遗留模式与runes moderunes 模式一旦组件进入 runes 模式——通过使用任意 rune或显式设置runes: true编译器选项——该组件内的 legacy 特性将不再可用。本文的主角是遗留模式的核心能力顶层let/var声明自动获得响应式。顶层let声明自动成为响应式状态在 runes 模式中响应式状态必须显式用$staterune 声明。而在遗留模式中组件顶层声明的变量会被自动视为响应式变量对其重新赋值或就地修改count 1或object.x y都会导致 UI 更新。典型示例即 01-legacy-let.md 的原始示例script let count 0; /script button on:click{() count 1} clicks: {count} /button这里没有任何「响应式标记」let count 0只是普通 JS 声明但点击按钮时模板中的{count}会随count 1自动刷新。这正是遗留模式与 runes 模式最根本的差异——响应性由「声明位置 编译器分析」隐式赋予而非由作者显式标注。注意前提自动响应式只作用于组件顶层的变量声明。声明在函数内部或条件块内部的变量不具备此能力此外遗留模式的响应式建立在「赋值」assignment之上这一事实直接引出下一节的坑。关键陷阱.push()/.splice()不触发更新必须自赋值遗留模式的响应式以赋值为触发条件。因此像.push()、.splice()这类「就地修改」数组元素的方法不会自动触发更新——必须跟随一次赋值来「告知」编译器需要更新 UIscript let numbers [1, 2, 3, 4]; function addNumber() { // this method call does not trigger an update numbers.push(numbers.length 1); // this assignment will update anything // that depends on numbers numbers numbers; } /script这里numbers numbers把同一个引用赋回给自己看似无意义却是遗留模式约定俗成的「刷新信号」编译器只关心「左值发生了赋值」从而标记信号为脏、让所有读取numbers的模板表达式重新求值。源码视角编译器为什么只认赋值编译器在分析阶段会为遗留组件顶层的let/var声明打上state绑定标记。到客户端代码生成阶段VariableDeclaration.js 中非 runes 分支的逻辑会检查声明的绑定类型binding.kind state并调用create_state_declarators()把普通变量改写为响应式信号// packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js declarations.push( ...create_state_declarators( declarator, context, /** type {Expression} */ (declarator.init context.visit(declarator.init)) ) );而create_state_declarators()最终生成的就是$.mutable_source(value, immutable)调用同文件 L398-L436。也就是说let count 0在编译产物中等价于一个可调用的信号包装所有对count的读/写都被重定向到信号上——这就是「赋值触发更新」的实现基础写操作会执行set读操作会订阅信号变化。mutable_source与mutate自赋值如何生效运行时实现位于 sources.js。遗留模式的mutable_source与 runes 模式的state有两点关键区别// packages/svelte/src/internal/client/reactivity/sources.js export function mutable_source(initial_value, immutable false, trackable true) { const s source(initial_value); if (!immutable) { s.equals safe_equals; } // bind the signal to the component context, in case we need to // track updates to trigger beforeUpdate/afterUpdate callbacks if (legacy_mode_flag trackable component_context ! null component_context.l ! null) { (component_context.l.s ?? []).push(s); } return s; }s.equals safe_equals非不可变模式下用深度相等判断保证对象/数组内容的变化也能被识别当处于遗留模式时该信号会被推入组件上下文component_context.l.s用于跟踪更新以触发beforeUpdate/afterUpdate生命周期回调——这是遗留模式特有的行为runes 模式的信号不做此绑定。而对于numbers numbers这类「同引用自赋值」同文件的mutate()函数 负责兜底export function mutate(source, value) { set( source, untrack(() get(source)) ); return value; }它先untrack地取出当前值再执行一次set——即便新值与旧值相同也会走通知路径从而让「依赖numbers的一切」重新计算。这正是文档示例中numbers numbers一行能真正刷新 UI 的底层原因。另一面invalidate_inner_signals手动失效除自赋值外遗留模式还提供手动失效工具。legacy.js 中实现了invalidate_inner_signals()它先执行fn通过capture_signals()捕获执行期间被读到的所有信号内部依赖全局变量captured_signals再逐个调用internal_set(signal, signal.v)强制标记为脏。这是遗留模式「按信号粒度手动刷新」的运行时基础与 runes 模式下由编译器静态跟踪依赖的方式形成对比。与$:响应式语句的关系遗留模式还有另一半$:响应式语句reactive declarations详见 02-legacy-reactive-assignments.md。顶层语句前缀$:后会在依赖变化时重新执行例如script let a 1; let b 2; $: console.log(${a} ${b} ${sum}); $: sum a b; /script理解两者的分工很重要本文的「响应式let」解决的是「状态从哪来」——顶层变量自动成为响应式源$:语句解决的是「状态如何联动」——编译期做依赖分析语句中只读不改的变量即依赖并按拓扑序执行。依赖是编译期确定的因此间接引用会失效$: doubled double()double内部读count不会在count变化时重跑因为编译器「看不见」这个依赖。此外$:语句在服务端渲染阶段也会执行浏览器专属代码需要显式用if (browser)包裹。在 runes 模式中这套组合对应let count $state(0)$derived/$effect。遗留let与export let的边界同属遗留模式顶层声明的还有props 声明带export关键字的let成为可接收默认值的 prop见 03-legacy-export-let.md。从源码结构看VariableDeclaration.js 的非 runes 分支先取声明的绑定列表若其中存在bindable_prop类型的绑定就走 prop 路径生成$.prop来源否则才走state路径mutable_source——即「export let是 prop普通let是状态」这一语义在编译器中是明确分流的。顺带一提遗留模式还有$$props/$$restProps04-legacy-$$props-and-$$restProps.md等周边语法迁移时可对照 遗留特性文档目录 逐项核对。从遗留模式迁移到 runes$state对照当组件进入 runes 模式后本文所述机制不再可用等价写法如下!-- 遗留模式 -- script let count 0; let numbers [1, 2, 3, 4]; /script !-- runes 模式 -- script import { $state } from svelte; let count $state(0); let numbers $state([1, 2, 3, 4]); /script从源码看两者生成的信号类型也不同runes 分支在 VariableDeclaration.js 中对$state声明调用$.state(value)并对可代理的值包裹$.proxy(value)——这意味着 runes 模式的$state通过代理proxy机制使.push()等就地修改天然可追踪不需要numbers numbers这样的自赋值而遗留模式依赖mutable_source的整体式更新这正是两者在数组更新行为上表现不同的根源。生命周期钩子也随之变化onMount/afterUpdate等遗留钩子在 runes 组件中不可用对应$effectlegacy lifecycle hooks 文档 中有说明。整体迁移步骤与runes: true的渐进启用策略参见 v5-migration-guide。小结与实践建议自动响应式有严格边界只覆盖遗留组件「顶层」let/var且只认赋值.push()/.splice()/深层属性修改后按文档约定补一次自赋值numbers numbers或换用$:语句派生。底层链路已验证let count 0→ 编译器分析打state标记2-analyze→ 客户端生成为$.mutable_sourceVariableDeclaration.js→ 运行时信号绑定组件上下文以驱动beforeUpdate/afterUpdatesources.js→ 自赋值经mutate()强制通知sources.js。迁移路径清晰let x 0→let x $state(0)$:语句 →$derived/$effectexport let→$props。单组件可通过使用 rune 或runes: true编译器选项独立切换新旧组件可并存便于增量迁移。【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表