ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Svelte 运行时警告完全指南:warning code、触发场景与源码级实现解析

Svelte 运行时警告完全指南:warning code、触发场景与源码级实现解析 Svelte 运行时警告完全指南warning code、触发场景与源码级实现解析【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte本文基于 Svelte 官方的 Runtime warnings 参考文档documentation/docs/98-reference/30-runtime-warnings.md完整覆盖客户端警告client warnings与共享警告shared warnings的全部条目每条警告的警告码warning code、控制台消息模板、触发场景、官方示例与推荐修复方式并结合本仓库源码解释这些警告是如何从messages/目录的 Markdown 消息源生成到运行时warnings.js模块、在哪些代码路径上被触发的。读完后你遇到任何一条[svelte] warning_code控制台输出都能快速定位根因并正确处理。1. 运行时警告是什么输出长什么样运行时警告runtime warnings是 Svelte运行时在组件渲染、事件处理、水合hydration等阶段通过console.warn发出的提示与编译期警告compiler warnings相对。它们的共同特征是每条警告都有唯一的warning code例如assignment_value_stale、await_waterfall、hydration_mismatch消息文本是带占位符如%property%、%location%的模板运行时填充实际值后输出。本仓库中警告函数的真实形态可以从生成产物 src/internal/client/warnings.js 看到。以assignment_value_stale为例其逻辑是import { DEV } from esm-env; var bold font-weight: bold; var normal font-weight: normal; /** * Assignment to %property% property (%location%) will evaluate to the * right-hand side, not the value of %property% following the assignment... * param {string} property * param {string} location */ export function assignment_value_stale(property, location) { if (DEV) { // 开发模式粗体的 [svelte] assignment_value_stale 标题 完整说明 错误页面链接 console.warn(%c[svelte] assignment_value_stale\n%cAssignment to \${property}\ ..., bold, normal); } else { // 生产模式只输出形如 svelte.dev/e/assignment_value_stale 的 URL 字符串 } }两个关键行为值得注意开发/生产差异警告函数内部使用esm-env的DEV标志分支。开发模式下输出带样式的完整描述与可跳转的错误页链接svelte.dev/e/code形式生产模式下只输出该链接字符串。因此排查运行时警告应在开发环境进行。消息模板与文档同源函数签名、JSDoc 参数、消息文案都来自同一份 Markdown 消息源保证控制台输出、生成的函数与参考文档三者一致见下一节。参考文档 30-runtime-warnings.md 本身只是一个入口页它通过include引入两个生成文件Client warnings部分对应客户端警告Shared warnings部分对应共享警告。本文下文即以这两部分的全部条目为骨架展开。2. 警告消息的生成流水线从 Markdown 到运行时函数本仓库用一套代码生成机制统一管理错误/警告消息入口是 scripts/process-messages/index.js。阅读该脚本可以确认完整流程消息源packages/svelte/messages/下按类别组织client-warnings、shared-warnings、compile-warnings等每个.md文件中每个## code段落定义一条消息。段落开头以引用的部分是消息模板可含%var%占位符其余部分是写入文档的详细说明。例如客户端警告源文件 messages/client-warnings/warnings.md 与共享警告源文件 messages/shared-warnings/warnings.md。校验与排序脚本会对重复的 warning code 直接抛错Duplicate message code并保证每条消息至少有一个模板文本所有条目按 code 字母序排序后回写源文件保持仓库内有序。生成文档输出documentation/docs/98-reference/.generated/category.md每个 code 生成### code 代码块包裹的消息模板 详细说明这正是参考文档include的内容来源。生成运行时模块基于scripts/process-messages/templates/下的模板其中定义了带CODE/PARAMETER/MESSAGE占位符的export function CODE骨架脚本把每个 code 实例化为独立函数写入src/internal/client/warnings.jsclient-warningssrc/internal/shared/warnings.jsshared-warnings以及 compiler/server 等对应模块见脚本末尾的transform(...)调用列表。模板变量%name%会解析为函数参数按首次出现顺序同一条警告有多个消息模板时生成的是条件表达式多出来的参数在 JSDoc 中标记为可选param {string | undefined | null} [name]。这就是为什么后文有些警告如binding_property_non_reactive、hydration_mismatch存在带%location%与不带%location%两种消息形态——运行时根据是否传入了位置信息选用不同模板。3. Client warnings 全量目录客户端警告共 20 条源码定义于 messages/client-warnings/warnings.md。下面按主题分组完整保留每条警告的消息、触发原因、示例与修复方案。3.1 状态与响应式相关assignment_value_staleAssignment to%property%property (%location%) will evaluate to the right-hand side, not the value of%property%following the assignment. This may result in unexpected behaviour.对$state属性的复合赋值表达式??、||、会在赋值前求值出旧值当右值是一个新对象/数组时表达式求值结果与赋值后的属性值不是同一个引用可能把变更“写丢”。官方示例script let object $state({ array: null }); function add() { (object.array ?? []).push(object.array.length); } /script button onclick{add}add/button pitems: {JSON.stringify(object.items)}/p首次点击按钮时push作用在右值[]上而object.array最终是一个新的空 state proxypush 的值被丢弃。修复方式是把“赋值”与“使用”拆成两条语句let object { array: [0] }; // ---cut--- function add() { object.array ?? []; object.array.push(object.array.length); }从源码看该警告在开发构建的 dev/assign.js 中实现assign()执行object[property] rhs或??/||/后用untrack重新读取赋值后的属性值若两者不相等且新值带有STATE_SYMBOL即是一个 state proxy就调用w.assignment_value_stale(property, location)发出警告。dev/目录下的检查逻辑只会进入开发构建。await_reactivity_lossDetected reactivity loss when reading%name%. This happens when state is read in an async function after an earlierawaitSvelte 的信号式响应式通过追踪模板或$derived(...)表达式执行时读取了哪些状态来工作。当表达式包含await时Svelte 会做转换使得await之后读取的状态也被追踪。因此下面这个例子中a和b都会被追踪尽管b是在aresolve 之后才读取的let a Promise.resolve(1); let b 2; // ---cut--- let total $derived(await a b);但如果await对表达式来说“不可见”藏在另一个 async 函数内部追踪就不会延伸到那个函数里读取的状态let a Promise.resolve(1); let b 2; // ---cut--- async function sum() { return await a b; } let total $derived(await sum());此时total只依赖a不依赖b。解决方法是把值作为参数显式传入函数let a Promise.resolve(1); let b 2; // ---cut--- /** * param {Promisenumber} a * param {number} b */ async function sum(a, b) { return await a b; } let total $derived(await sum(a, b));从源码看该警告由编译器在客户端转换阶段检测见 AwaitExpression.js 与 ForOfStatement.js 中对is_ignored(node, await_reactivity_loss)的判断——意味着可用svelte-ignore注释抑制并在运行时读取到相关信号时由 runtime.js 处的w.await_reactivity_loss(signal.label)实际发出reactivity/async.js 中的注释也明确了这套机制服务于该警告的发射。await_waterfallAn async derived,%name%(%location%) was not read immediately after it resolved. This often indicates an unnecessary waterfall, which can slow down your app如下例async function one() { return 1 } async function two() { return 2 } // ---cut--- let a $derived(await one()); let b $derived(await two());第二个$derived要等第一个 resolve 之后才会创建而await two()并不依赖a的值这段“瀑布”waterfall延迟是不必要的。注意一旦创建完成两个值后续的变化是可以并发发生的——瀑布只发生在 derived 首次创建时。修复方式是先创建 Promise再分别 awaitasync function one() { return 1 } async function two() { return 2 } // ---cut--- let aPromise $derived(one()); let bPromise $derived(two()); let a $derived(await aPromise); let b $derived(await bPromise);derived_inertReading a derived belonging to a now-destroyed effect may result in stale values在 effect 内部创建的$derived会在该 effect 被销毁后停止更新。应把$derived创建在 effect 之外或放在$effect.root内部。console_log_stateYourconsole.%method%contained$stateproxies. Consider using$inspect(...)or$state.snapshot(...)instead把 Proxy 对象直接console.log时浏览器 devtools 打印的是 proxy 本身而不是它代表的值$stateproxy 的 target 可能与当前值不一致容易造成困惑。持续观察一个值的变化最简单的方式是使用$inspectrune一次性打印如在事件处理器里可以用$state.snapshot取当前值的快照。从源码看开发构建通过 dev/console-log.js 拦截console方法实现检测与告警。state_proxy_equality_mismatchReactive$state(...)proxies and the values they proxy have different identities. Because of this, comparisons with%operator%will produce unexpected results$state(...)会为其接收的值创建一个 Proxy。proxy 与其包装的值是不同的对象身份因此相等性检查会出乎意料地失败script let value { foo: bar }; let proxy $state(value); value proxy; // always false /script解决方式是保证比较双方要么都来自$state(...)要么都不是。注意$state.raw(...)不会创建 state proxy。从源码看开发构建的 dev/equality.js 中的strict_equals/equals会把get_proxied_value(a) get_proxied_value(b)与a b的结果对比若两者不一致则发出该警告参数为实际运算符/!//!。更细致的是 init_array_prototype_warnings开发构建会临时修补Array.prototype的indexOf/lastIndexOf/includes——当按引用没找到、但按“解包后的代理值”能找到时发出state_proxy_equality_mismatch(array.indexOf(...))等警告并注册清理函数避免 REPL 反复打补丁导致原型被越叠越多。3.2 水合hydration相关hydratable_missing_but_expectedExpected to find a hydratable with key%key%during hydration, but did not.当客户端渲染了一个服务端没有渲染的 hydratable 时会出现此警告意味着客户端被迫在水合期间阻塞式地运行它的函数。这会阻塞水合直到异步工作完成对性能是负面的。例如script import { hydratable } from svelte; if (BROWSER) { // bad! nothing can become interactive until this asynchronous work is done await hydratable(foo, get_slow_random_number); } /scripthydration_attribute_changedThe%attribute%attribute on%html%changed its value between server and client renders. The client value,%value%, will be ignored in favour of the server value某些属性如img的src在水合期间不会被修复即保留服务端值。原因是更新这些属性可能触发图片重新请求iframe则是重新加载 frame即使它们最终指向同一资源。修复方式要么按文档建议用 svelte-ignore 注释静默警告要么保证值在服务端与客户端一致。如果确实需要在水合时改变值可以强制更新script let { src } $props(); if (typeof window ! undefined) { // stash the value... const initial src; // unset it... src undefined; $effect(() { // ...and reset after weve mounted src initial; }); } /script img {src} /hydration_html_changedThe value of an{html ...}block changed between server and client renders. The client value will be ignored in favour of the server valueThe value of an{html ...}block %location% changed between server and client renders. The client value will be ignored in favour of the server value两种模板对应是否带位置信息。如果{html ...}的值在服务端与客户端之间发生了变化水合期间不会修复它会保留服务端值——因为水合期间的变更检测代价高且通常没必要。修复方式同上用svelte-ignore静默或保证两侧一致确需变化时可用同样的“暂存 → 置空 →$effect中恢复”模式script let { markup } $props(); if (typeof window ! undefined) { // stash the value... const initial markup; // unset it... markup undefined; $effect(() { // ...and reset after weve mounted markup initial; }); } /script {html markup}hydration_mismatchHydration failed because the initial UI does not match what was rendered on the serverHydration failed because the initial UI does not match what was rendered on the server. The error occurred near %location%当 Svelte 水合服务端 HTML 时遇到错误就会抛出此警告。水合过程中 Svelte 会遍历 DOM 并期待特定结构如果实际结构不同例如 HTML 因为非法嵌套被浏览器 DOM 解析器“修复”过Svelte 就会出问题并产生此警告。开发模式下此警告前通常会伴随一条console.error指明有问题的 HTML需要据此修复。3.3 绑定与 props 所有权ownership相关binding_property_non_reactive%binding%is binding to a non-reactive property%binding%(%location%) is binding to a non-reactive property绑定目标不是响应式属性时发出两种模板对应是否带位置信息。ownership_invalid_binding%parent% passed property%prop%to %child% withbind:, but its parent component %owner% did not declare%prop%as a binding. Consider creating a binding between %owner% and %parent% (e.g.bind:%prop%{...}instead of%prop%{...})考虑三个组件GrandParent、Parent、Child如果在GrandParent上写GrandParent bind:value在GrandParent内部却用Parent {value} /注意缺了bind:把变量传下去然后在Parent内部对Child bind:value做绑定就会触发该警告。修复方式是在组件之间用bind:而不是普通传 prop即本例中应写Parent bind:value /。ownership_invalid_mutationMutating unbound props (%name%, at %location%) is strongly discouraged. Consider usingbind:%prop%{...}in %parent% (or using a callback) instead官方示例两个文件!--- file: App.svelte --- script import Child from ./Child.svelte; let person $state({ name: Florida, surname: Man }); /script Child {person} /!--- file: Child.svelte --- script let { person } $props(); /script input bind:value{person.name} input bind:value{person.surname}Child在没有被显式“允许”的情况下修改了属于App的person。这在规模化后会产生难以推理的代码“谁改了这个值”因此强烈不推荐。修复方式要么创建回调 prop 来通信变化要么把person标记为$bindable。3.4 生命周期与错误边界相关lifecycle_double_unmountTried to unmount a component that was not mounted对未挂载或已卸载的组件再次执行 unmount 时发出。svelte_boundary_reset_noopAsvelte:boundaryresetfunction only resets the boundary the first time it is called当svelte:boundary内容渲染出错时onerror处理器会收到错误以及一个reset函数用于尝试重新渲染内容。该reset函数只能有效调用一次之后调用不再生效。例如把reset的引用存到 boundary 外部的情况下在Contents /正常渲染时点击按钮不会再次触发重渲染script let reset; /script button onclick{reset}reset/button svelte:boundary onerror{(e, r) (reset r)} !-- contents -- {#snippet failed(e)} poops! {e.message}/p {/snippet} /svelte:boundary3.5 模板、事件与其他event_handler_invalid%handler% should be a function. Did you mean to %suggestion%?事件处理器不是函数时发出并给出修正建议例如把{handler}写成{handler()}或反过来。invalid_raw_snippet_renderTherenderfunction passed tocreateRawSnippetshould return HTML for a single element传给createRawSnippet的render函数应返回单个元素的 HTML。select_multiple_invalid_valueThevalueproperty of aselect multipleelement should be an array, but it received a non-array value. The selection will be kept as is.使用select multiple value{...}时Svelte 通过遍历传给value的数组来标记所有被选中的option。如果value不是数组Svelte 会发出此警告并保持当前选中项不变。要消除警告请确保value是数组显式选择是null或undefined保持现状。transition_slide_displayTheslidetransition does not work correctly for elements withdisplay: %value%slide 过渡通过动画化元素的height实现因此要求display为block、flex、grid这类可设置高度的值。以下 display 值不适用display: inlinespan等元素的默认值及其变体inline-block、inline-flex、inline-griddisplay: table与table-[name]table、tr等元素的默认值display: contents。legacy_recursive_reactive_blockDetected a migrated$:reactive block in%filename%that both accesses and updates the same reactive value. This may cause recursive updates when converted to an$effect.这是针对经迁移工具转换的旧版$:响应式语句的警告如果一个$:块同时读取和更新同一个响应式值转换成的$effect可能引发递归更新。4. Shared warnings 全量目录共享警告shared warnings同时适用于客户端与服务端运行环境消息源为 messages/shared-warnings/warnings.md生成到src/internal/shared/warnings.js。共 2 条dynamic_void_element_contentsvelte:element this%tag%is a void element — it cannot have content像input这类 void 元素不能拥有内容传给它们的任何子节点都会被忽略。state_snapshot_uncloneableValue cannot be cloned with$state.snapshot— the original value was returnedThe following properties cannot be cloned with$state.snapshot— the return value contains the originals:%properties%两种模板整体不可克隆 vs 部分属性不可克隆并列出%properties%。$state.snapshot会尝试克隆给定值以便返回一个不再变化的引用。某些对象无法克隆此时返回原始值。官方示例中property可克隆而window不可克隆因为 DOM 元素不可克隆const object $state({ property: this is cloneable, window }) const snapshot $state.snapshot(object);5. 实践建议如何响应这些运行时警告结合参考文档与源码实现可以总结出以下处理原则在开发环境排查。生产构建中警告函数只输出错误页 URL 字符串完整描述只在DEV模式下打印见 src/internal/client/warnings.js 中每个函数的DEV分支。按 code 对号入座。每条警告的修复方向在消息源中都有明确说明常见模式包括复合赋值丢更新 → 拆成赋值与使用两条语句assignment_value_stale异步闭包丢失响应性 → 把状态显式作为参数传入函数await_reactivity_loss级联 await → 先创建 Promise 再 awaitawait_waterfall;打印状态 → 用$inspect或$state.snapshotconsole_log_state引用比较错乱 → 比较双方统一在 proxy/原值同一侧必要时用$state.rawstate_proxy_equality_mismatch服务端/客户端渲染不一致 → 保证值一致或用“暂存 → 置空 →$effect恢复”强制更新确属预期时可用svelte-ignore注释静默hydration 系列警告props 归属问题 → 使用bind:链或$bindable/回调 propownership_*系列。理解部分警告来自编译器、部分来自运行时。例如await_reactivity_loss由编译期转换插入检测逻辑、运行期在信号被读取时发出而assignment_value_stale、state_proxy_equality_mismatch、console_log_state的检测逻辑集中在src/internal/client/dev/目录dev/assign.js、dev/equality.js、dev/console-log.js只存在于开发构建中不影响生产代码体积与行为。维护者视角新增或修改运行时警告的正确姿势是在 messages/ 对应类别的.md源文件中增删## code段落消息模板必须位于段落最前、以引用占位符用%var%同一 code 不可重复然后运行 scripts/process-messages/index.js它会同步更新参考文档的生成文件与各运行时warnings.js模块该脚本还支持-w参数监听messages/目录的变化自动重新生成。6. 小结Svelte 的运行时警告体系由“一份 Markdown 消息源、一条生成流水线、双端输出控制台 参考文档”构成客户端 20 条警告覆盖状态陷阱复合赋值、异步响应性丢失、级联 waterfall、proxy 身份比较、水合不一致属性/HTML 变更、结构不匹配、缺失 hydratable、绑定与 props 所有权、组件生命周期与svelte:boundary重置、以及模板/事件/过渡等具体误用共享 2 条警告则处理动态 void 元素与$state.snapshot的克隆限制。每条警告都有稳定 code 与错误页链接配合本仓库messages/源文件与src/internal/*/warnings.js、src/internal/client/dev/下的检测实现可以从控制台输出一路追溯到触发它的运行时代码路径。【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表