
Svelte{#each}块完整指南列表遍历、键控更新、解构与空态渲染【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte本篇指南基于 Svelte 官方文档中{#each ...}块的模板语法说明展开覆盖基本遍历、索引访问、带 key 的键控列表、解构/Rest 模式、无项渲染重复 N 次与{:else}空态六大核心用法并结合 Svelte 仓库源码剖析 each 块从编译到运行时协调diff的实现链路。读完本文你既能直接复制可运行的列表写法也能理解 key 为何能让列表智能移动而非整段重建以及编译器为 each 块生成的底层代码形态。基本语法与适用的集合类型each 块的基本形式{#each expression as name}...{/each}带索引的形式{#each expression as name, index}...{/each}被遍历的值可以是以下几类数组array数组类对象array-like object即任何带有length属性的对象可迭代对象iterable如Map和Set。从实现上可以印证这一点编译器内部会将这些值统一转换为数组文档说明内部使用Array.from完成转换。在仓库中服务端运行时也确实提供了这样的归一化工具 ensure_array_like服务端编译出的 each 块会调用$.ensure_array_like(collection)对表达式求值并归一化见 服务端 EachBlock 转换器。一个典型的购物清单示例h1Shopping list/h1 ul {#each items as item} li{item.name} x {item.qty}/li {/each} /ul空值语义如果表达式求值为null或undefinedeach 块会按空数组处理——也就是说不会渲染任何列表项同时会触发{:else}分支如果存在这与直接传[]的行为一致。带索引遍历each 块可以指定一个索引变量语义等价于array.map(...)回调的第二个参数{#each items as item, i} li{i 1}: {item.name} x {item.qty}/li {/each}键控 each 块Keyed each blocks语法{#each expression as name (key)}...{/each}{#each expression as name, index (key)}...{/each}当提供 key 表达式——它必须能唯一标识列表中的每一项——Svelte 会在数据变化时利用 key 智能地更新列表插入、移动、删除对应的项目而不是在末尾增删项目、再更新中间的状态。这正是文档强调的核心收益列表的视觉状态焦点、输入框内容、组件内部状态等跟随 key 对应的条目移动而不是留在原来的 DOM 位置上。官方建议key 可以是任意对象但推荐使用字符串和数字因为这样即使条目对象本身被替换对象引用变化身份identity也能保持不变。{#each items as item (item.id)} li{item.name} x {item.qty}/li {/each} !-- 或者附带索引 -- {#each items as item, i (item.id)} li{i 1}: {item.name} x {item.qty}/li {/each}解构与 Rest 模式each 块中可自由使用解构destructuring和 rest 模式{#each items as { id, name, qty }, i (id)} li{i 1}: {name} x {qty}/li {/each} {#each objects as { id, ...rest }} lispan{id}/spanMyComponent {...rest} //li {/each} {#each items as [id, ...rest]} lispan{id}/spanMyComponent values{rest} //li {/each}从客户端编译器的实现看当上下文是标识符普通变量时走一条简单路径而当上下文是对象/数组解构模式时客户端 EachBlock 转换器 会通过extract_paths展开解构路径为每个绑定项生成独立的派生信号$.derived/$.derived_safe_equal带默认值的解构项会走derived_safe_equal确保默认值只求值一次并为每一项注册read/assign/mutate行为——这意味着你可以直接在模板里改写解构出来的字段赋值会写回到原集合对应位置。无项的 each 块重复渲染 N 次如果你只是想把某段内容渲染固定n次可以省略as部分{#each expression}...{/each}{#each expression, index}...{/each}官方文档给出的完整示例——用两层无项 each 渲染一个 8x8 国际象棋棋盘div classchess-board {#each { length: 8 }, rank} {#each { length: 8 }, file} div class:black{(rank file) % 2 1}/div {/each} {/each} /div style .chess-board { display: grid; grid-template-columns: repeat(8, 1fr); grid-template-rows: repeat(8, 1fr); border: 1px solid black; aspect-ratio: 1; .black { background: black; } } /style这里外层{#each { length: 8 }, rank}利用了数组类对象的能力一个只有length属性的普通对象即可充当可遍历集合第二个参数rank拿到的是 0 到 7 的索引配合奇偶判断(rank file) % 2 1着色黑格。{:else} 空态分支{#each expression as name}...{:else}...{/each}each 块可以带{:else}子句当列表为空含null/undefined的情形时渲染该分支。{#each todos as todo} p{todo.text}/p {:else} pNo tasks today!/p {/each}这一点在服务端编译结果中体现得很直白服务端 EachBlock 转换器 在有 fallback 时会生成if (array.length ! 0) { ...for 循环... } else { fallback }的结构没有 fallback 时则直接输出一个按length遍历的 for 循环// 服务端编译产物形态简化自源码 const each_array $.ensure_array_like(expression); for (let i 0, $$length each_array.length; i $$length; i) { let item each_array[i]; // ...body }源码纵深客户端 each 块的编译与运行时编译阶段为每个 each 块计算 flags客户端 EachBlock 转换器 是理解客户端行为的入口。它会为每个 each 块计算一组位标志定义在 constants 中EACH_INDEX_REACTIVE键控 each 块带索引时设置。因为条目会移动索引本身也变成了响应式的读取索引需要经过信号取值EACH_ITEM_REACTIVE当列表表达式引用了外部状态如 store 订阅、跨作用域依赖时设置条目需要包在信号里以便追踪EACH_ITEM_IMMUTABLErunes 模式下且无 store 时设置EACH_IS_ANIMATED当键控 each 的子元素带有animate:指令时设置——注释里说明由于animate:只能出现在键控 each 块的唯一子元素上所以编译器能在编译期确定该 each 块是否被动画化从而在协调前后测量动画元素的位置EACH_IS_CONTROLLED当该 each 块是父元素的唯一子节点controlled 形态时设置允许运行时走更快的清理路径。关于 key编译器会生成一个 key 函数无 key 时默认使用 index 函数即直接用下标作为 key有 key 时则编译为pattern keyExpression形式的箭头函数见 客户端 EachBlock 转换器 L295-L307。最终每个 each 块被编译为一次$.each(flags, thunk, key_function, render_fn, fallback?)调用。另外值得注意的细节如果表达式依赖的是 legacy store 订阅编译器会在条目变更时生成$.invalidate_store调用来反向刷新 store见 客户端 EachBlock 转换器 L104-L111。运行阶段协调、暂停与快速路径运行时实现位于 each.js。它的核心机制包括条目即 effect每个列表项对应一个独立的 effect子块列表更新时按 key 比对新增项创建 effect、删除项销毁 effect、移动项则重排pause_effects批量暂停删除多个条目时each.js L66 起编译器/运行时会先把所有待删 effect 暂停pause等待其中若有 out 过渡动画完成后再统一销毁——这就是列表删除时过渡动画能正常播放的底层机制controlled 快速路径当 each 块是父元素的唯一子节点EACH_IS_CONTROLLED、全部条目被删除且没有过渡时可以直接清空父元素内容并重置锚点省去逐个销毁的开销压力测试背书文件头部注释明确要求对该文件的任何实质改动都必须通过 each 块压力测试验证该测试同时存在于仓库中的 each-stress-test覆盖了大规模列表增删改移的极端场景。编译产物的对照验证如果你想在仓库内验证文档语法 → 实际生成代码的对应关系可以直接查看服务端转换server/visitors/EachBlock.js生成ensure_array_like for 循环 else 分支客户端转换client/visitors/EachBlock.js生成$.each调用与 key 函数;相关行为测试runtime-runes 测试样本集 中每个含.svelte与.js断言的目录都是一条可复现的回归用例手动压测each-stress-test。实践建议小结能取稳定 id 就用 key{#each items as item (item.id)}让状态跟随数据条目移动仅当列表内容绝对静态或纯展示时才省略 key此时编译器默认按下标做 keykey 优先选字符串/数字条目对象整体被替换时身份仍可保持解构要配 key{#each items as { id, name, qty }, i (id)}是既省事又不丢状态的推荐组合用无项 each 渲染固定重复内容{#each { length: n }, i}是生成 N 份占位棋盘、星评、表格行的惯用写法列表可能为空就写{:else}编译器会把它编译成空态分支语义清晰且零成本。适用前提说明以上行为描述基于当前仓库的 Svelte 5runes 时代实现legacy 模式非 runes下对列表项的重赋值会触发invalidate式的响应式刷新客户端转换器中有对应的TODO 6.0注释表明该行为是 legacy 模式专属具体差异可参考仓库中的 legacy 测试样本 与迁移指南 v5-migration-guide。【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考