This is the developer documentation for Svelte. # 概述 Svelte 是一个用于构建 web 用户界面的框架。它使用编译器将用 HTML、CSS 和 JavaScript 编写的声明式组件... ```svelte ``` ...转换为精简的、高度优化的 JavaScript。 你可以用它在 web 上构建任何东西,从独立组件到全栈应用(使用 Svelte 的配套应用框架 [SvelteKit](../kit)),以及介于两者之间的任何东西。 这些页面作为参考文档。如果你是 Svelte 新手,我们建议从[交互式教程](/tutorial)开始,遇到问题时再回到这里查阅。 你也可以在在线[演练场](/playground)尝试 Svelte,或者如果你需要更完整功能的环境,可以在 [StackBlitz](https://sveltekit.new) 上尝试。 # 入门指南 我们推荐使用 [SvelteKit](../kit),这是由 [Vite](https://vite.dev/) 驱动的 Svelte 官方应用框架: ```bash npx sv create myapp cd myapp npm install npm run dev ``` 如果你还不了解 Svelte 也不用担心!你现在可以暂时忽略 SvelteKit 带来的所有优秀特性,以后再深入了解。 ## SvelteKit 的替代方案 你也可以通过运行 `npm create vite@latest`、选择 `svelte` 选项直接使用带 Vite 的 Svelte。这样,`npm run build` 将使用 [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte) 在 `dist` 目录中生成 HTML、JS 和 CSS 文件。大多数情况下,你可能还需要[选择一个路由库](faq#Is-there-a-router)。 虽然也有 [Rollup、Webpack 和其他一些工具的插件](https://sveltesociety.dev/packages?category=build-plugins),但我们推荐使用 Vite。 ## 编辑器工具 Svelte 团队维护着一个 [VS Code 插件](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode),同时也有与其他[编辑器](https://sveltesociety.dev/resources#editor-support)和工具的集成。 你也可以使用 [sv check](https://github.com/sveltejs/cli) 在命令行检查你的代码。 ## 获取帮助 不要羞于在 [Discord 聊天室](/chat)寻求帮助!你也可以在 [Stack Overflow](https://stackoverflow.com/questions/tagged/svelte) 上找到答案。 # .svelte 文件 组件是 Svelte 应用程序的构建基块。它们被写入 `.svelte` 文件中,使用 HTML 的超集。 这三个部分 — 脚本、样式和标记 — 都是可选的。 ```svelte /// file: MyComponent.svelte ``` ## ` ``` 您可以从此代码块中 `export` 绑定,它们将成为编译后模块的导出。您不能使用 `export default`,因为默认导出是组件本身。 > [!NOTE] 如果你正在使用 TypeScript 并将 `module` 代码块的导出导入到 `.ts` 文件中,请确保设置好你的编辑器以便 TypeScript 能够识别它们。我们的 VS Code 插件和 IntelliJ 插件都支持这一点,但在其他情况下,你可能需要设置我们的 [TypeScript 编辑器插件](https://www.npmjs.com/package/typescript-svelte-plugin)。 > [!LEGACY] > 在 Svelte 4 中,此脚本标签是使用 ` ``` 你可以为 prop 指定一个默认值。如果组件使用者在实例化组件时没有指定该 prop,或者在某个时点传入的值为 `undefined`,将使用这个默认值。 ```svelte ``` 要获取所有属性,可以使用剩余语法: ```svelte ``` 你可以使用保留字作为 prop 名称。 ```svelte ``` 如果你使用 TypeScript,你可以声明 prop 类型: ```svelte ``` 如果你使用 JavaScript,你可以使用 JSDoc 声明 prop 类型: ```svelte ``` 如果你导出一个 `const`、`class` 或 `function`,它在组件外部是只读的。 ```svelte ``` 只读的 props 可以作为元素的属性访问,通过使用 [`bind:this` 语法](bindings#bind:this) 绑定到组件。 ### 响应式变量 要改变组件状态并触发重新渲染,只需要给使用 `$state` 符文声明的局部变量赋值即可。 更新表达式(`count += 1`)和属性赋值(`obj.x = y`)具有相同的效果。 ```svelte ``` Svelte 的 ` ``` 如果你想要响应 prop 的变化,请使用 `$derived` 或 `$effect` 符文。 ```svelte ``` 有关响应式的更多信息,请阅读关于符文的文档。 # 响应式基础知识 响应式是交互式 UI 的核心。当你点击一个按钮时,你期望得到某种响应。作为开发者,让这种响应发生是你的工作。而 Svelte 的工作是通过提供一个好的 API 来表达响应式系统,使你的工作尽可能直观。 ## 符文(Runes) Svelte 5 使用了 _符文_,这是一组强大的原语,用于控制 Svelte 组件内部以及 `.svelte.js` 和 `.svelte.ts` 模块中的响应式行为。 符文是为 Svelte 编译器提供指令的类函数符号。你不需要从任何地方导入它们 — 当你使用 Svelte 时,它们就是语言的一部分。 以下部分将从高层次介绍用于声明状态、派生状态和副作用的最重要的符文。有关更多详细信息,请参阅后面关于[状态](state)和[副作用](side-effects)的章节。 ## `$state` 响应式状态通过 `$state` 符文声明: ```svelte ``` 你也可以在类字段中使用 `$state`(无论是公共的还是私有的): ```js // @errors: 7006 2554 class Todo { done = $state(false); text = $state(); constructor(text) { this.text = text; } } ``` > [!LEGACY] > 在 Svelte 4 中,如果变量在顶层声明,状态会隐式地变为响应式 > > ```svelte > > > > ``` ## `$derived` 派生状态通过 `$derived` 符文声明: ```svelte

{count} doubled is {doubled}

``` `$derived(...)` 内部的表达式应该没有副作用。Svelte 将不允许在派生表达式内部进行状态更改(例如 `count++`)。 与 `$state` 一样,你可以将类字段标记为 `$derived`。 > [!LEGACY] > 在 Svelte 4 中,你可以使用响应式语句来实现这一点。 > > ```svelte > > > > >

{count} doubled is {doubled}

> ``` > > 这只在组件的顶层有效。 ## `$effect` 要在组件挂载到 DOM 时以及值发生变化时运行 _副作用_,我们可以使用 `$effect` 符文([演示](/playground/untitled#H4sIAAAAAAAAE31T24rbMBD9lUG7kAQ2sbdlX7xOYNk_aB_rQhRpbAsU2UiTW0P-vbrYubSlYGzmzMzROTPymdVKo2PFjzMzfIusYB99z14YnfoQuD1qQh-7bmdFQEonrOppVZmKNBI49QthCc-OOOH0LZ-9jxnR6c7eUpOnuv6KeT5JFdcqbvbcBcgDz1jXKGg6ncFyBedYR6IzLrAZwiN5vtSxaJA-EzadfJEjKw11C6GR22-BLH8B_wxdByWpvUYtqqal2XB6RVkG1CoHB6U1WJzbnYFDiwb3aGEdDa3Bm1oH12sQLTcNPp7r56m_00mHocSG97_zd7ICUXonA5fwKbPbkE2ZtMJGGVkEdctzQi4QzSwr9prnFYNk5hpmqVuqPQjNnfOJoMF22lUsrq_UfIN6lfSVyvQ7grB3X2mjMZYO3XO9w-U5iLx42qg29md3BP_ni5P4gy9ikTBlHxjLzAtPDlyYZmRdjAbGq7HprEQ7p64v4LU_guu0kvAkhBim3nMplWl8FreQD-CW20aZR0wq12t-KqDWeBywhvexKC3memmDwlHAv9q4Vo2ZK8KtK0CgX7u9J8wXbzdKv-nRnfF_2baTqlYoWUF2h5efl9-n0O6koAMAAA==)): ```svelte ``` 传递给 `$effect` 的函数将在组件挂载时运行,并在其读取的使用 `$state` 或 `$derived` 声明的值(包括通过 `$props` 传入的值)发生变化后重新运行。重新运行是批处理的(即在同一时刻改变 `color` 和 `size` 不会导致两次单独的运行),并且会在任何 DOM 更新应用之后发生。 > [!LEGACY] > 在 Svelte 4 中,你可以使用响应式语句来实现这一点。 > > ```svelte > > > > ``` > > 这只在组件的顶层有效。 # 什么是符文? > [!NOTE] **符文** /ro͞on/ _名词_ > > 一个用作神秘或魔法符号的字母或标记。 符文是你在 `.svelte` 和 `.svelte.js`/`.svelte.ts` 文件中用来控制 Svelte 编译器的符号。如果你把 Svelte 看作一门语言,符文就是语法的一部分 — 它们是 _关键字_。 符文有一个 `$` 前缀,看起来像函数: ```js let message = $state('hello'); ``` 然而,它们与普通的 JavaScript 函数有很大不同: - 你不需要导入它们 — 它们是语言的一部分 - 它们不是值 — 你不能将它们赋值给变量或作为参数传递给函数 - 就像 JavaScript 关键字一样,它们只在特定位置有效(如果你把它们放在错误的地方,编译器会提示你) > [!LEGACY] > 在 Svelte 5 之前,符文是不存在的。 # $state `$state` 符文允许你创建响应式状态,这意味着当状态改变时,你的 UI 会作出响应。 ```svelte ``` 与你可能遇到的其他框架不同,这里没有用于操作状态的 API —— `count` 只是一个数字,而不是对象或函数,你可以像更新任何其他变量一样更新它。 ### 深层状态 如果 `$state` 用于数组或简单对象,结果将是一个深度响应式的*状态代理*。[代理(Proxies)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)允许 Svelte 在你读取或写入属性时运行代码,包括通过像 `array.push(...)` 这样的方法,触发精确的更新。 > [!NOTE] 像 `Set` 和 `Map` 这样的类不会被代理,但 Svelte 为这些内置类型提供了响应式实现,可以从 [`svelte/reactivity`](./svelte-reactivity) 导入。 状态会递归地进行代理,直到 Svelte 找到数组或简单对象以外的东西。在像这样的情况下... ```js let todos = $state([ { done: false, text: '添加更多待办事项' } ]); ``` ...修改单个待办事项的属性将触发 UI 中依赖该特定属性的任何内容的更新: ```js let todos = [{ done: false, text: '添加更多待办事项' }]; // ---cut--- todos[0].done = !todos[0].done; ``` 如果你向数组推入一个新对象,它也会被代理: ```js // @filename: ambient.d.ts declare global { const todos: Array<{ done: boolean, text: string }> } // @filename: index.js // ---cut--- todos.push({ done: false, text: '吃午饭' }); ``` > [!NOTE] 当你更新代理的属性时,原始对象不会被改变。 注意,如果你解构一个响应式值,解构后的引用不是响应式的 —— 就像普通的 JavaScript 一样,它们在解构时就被求值了:: ```js let todos = [{ done: false, text: '添加更多待办事项' }]; // ---cut--- let { done, text } = todos[0]; // 这不会影响 `done` 的值 todos[0].done = !todos[0].done; ``` ### 类 你也可以在类字段中使用 `$state`(无论是公共的还是私有的): ```js // @errors: 7006 2554 class Todo { done = $state(false); text = $state(); constructor(text) { this.text = text; } reset() { this.text = ''; this.done = false; } } ``` > [!NOTE] 编译器将 `done` 和 `text` 转换为类原型上引用私有字段的 `get`/`set` 方法。这意味着这些属性是不可枚举的。 在 JavaScript 中调用方法时,[`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) 的值很重要。下面这种写法不会起作用,因为 `reset` 方法中的 `this` 将是 ` ``` 你可以使用内联函数... ```svelte ``` ...或者在类定义中使用箭头函数: ```js // @errors: 7006 2554 class Todo { done = $state(false); text = $state(); constructor(text) { this.text = text; } +++reset = () => {+++ this.text = ''; this.done = false; } } ``` ## `$state.raw` 在不希望对象和数组具有深度响应性的情况下,你可以使用 `$state.raw`。 使用 `$state.raw` 声明的状态不能被改变;它只能被重新赋值。换句话说,与其给对象的属性赋值或使用数组方法如 `push`,不如在想要更新时完全替换对象或数组: ```js let person = $state.raw({ name: 'Heraclitus', age: 49 }); // 这将不会生效 person.age += 1; // 这将生效,因为我们创建了一个新的 person person = { name: 'Heraclitus', age: 50 }; ``` 这可以提高性能,特别是对于那些你本来就不打算改变的大型数组和对象,因为它避免了使它们变成响应式的开销。注意,原始状态可以包含响应式状态(例如,一个包含响应式对象的原始数组)。 ## `$state.snapshot` 要获取深度响应式 `$state` 代理的静态快照,使用 `$state.snapshot`: ```svelte ``` 当你想要将某些状态传递给不希望接收代理的外部库或 API(如 `structuredClone`)时,这会很有用。 ## 将状态传递给函数 JavaScript 是一种按值传递的语言 —— 当你调用一个函数时,参数是值而不是变量。换句话说: ```js /// file: index.js // @filename: index.js // ---cut--- /** * @param {number} a * @param {number} b */ function add(a, b) { return a + b; } let a = 1; let b = 2; let total = add(a, b); console.log(total); // 3 a = 3; b = 4; console.log(total); // 仍然是 3! ``` 如果 `add` 想要访问 `a` 和 `b` 的当前值,并返回当前的 `total` 值,你需要使用函数: ```js /// file: index.js // @filename: index.js // ---cut--- /** * @param {() => number} getA * @param {() => number} getB */ function add(+++getA, getB+++) { return +++() => getA() + getB()+++; } let a = 1; let b = 2; let total = add+++(() => a, () => b)+++; console.log(+++total()+++); // 3 a = 3; b = 4; console.log(+++total()+++); // 7 ``` Svelte 中的状态也不例外 —— 当你引用使用 `$state` 符文声明的内容时... ```js let a = +++$state(1)+++; let b = +++$state(2)+++; ``` ...你访问的是它的当前值。 注意,"函数"的范围很广 —— 它包括代理的属性和 [`get`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)/[`set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) 属性... ```js /// file: index.js // @filename: index.js // ---cut--- /** * @param {{ a: number, b: number }} input */ function add(input) { return { get value() { return input.a + input.b; } }; } let input = $state({ a: 1, b: 2 }); let total = add(input); console.log(total.value); // 3 input.a = 3; input.b = 4; console.log(total.value); // 7 ``` ...不过如果你发现自己在写这样的代码,考虑使用[类](#Classes)代替。 # $derived 派生状态通过 `$derived` 符文声明: ```svelte

{count} 的两倍是 {doubled}

``` `$derived(...)` 内的表达式应该没有副作用。Svelte 将不允许在派生表达式内进行状态更改(例如 `count++`)。 与 `$state` 一样,你可以将类字段标记为 `$derived`。 > [!NOTE] Svelte 组件中的代码仅在创建时执行一次。如果没有 `$derived` 符文,即使 `count` 发生变化,`doubled` 也会保持其原始值。 ## `$derived.by` 有时你需要创建不适合放在简短表达式中的复杂派生。在这些情况下,你可以使用 `$derived.by`,它接受一个函数作为参数。 ```svelte ``` 本质上,`$derived(expression)` 等同于 `$derived.by(() => expression)`。 ## 理解依赖关系 在 `$derived` 表达式(或 `$derived.by` 函数体)内同步读取的任何内容都被视为派生状态的*依赖项*。当状态发生变化时,派生将被标记为*脏数据(dirty)*,并在下次读取时重新计算。 要使一段状态不被视为依赖项,请使用 [`untrack`](svelte#untrack)。 # $effect Effects 使你的应用程序能够 _做点事情_。当 Svelte 运行一个 effect 函数时,它会跟踪被访问(除非在 [`untrack`](svelte#untrack) 中访问)的状态(和派生状态),并在该状态后续发生变化时重新运行该函数。 Svelte 应用程序中的大多数 effects 是由 Svelte 本身创建的——例如,当 `name` 变化时,更新 `

hello {name}!

` 中的文本。 但你也可以使用 `$effect` 符文创建自己的 effects,当你需要将外部系统(无论是库、`` 元素,还是跨网络的某些东西)与 Svelte 应用程序内部的状态同步时,这非常有用。 > [!NOTE] 避免过度使用 `$effect`!当你在 effects 中做太多工作时,代码通常会变得难以理解和维护。请参阅 [何时不使用 `$effect`](#When-not-to-use-$effect) 了解替代方法。 你的 effects 在组件挂载到 DOM 之后运行,并在状态变化后的 [微任务](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide) 中运行([demo](/playground/untitled#H4sIAAAAAAAAE31S246bMBD9lZF3pSRSAqTVvrCAVPUP2sdSKY4ZwJJjkD0hSVH-vbINuWxXfQH5zMyZc2ZmZLVUaFn6a2R06ZGlHmBrpvnBvb71fWQHVOSwPbf4GS46TajJspRlVhjZU1HqkhQSWPkHIYdXS5xw-Zas3ueI6FRn7qHFS11_xSRZhIxbFtcDtw7SJb1iXaOg5XIFeQGjzyPRaevYNOGZIJ8qogbpe8CWiy_VzEpTXiQUcvPDkSVrSNZz1UlW1N5eLcqmpdXUvaQ4BmqlhZNUCgxuzFHDqUWNAxrYeUM76AzsnOsdiJbrBp_71lKpn3RRbii-4P3f-IMsRxS-wcDV_bL4PmSdBa2wl7pKnbp8DMgVvJm8ZNskKRkEM_OzyOKQFkgqOYBQ3Nq89Ns0nbIl81vMFN-jKoLMTOr-SOBOJS-Z8f5Y6D1wdcR8dFqvEBdetK-PHwj-z-cH8oHPY54wRJ8Ys7iSQ3Bg3VA9azQbmC9k35kKzYa6PoVtfwbbKVnBixBiGn7Pq0rqJoUtHiCZwAM3jdTPWCVtr_glhVrhecIa3vuksJ_b7TqFs4DPyriSjd5IwoNNQaAmNI-ESfR2p8zimzvN1swdCkvJHPH6-_oX8o1SgcIDAAA=)): ```svelte ``` 重新运行是批量处理的(即在同一时刻更改 `color` 和 `size` 不会导致两次单独的运行),并在所有 DOM 更新完成后发生。 你可以将 `$effect` 放在任何地方,不仅仅在组件的顶层,只要在组件初始化时调用它(或者在父 effect 处于激活状态时)。它就会与组件(或父 effect)的生命周期绑定,因此当组件卸载(或父 effect 被销毁)时,它会自行销毁。 你可以从 `$effect` 返回一个函数,该函数将在 effect 重新运行之前立即运行,并在它被销毁之前运行([demo](/playground/untitled#H4sIAAAAAAAAE42RQY-bMBCF_8rI2kPopiXpMQtIPfbeW6m0xjyKtWaM7CFphPjvFVB2k2oPe7LmzXzyezOjaqxDVKefo5JrD3VaBLVXrLu5-tb3X-IZTmat0hHv6cazgCWqk8qiCbaXouRSHISMH1gop4coWrA7JE9bp7PO2QjjuY5vA8fDYZ3hUh7QNDCy2yWUFzTOUilpSj9aG-linaMKFGACtKCmSwvGGYGeLQvCWbtnMq3m34grajxHoa1JOUXI93_V_Sfz7Oz7Mafj0ypN-zvHm8dSAmQITP_xaUq2IU1GO1dp80I2Uh_82dao92Rl9R8GvgF0QrbrUFstcFeq0PgAkha0LoICPoeB4w1SJUvsZcj4rvcMlvmvGlGCv6J-DeSgw2vabQnJlm55p7nM0rcTctYei3HZxZSl7XHVqkHEM3k2zpqXfFyj393zU05fpyI6f0HI0hUoPoamC9roKDeo2ivBH1EnCQOmX9NfYw2GHrgCAAA=))。 ```svelte

{count}

``` ### 理解依赖关系 `$effect` 会自动获取在其函数体内 _同步_ 读取的任何响应值(`$state`、`$derived`、`$props`),并将它们注册为依赖关系。当这些依赖关系发生变化时,`$effect` 会安排重新运行。 在 `await` 之后或在 `setTimeout` 内部等情况下读取的值将不会被追踪。在这里,当 `color` 变化时,canvas 会重新绘制,但当 `size` 变化时将不会变化([demo](/playground/untitled#H4sIAAAAAAAAE31T246bMBD9lZF3pWSlBEirfaEQqdo_2PatVIpjBrDkGGQPJGnEv1e2IZfVal-wfHzmzJyZ4cIqqdCy9M-F0blDlnqArZjmB3f72XWRHVCRw_bc4me4aDWhJstSlllhZEfbQhekkMDKfwg5PFvihMvX5OXH_CJa1Zrb0-Kpqr5jkiwC48rieuDWQbqgZ6wqFLRcvkC-hYvnkWi1dWqa8ESQTxFRjfQWsOXiWzmr0sSLhEJu3p1YsoJkNUcdZUnN9dagrBu6FVRQHAM10sJRKgUG16bXcGxQ44AGdt7SDkTDdY02iqLHnJVU6hedlWuIp94JW6Tf8oBt_8GdTxlF0b4n0C35ZLBzXb3mmYn3ae6cOW74zj0YVzDNYXRHFt9mprNgHfZSl6mzml8CMoLvTV6wTZIUDEJv5us2iwMtiJRyAKG4tXnhl8O0yhbML0Wm-B7VNlSSSd31BG7z8oIZZ6dgIffAVY_5xdU9Qrz1Bnx8fCfwtZ7v8Qc9j3nB8PqgmMWlHIID6-bkVaPZwDySfWtKNGtquxQ23Qlsq2QJT0KIqb8dL0up6xQ2eIBkAg_c1FI_YqW0neLnFCqFpwmreedJYT7XX8FVOBfwWRhXstZrSXiwKQjUhOZeMIleb5JZfHWn2Yq5pWEpmR7Hv-N_wEqT8hEEAAA=)): ```ts // @filename: index.ts declare let canvas: { width: number; height: number; getContext(type: '2d', options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D; }; declare let color: string; declare let size: number; // ---cut--- $effect(() => { const context = canvas.getContext('2d'); context.clearRect(0, 0, canvas.width, canvas.height); // 每当 `color` 发生变化时,这段代码都会重新运行... context.fillStyle = color; setTimeout(() => { // ...但当 `size` 发生变化时却不会 context.fillRect(0, 0, size, size); }, 0); }); ``` effect 仅在它读取的对象发生变化时才重新运行,而不是在对象内部的属性发生变化时。(如果你想在开发时观察一个对象内部的变化,可以使用 [`$inspect`]($inspect)。) ```svelte

{state.value} 的两倍是 {derived.value}

``` effect 仅依赖于它上次运行时读取的值。如果 `a` 为真,则对 `b` 的更改不会 [导致该 effect 重新运行](/playground/untitled#H4sIAAAAAAAAE3WQ0WrDMAxFf0U1hTow1vcsMfQ7lj3YjlxEXTvEymC4_vfFC6Ewtidxde8RkrJw5DGJ9j2LoO8oWnGZJvEi-GuqIn2iZ1x1istsa6dLdqaJ1RAG9sigoYdjYs0onfYJm7fdMX85q3dE59CylA30CnJtDWxjSNHjq49XeZqXEChcT9usLUAOpIbHA0yzM78oColGhDVofLS3neZSS6mqOz-XD51ZmGOAGKwne-vztk-956CL0kAJsi7decupf4l658EUZX4I8yTWt93jSI5wFC3PC5aP8g0Aje5DcQEAAA==): ```ts let a = false; let b = false; // ---cut--- $effect(() => { console.log('运行中'); if (a || b) { console.log('在 if 块内'); } }); ``` ## `$effect.pre` 在极少数情况下,你可能需要在 DOM 更新 _之前_ 运行代码。为此,我们可以使用 `$effect.pre` 符文: ```svelte
{#each messages as message}

{message}

{/each}
``` 除了时机不同,`$effect.pre` 的工作方式与 `$effect` 完全相同。 ## `$effect.tracking` `$effect.tracking` 符文是一个高级特性,用于告知你代码是否在跟踪上下文中运行,例如 effect 或模板内部 ([demo](/playground/untitled#H4sIAAAAAAAACn3PwYrCMBDG8VeZDYIt2PYeY8Dn2HrIhqkU08nQjItS-u6buAt7UDzmz8ePyaKGMWBS-nNRcmdU-hHUTpGbyuvI3KZvDFLal0v4qvtIgiSZUSb5eWSxPfWSc4oB2xDP1XYk8HHiSHkICeXKeruDDQ4Demlldv4y0rmq6z10HQwuJMxGVv4mVVXDwcJS0jP9u3knynwtoKz1vifT_Z9Jhm0WBCcOTlDD8kyspmML5qNpHg40jc3fFryJ0iWsp_UHgz3180oBAAA=)): ```svelte

在模板中: {$effect.tracking()}

``` 这允许你(例如)添加诸如订阅之类的内容而不会导致内存泄漏,方法是将它们放在子 effects 中。以下是一个 `readable` 函数,只要它在跟踪上下文中就会监听回调函数的变化: ```ts import { tick } from 'svelte'; export default function readable( initial_value: T, start: (callback: (update: (v: T) => T) => T) => () => void ) { let value = $state(initial_value); let subscribers = 0; let stop: null | (() => void) = null; return { get value() { // 如果在跟踪上下文中 ... if ($effect.tracking()) { $effect(() => { // ...且订阅者还没有 if (subscribers === 0) { // ...调用函数并监听变化以更新状态 stop = start((fn) => (value = fn(value))); } subscribers++; // 返回的回调在监听器取消监听时调用 return () => { tick().then(() => { subscribers--; // 如果是最后一个订阅者... if (subscribers === 0) { // ...停止监听变化 stop?.(); stop = null; } }); }; }); } return value; } }; } ``` ## `$effect.root` `$effect.root` 符文是一个高级特性,它创建了一个不会自动清理的非跟踪作用域。这对于需要手动控制的嵌套 effects 很有用。这个符文还允许在组件初始化阶段之外创建 effects。 ```svelte ``` ## 什么时候不应该使用 `$effect` 总体而言,`$effect` 最好被视为一种逃生舱口——适用于分析和直接 DOM 操作等场景——而不是一个应该频繁使用的工具。特别是要避免使用它来同步状态。千万不要这样做... ```svelte ``` ...请这样做: ```svelte ``` > [!NOTE] 对于比像 `count * 2` 这样的简单表达式更复杂的内容,你也可以使用 `$derived.by`。 你可能会想用 effects 以复杂的方式将一个值链接到另一个值。以下示例展示了两个输入框:"已花费金额"和"剩余金额",它们彼此关联。如果你更新其中一个,另一个应该相应更新。不要为此使用 effects([demo](/playground/untitled#H4sIAAAAAAAACpVRy26DMBD8FcvKgUhtoIdeHBwp31F6MGSJkBbHwksEQvx77aWQqooq9bgzOzP7mGTdIHipPiZJowOpGJAv0po2VmfnDv4OSBErjYdneHWzBJaCjcx91TWOToUtCIEE3cig0OIty44r5l1oDtjOkyFIsv3GINQ_CNYyGegd1DVUlCR7oU9iilDUcP8S8roYs9n8p2wdYNVFm4csTx872BxNCcjr5I11fdgonEkXsjP2CoUUZWMv6m6wBz2x7yxaM-iJvWeRsvSbSVeUy5i0uf8vKA78NIeJLSZWv1I8jQjLdyK4XuTSeIdmVKJGGI4LdjVOiezwDu1yG74My8PLCQaSiroe5s_5C2PHrkVGAgAA)): ```svelte ``` 相反,尽可能使用回调([demo](/playground/untitled#H4sIAAAAAAAACo1SMW6EMBD8imWluFMSIEUaDiKlvy5lSOHjlhOSMRZeTiDkv8deMEEJRcqdmZ1ZjzzxqpZgePo5cRw18JQA_sSVaPz0rnVk7iDRYxdhYA8vW4Wg0NnwzJRdrfGtUAVKQIYtCsly9pIkp4AZ7cQOezAoEA7JcWUkVBuCdol0dNWrEutWsV5fHfnhPQ5wZJMnCwyejxCh6G6A0V3IHk4zu_jOxzzPBxBld83PTr7xXrb3rUNw8PbiYJ3FP22oTIoLSComq5XuXTeu8LzgnVA3KDgj13wiQ8taRaJ82rzXskYM-URRlsXktejjgNLoo9e4fyf70_8EnwncySX1GuunX6kGRwnzR_BgaPNaGy3FmLJKwrCUeBM6ZUn0Cs2mOlp3vwthQJ5i14P9st9vZqQlsQIAAA==)): ```svelte ``` 如果您出于任何原因需要使用绑定(例如当您想要某种"可写的 `$derived`"时),请考虑使用 getter 和 setter 来同步状态([demo](/playground/untitled#H4sIAAAAAAAACpWRwW6DMBBEf8WyekikFOihFwcq9TvqHkyyQUjGsfCCQMj_XnvBNKpy6Qn2DTOD1wu_tRocF18Lx9kCFwT4iRvVxenT2syNoDGyWjl4xi93g2AwxPDSXfrW4oc0EjUgwzsqzSr2VhTnxJwNHwf24lAhHIpjVDZNwy1KS5wlNoGMSg9wOCYksQccerMlv65p51X0p_Xpdt_4YEy9yTkmV3z4MJT579-bUqsaNB2kbI0dwlnCgirJe2UakJzVrbkKaqkWivasU1O1ULxnOVk3JU-Uxti0p_-vKO4no_enbQ_yXhnZn0aHs4b1jiJMK7q2zmo1C3bTMG3LaZQVrMjeoSPgaUtkDxePMCEX2Ie6b_8D4WyJJEwCAAA=)): ```svelte ``` 如果您必须在 effect 中更新 `$state` 并且因为你读取和写入的是同一个 `$state` 而陷入无限循环,请使用 [untrack](svelte#untrack)。 # $props 组件的输入称为 _props_,这是 _properties_ 的缩写。你将 props 传递给组件,就像将属性传递给元素一样: ```svelte ``` 另一方面,在 `MyComponent.svelte` 内部,我们可以使用 `$props` 符文接收 props... ```svelte

这个组件是 {props.adjective}

``` ...更常见的是,你会 [_解构_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) 你的 props: ```svelte

这个组件是 {+++adjective+++}

``` ## 后备值(Fallback values) 解构允许我们声明后备值,如果父组件没有设置给定的 prop,就会使用这些后备值: ```js let { adjective = 'happy' } = $props(); ``` > [!NOTE] 后备值不会变成响应式状态代理(有关更多信息,请参见[更新 props](#Updating-props)) ## 重命名 props 我们也可以用解构赋值来重命名 props,如果它们是无效标识符,或者是像 `super` 这样的 JavaScript 关键字,这样做是必要的: ```js let { super: trouper = '光芒终会照亮我' } = $props(); ``` ## 剩余 props 最后,我们可以使用 _剩余属性_ 来获取,其实就是获取剩下的 props: ```js let { a, b, c, ...others } = $props(); ``` ## 更新 props 当 prop 本身更新时,组件内部对该 prop 的引用也会更新——当 `count` 在 `App.svelte` 中发生变化时,它在 `Child.svelte` 内部也会相应改变。但是子组件能暂时覆盖 prop 的值,这对未保存的临时状态很有用([demo](/playground/untitled#H4sIAAAAAAAAE6WQ0WrDMAxFf0WIQR0Wmu3VTQJln7HsIfVcZubIxlbGRvC_DzuBraN92qPula50tODZWB1RPi_IX16jLALWSOOUq6P3-_ihLWftNEZ9TVeOWBNHlNhGFYznfqCBzeRdYHh6M_YVzsFNsNs3pdpGd4eBcqPVDMrNxNDBXeSRtXioDgO1zU8ataeZ2RE4Utao924RFXQ9iHXwvoPHKpW1xY4g_Bg0cSVhKS0p560Za95612ZC02ONrD8ZJYdZp_rGQ37ff_mSP86Np2TWZaNNmdcH56P4P67K66_SXoK9pG-5dF5Z9QEAAA==)): ```svelte ``` ```svelte ``` 虽然你可以临时 _重新赋值_ props,但除非它们是 [可绑定]($bindable) 的 props,否则不应 _修改_ props。 如果 prop 是一个普通对象,修改将没有效果([demo](/playground/untitled#H4sIAAAAAAAAE3WQwU7DMBBEf2W1QmorQgJXk0RC3PkBwiExG9WQrC17U4Es_ztKUkQp9OjxzM7bjcjtSKjwyfKNp1aLORA4b13ADHszUED1HFE-3eyaBcy-Mw_O5eFAg8xa1wb6T9eWhVgCKiyD9sZJ3XAjZnTWCzzuzfAKvbcjbPJieR2jm_uGy-InweXqtd0baaliBG0nFgW3kBIUNWYo9CGoxE-UsgvIpw2_oc9-LmAPJBCPDJCggqvlVtvdH9puErEMlvVg9HsVtzuoaojzkKKAfRuALVDfk5ZZW0fmy05wXcFdwyktlUs-KIinljTXrRVnm7-kL9dYLVbUAQAA)): ```svelte ``` ```svelte ``` 如果 prop 是一个响应式状态代理,那么修改它会产生效果,但你会看到一个 [`ownership_invalid_mutation`](runtime-warnings#Client-warnings-ownership_invalid_mutation) 警告,因为该组件正在修改不“属于”它的状态([demo](/playground/untitled#H4sIAAAAAAAAE3WR0U7DMAxFf8VESBuiauG1WycheOEbKA9p67FA6kSNszJV-XeUZhMw2GN8r-1znUmQ7FGU4pn2UqsOes-SlSGRia3S6ET5Mgk-2OiJBZGdOh6szd0eNcdaIx3-V28NMRI7UYq1awdleVNTzaq3ZmB43CndwXYwPSzyYn4dWxermqJRI4Np3rFlqODasWRcTtAaT1zCHYSbVU3r4nsyrdPMKTUFKDYiE4yfLEoePIbsQpqfy3_nOVMuJIqg0wk1RFg7GOuWfwEbz2wIDLVatR_VtLyBagNTHFIUMCqtoZXeIfAOU1JoUJsR2IC3nWTMjt7GM4yKdyBhlAMpesvhydCC0y_i0ZagHByMh26WzUhXUUxKnpbcVnBfUwhznJnNlac7JkuIURL-2VVfwxflyrWcSQIAAA==)): ```svelte ``` ```svelte ``` 未使用 `$bindable` 声明的 prop 的后备值将保持不变——它不会被转换为响应式状态代理——这意味着对其的修改不会触发更新([demo](/playground/untitled#H4sIAAAAAAAAE3WQwU7DMBBEf2VkIbUVoYFraCIh7vwA4eC4G9Wta1vxpgJZ_nfkBEQp9OjxzOzTRGHlkUQlXpy9G0gq1idCL43ppDrAD84HUYheGwqieo2CP3y2Z0EU3-En79fhRIaz1slA_-nKWSbLQVRiE9SgPTetbVkfvRsYzztttugHd8RiXU6vr-jisbWb8idhN7O3bEQhmN5ZVDyMlIorcOddv_Eufq4AGmJEuG5PilEjQrnRcoV7JCTUuJlGWq7-YHYjs7NwVhmtDnVcrlA3iLmzLLGTAdaB-j736h68Oxv-JM1I0AFjoG1OzPfX023c1nhobUoT39QeKsRzS8owM8DFTG_pE6dcVl70AQAA)): ```svelte ``` 总之:不要修改 props。要么使用回调 props 来传递变化,要么——如果父子组件应该共享同一个对象——使用 [`$bindable`]($bindable) 符文。 ## 类型安全 通过对 props 进行注解,你可以为组件添加类型安全,就像对其他变量声明一样。在 TypeScript 中,它看起来是这样的... ```svelte ``` ...而在 JSDoc 中你可以这样做: ```svelte ``` 当然,你也可以将类型声明与注解分开: ```svelte ``` 建议添加类型,这样可以确保使用你组件的人可以轻松发现他们应该提供哪些 props。 # $bindable 通常情况下,props 是单向的,从父组件流向子组件。这使得理解应用中的数据流向变得容易。 在 Svelte 中,组件的 props 可以被*绑定*,这意味着数据也可以从子组件流向父组件。这不是你应该经常做的事情,但如果谨慎且适度地使用,可以简化你的代码。 这也意味着状态代理可以在子组件中被*修改*。 > [!NOTE] 普通的 props 也可以被修改,但强烈不建议这样做 — 如果 Svelte 检测到一个组件正在修改它不"拥有"的状态,会发出警告。 要将一个 prop 标记为可绑定的,我们使用 `$bindable` 符文: ```svelte /// file: FancyInput.svelte ``` 现在,使用 `` 的组件可以添加 [`bind:`](bind) 指令([demo](/playground/untitled#H4sIAAAAAAAAE3WQwWrDMBBEf2URBSfg2nfFMZRCoYeecqx6UJx1IyqvhLUONcb_XqSkTUOSk1az7DBvJtEai0HI90nw6FHIJIhckO7i78n7IhzQctS2OuAtvXHESByEFFVoeuO5VqTYdN71DC-amvGV_MDQ9q6DrCjP0skkWymKJxYZOgxBfyKs4SGwZlxke7TWZcuVoqo8-1P1z3lraCcP2g64nk4GM5S1osrXf0JV-lrkgvGbheR-wDm_g30V8JL-1vpOCZFogpQsEsWcemtxscyhKArfOx9gjps0Lq4hzRVfemaYfu-PoIqqwKPFY_XpaIqj4tYRP7a6M3aUkD27zjSw0RTgbZN6Z8WNs66XsEP03tBXUueUJFlelvYx_wCuI3leNwIAAA==)): ```svelte /// App.svelte

{message}

``` 父组件不*一定*非要使用 `bind:` — 它可以只传递一个普通的 prop。有些父组件不想听取子组件要说的话。 在这种情况下,你可以为没有传递 prop 时指定一个后备值: ```js /// file: FancyInput.svelte let { value = $bindable('fallback'), ...props } = $props(); ``` # $inspect > [!NOTE] `$inspect` 仅在开发环境有效。在生产构建中它会变成空操作。 `$inspect` 符文大致等同于 `console.log`,不同之处在于当其参数发生变化时它会重新运行。`$inspect` 会深度跟踪响应式状态,这意味着使用细粒度响应性更新对象或数组内的内容会导致它重新触发([demo](/playground/untitled#H4sIAAAAAAAACkWQ0YqDQAxFfyUMhSotdZ-tCvu431AXtGOqQ2NmmMm0LOK_r7Utfby5JzeXTOpiCIPKT5PidkSVq2_n1F7Jn3uIcEMSXHSw0evHpAjaGydVzbUQCmgbWaCETZBWMPlKj29nxBDaHj_edkAiu12JhdkYDg61JGvE_s2nR8gyuBuiJZuDJTyQ7eE-IEOzog1YD80Lb0APLfdYc5F9qnFxjiKWwbImo6_llKRQVs-2u91c_bD2OCJLkT3JZasw7KLA2XCX31qKWE6vIzNk1fKE0XbmYrBTufiI8-_8D2cUWBA_AQAA)): ```svelte ``` ## $inspect(...).with `$inspect` 返回一个 `with` 属性,你可以用回调函数调用它,该回调函数将代替 `console.log` 被调用。回调函数的第一个参数是 `"init"` 或 `"update"`;后续参数是传递给 `$inspect` 的值([demo](/playground/untitled#H4sIAAAAAAAACkVQ24qDMBD9lSEUqlTqPlsj7ON-w7pQG8c2VCchmVSK-O-bKMs-DefKYRYx6BG9qL4XQd2EohKf1opC8Nsm4F84MkbsTXAqMbVXTltuWmp5RAZlAjFIOHjuGLOP_BKVqB00eYuKs82Qn2fNjyxLtcWeyUE2sCRry3qATQIpJRyD7WPVMf9TW-7xFu53dBcoSzAOrsqQNyOe2XUKr0Xi5kcMvdDB2wSYO-I9vKazplV1-T-d6ltgNgSG1KjVUy7ZtmdbdjqtzRcphxMS1-XubOITJtPrQWMvKnYB15_1F7KKadA_AQAA)): ```svelte ``` 一个找到某些更改的来源的便捷方法是将 `console.trace` 传递给 `with`: ```js // @errors: 2304 $inspect(stuff).with(console.trace); ``` ## $inspect.trace(...) 这个符文在 5.14 版本中添加,会使周围的函数在开发环境中被追踪。每当函数作为 [effect]($effect) 或 [derived]($derived) 的一部分重新运行时,控制台都会打印出哪些响应式状态导致了 effect 触发。 ```svelte ``` `$inspect.trace` 接受一个可选的第一参数,该参数将被用作标签。 # $host 当将组件编译为自定义元素时,`$host` 符文提供了对宿主元素的访问,使您能够(例如)触发自定义事件([demo](/playground/untitled#H4sIAAAAAAAAE41Ry2rDMBD8FSECtqkTt1fHFpSSL-ix7sFRNkTEXglrnTYY_3uRlDgxTaEHIfYxs7szA9-rBizPPwZOZwM89wmecqxbF70as7InaMjltrWFR3mpkQDJ8pwXVnbKkKiwItUa3RGLVtk7gTHQXRDR2lXda4CY1D0SK9nCUk0QPyfrCovsRoNFe17aQOAwGncgO2gBqRzihJXiQrEs2csYOhQ-7HgKHaLIbpRhhBG-I2eD_8ciM4KnnOCbeE5dD2P6h0Dz0-Yi_arNhPLJXBtSGi2TvSXdbpqwdsXvjuYsC1veabvvUTog2ylrapKH2G2XsMFLS4uDthQnq2t1cwKkGOGLvYU5PvaQxLsxOkPmsm97Io1Mo2yUPF6VnOZFkw1RMoopKLKAE_9gmGxyDFMwMcwN-Bx_ABXQWmOtAgAA)): ```svelte /// file: Stepper.svelte ``` ```svelte /// file: App.svelte count -= 1} onincrement={() => count += 1} >

计数: {count}

``` # 基础标记 Svelte 组件内的标记可以被理解为增强版的 HTML。 ## 标签 小写标签,如 `
`,表示常规的 HTML 元素。大写标签或使用点符号的标签,如 `` 或 ``,表示一个*组件*。 ```svelte
``` ## 元素属性 默认情况下,属性的工作方式与其 HTML 对应项完全相同。 ```svelte
``` 与 HTML 一样,属性值可以不加引号。 ```svelte ``` 属性值可以包含 JavaScript 表达式。 ```svelte page {p} ``` 或者它们本身就可以*是* JavaScript 表达式。 ```svelte ``` 如果布尔属性的值为[真值](https://developer.mozilla.org/en-US/docs/Glossary/Truthy),则会包含在元素中,如果为[假值](https://developer.mozilla.org/en-US/docs/Glossary/Falsy),则会被排除。 所有其他属性除非其值为[空值](https://developer.mozilla.org/en-US/docs/Glossary/Nullish)(`null` 或 `undefined`),否则都会被包含。 ```svelte
This div has no title attribute
``` > [!NOTE] 在单个表达式外加引号不会影响值的解析方式,但在 Svelte 6 中会导致值被强制转换为字符串: > > > ```svelte > > ``` 当属性名和值相同时(`name={name}`),可以简写为 `{name}`。 ```svelte ``` ## 组件属性 按照惯例,传递给组件的值被称为*属性(properties)*或*props*,而不是*特性(attributes)*,后者是 DOM 的一个特征。 > 译者注:Property 和 Attributes 通常都翻译为“属性”,在做区分时,通常会将 Attributes 翻译为“特性”。Attributes 指的是直接在 HTML 元素上设置的值,通过提供元素的附加信息来指导其初始行为和状态。 与元素一样,`name={name}` 可以简写为 `{name}`。 ```svelte ``` *展开属性*允许一次性将多个特性或属性传递给元素或组件。 一个元素或组件可以有多个展开属性,并与常规属性交错使用。 ```svelte ``` ## 事件 通过在元素上添加以 `on` 开头的属性,可以监听 DOM 事件。例如,要监听 `click` 事件,在按钮上添加 `onclick` 属性: ```svelte ``` 事件属性区分大小写。`onclick` 监听 `click` 事件,`onClick` 监听 `Click` 事件,这是不同的。这确保你可以监听包含大写字符的自定义事件。 因为事件只是属性,所以适用与属性相同的规则: - 你可以使用简写形式:`` - 你可以展开它们:`` 在时序上,事件属性总是在绑定事件之后触发(例如,`oninput` 总是在 `bind:value` 更新后触发)。在底层,一些事件处理程序是直接通过 `addEventListener` 附加的,而其他的则是*委托*的。 当使用 `ontouchstart` 和 `ontouchmove` 事件属性时,处理程序是[passive](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#using_passive_listeners)可以获得更好的性能。这极大地提高了响应性,因为浏览器可以立即滚动文档,而不是等待查看事件处理程序是否调用 `event.preventDefault()`。 在极少数情况下,如果你需要阻止这些事件的默认行为,你应该使用 [`on`](svelte-events#on)(例如在 action 内部)。 ### 事件委托 为了减少内存占用并提高性能,Svelte 使用了一种称为事件委托的技术。这意味着对于某些事件(见下面的列表),在应用程序根部的单个事件监听器负责运行事件路径上的所有处理程序。 需要注意以下几个陷阱: - 当你手动触发一个带有委托监听器的事件时,确保设置 `{ bubbles: true }` 选项,否则它将无法到达应用程序根部 - 当直接使用 `addEventListener` 时,避免调用 `stopPropagation`,否则事件将无法到达应用程序根部,处理程序将不会被调用。同样,在应用程序根部手动添加的处理程序将在 DOM 深处声明式添加的处理程序(例如用 `onclick={...}`)之前运行,无论是在捕获还是冒泡阶段。出于这些原因,最好使用从 `svelte/events` 导入的 `on` 函数,而不是 `addEventListener`,因为它将确保顺序得到保持,并且正确处理 `stopPropagation`。 以下事件处理程序是委托的: - `beforeinput` - `click` - `change` - `dblclick` - `contextmenu` - `focusin` - `focusout` - `input` - `keydown` - `keyup` - `mousedown` - `mousemove` - `mouseout` - `mouseover` - `mouseup` - `pointerdown` - `pointermove` - `pointerout` - `pointerover` - `pointerup` - `touchend` - `touchmove` - `touchstart` ## 文本表达式 可以通过将 JavaScript 表达式用大括号括起来将其作为文本包含。 ```svelte {expression} ``` 可以通过使用它们的 [HTML 实体](https://developer.mozilla.org/docs/Glossary/Entity)字符串在 Svelte 模板中包含大括号:`{`、`{` 或 `{` 表示 `{`,`}`、`}` 或 `}` 表示 `}`。 如果你使用正则表达式(`RegExp`)[字面量表示法](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#literal_notation_and_constructor),你需要用括号将其括起来。 ```svelte

Hello {name}!

{a} + {b} = {a + b}.

{(/^[A-Za-z ]+$/).test(value) ? x : y}
``` 表达式将被字符串化并转义以防止代码注入。如果你想渲染 HTML,请使用 `{@html}` 标签。 ```svelte {@html potentiallyUnsafeHtmlString} ``` > [!NOTE] 确保你要么对传入的字符串进行转义,要么只使用你控制下的值来填充它,以防止 [XSS 攻击](https://owasp.org/www-community/attacks/xss/) ## 注释 你可以在组件内使用 HTML 注释。 ```svelte

Hello world

``` 以 `svelte-ignore` 开头的注释会禁用下一个标记块的警告。通常,这些是可访问性警告;确保你有充分的理由禁用它们。 ```svelte ``` 你可以添加一个以 `@component` 开头的特殊注释,当在其他文件中悬停在组件名称上时会显示该注释。 ````svelte

Hello, {name}

```` # {#if ...} ```svelte {#if expression}...{/if} ``` ```svelte {#if expression}...{:else if expression}...{/if} ``` ```svelte {#if expression}...{:else}...{/if} ``` 需要条件渲染的内容可以包装在 if 块中。 ```svelte {#if answer === 42}

问题是什么?

{/if} ``` 可以使用 `{:else if expression}` 添加额外的条件,最后可以选择性地以 `{:else}` 子句结束。 ```svelte {#if porridge.temperature > 100}

太热了!

{:else if 80 > porridge.temperature}

太冷了!

{:else}

刚刚好!

{/if} ``` (块不必只包裹元素,它们也可以包裹元素内的文本。) # {#each ...} ```svelte {#each expression as name}...{/each} ``` ```svelte {#each expression as name, index}...{/each} ``` 遍历值可以通过 each 块来完成。这些值可以是数组、类数组对象(即任何具有 `length` 属性的对象),或者可迭代对象如 `Map` 和 `Set` —— 换句话说,任何可以用 `Array.from` 处理的对象都可以。 ```svelte

购物清单

    {#each items as item}
  • {item.name} x {item.qty}
  • {/each}
``` each 块还可以指定一个 _index_,相当于 `array.map(...)` 回调中的第二个参数: ```svelte {#each items as item, i}
  • {i + 1}: {item.name} x {item.qty}
  • {/each} ``` ## 带键的 each 块 ```svelte {#each expression as name (key)}...{/each} ``` ```svelte {#each expression as name, index (key)}...{/each} ``` 如果提供了一个 _key_ 表达式(必须能唯一标识每个列表项),当数据发生变化时,Svelte 将使用它对列表进行差异比较,而不是在末尾添加或删除条目。key 可以是任何对象,但建议使用字符串和数字,因为它们允许在对象本身发生变化时保持标识继续存在。 ```svelte {#each items as item (item.id)}
  • {item.name} x {item.qty}
  • {/each} {#each items as item, i (item.id)}
  • {i + 1}: {item.name} x {item.qty}
  • {/each} ``` 你可以在 each 块中自由使用解构和剩余模式。 ```svelte {#each items as { id, name, qty }, i (id)}
  • {i + 1}: {name} x {qty}
  • {/each} {#each objects as { id, ...rest }}
  • {id}
  • {/each} {#each items as [id, ...rest]}
  • {id}
  • {/each} ``` ## 不带条目的 each 块 ```svelte {#each expression}...{/each} ``` ```svelte {#each expression, index}...{/each} ``` 如果你只是想渲染某些内容 `n` 次,可以省略 `as` 部分([demo](/playground/untitled#H4sIAAAAAAAAE3WR0W7CMAxFf8XKNAk0WsSeUEaRpn3Guoc0MbQiJFHiMlDVf18SOrZJ48259_jaVgZmxBEZZ28thgCNFV6xBdt1GgPj7wOji0t2EqI-wa_OleGEmpLWiID_6dIaQkMxhm1UdwKpRQhVzWSaVORJNdvWpqbhAYVsYQCNZk8thzWMC_DCHMZk3wPSThNQ088I3mghD9UwSwHwlLE5PMIzVFUFq3G7WUZ2OyUvU3JOuZU332wCXTRmtPy1NgzXZtUFp8WFw9536uWqpbIgPEaDsJBW90cTOHh0KGi2XsBq5-cT6-3nPauxXqHnsHJnCFZ3CvJVkyuCQ0mFF9TZyCQ162WGvteLKfG197Y3iv_pz_fmS68Hxt8iPBPj5HscP8YvCNX7uhYCAAA=)): ```svelte
    {#each { length: 8 }, rank} {#each { length: 8 }, file}
    {/each} {/each}
    ``` ## Else 块 ```svelte {#each expression as name}...{:else}...{/each} ``` each 块还可以有一个 `{:else}` 子句,当列表为空时会渲染该子句。 ```svelte {#each todos as todo}

    {todo.text}

    {:else}

    今天没有任务!

    {/each} ``` # {#key ...} ```svelte {#key expression}...{/key} ``` 当表达式的值发生变化时,Key 块会销毁并重新创建其内容。当用在组件时,这将导致组件被重新实例化和重新初始化: ```svelte {#key value} {/key} ``` 如果你想要在值发生变化时播放过渡效果,这也很有用: ```svelte {#key value}
    {value}
    {/key} ``` # {#await ...} ```svelte {#await expression}...{:then name}...{:catch name}...{/await} ``` ```svelte {#await expression}...{:then name}...{/await} ``` ```svelte {#await expression then name}...{/await} ``` ```svelte {#await expression catch name}...{/await} ``` Await 块允许你根据 [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) 的三种可能状态 — 等待中(pending)、已完成(fulfilled)或已拒绝(rejected) — 进行分支处理。 ```svelte {#await promise}

    等待 promise 解决中...

    {:then value}

    值是 {value}

    {:catch error}

    出现错误: {error.message}

    {/await} ``` > [!NOTE] 在服务端渲染期间,只会渲染等待中分支。 > > 如果提供的表达式不是一个 `Promise`,则只会渲染 `:then` 分支,包括在服务端渲染期间。 如果你不需要在 promise 被拒绝时渲染任何内容(或不可能出现错误),可以省略 `catch` 块。 ```svelte {#await promise}

    等待 promise 解决中...

    {:then value}

    值是 {value}

    {/await} ``` 如果你不关心等待状态,你也可以省略初始块。 ```svelte {#await promise then value}

    值是 {value}

    {/await} ``` 同样,如果你只想显示错误状态,你可以省略 `then` 块。 ```svelte {#await promise catch error}

    错误是 {error}

    {/await} ``` > [!NOTE] 你可以将 `#await` 和 [`import(...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) 一起使用实现组件懒加载: > > ```svelte > {#await import('./Component.svelte') then { default: Component }} > > {/await} > ``` # {#snippet ...} ```svelte {#snippet name()}...{/snippet} ``` ```svelte {#snippet name(param1, param2, paramN)}...{/snippet} ``` 代码片段和 [渲染标签](@render) 是在组件内部创建可复用标记块的一种方法。与其编写[这样的](/playground/untitled#H4sIAAAAAAAAE5VUYW-kIBD9K8Tmsm2yXXRzvQ-s3eR-R-0HqqOQKhAZb9sz_vdDkV1t000vRmHewMx7w2AflbIGG7GnPlK8gYhFv42JthG-m9Gwf6BGcLbVXZuPSGrzVho8ZirDGpDIhldgySN5GpEMez9kaNuckY1ANJZRamRuu2ZnhEZt6a84pvs43mzD4pMsUDDi8DMkQFYCGdkvsJwblFq5uCik9bmJ4JZwUkv1eoknWigX2eGNN6aGXa6bjV8ybP-X7sM36T58SVcrIIV2xVIaA41xeD5kKqWXuqpUJEefOqVuOkL9DfBchGrzWfu0vb-RpTd3o-zBR045Ga3HfuE5BmJpKauuhbPtENlUF2sqR9jqpsPSxWsMrlngyj3VJiyYjJXb1-lMa7IWC-iSk2M5Zzh-SJjShe-siq5kpZRPs55BbSGU5YPyte4vVV_VfFXxVb10dSLf17pS2lM5HnpPxw4Zpv6x-F57p0jI3OKlVnhv5V9wPQrNYQQ9D_f6aGHlC89fq1Z3qmDkJCTCweOGF4VUFSPJvD_DhreVdA0eu8ehJJ5x91dBaBkpWm3ureCFPt3uzRv56d4kdp-2euG38XZ6dsnd3ZmPG9yRBCrzRUvi-MccOdwz3qE-fOZ7AwAhlrtTUx3c76vRhSwlFBHDtoPhefgHX3dM0PkEAAA=)重复代码... ```svelte {#each images as image} {#if image.href}
    {image.caption}
    {image.caption}
    {:else}
    {image.caption}
    {image.caption}
    {/if} {/each} ``` ...你可以[这样写](/playground/untitled#H4sIAAAAAAAAE5VUYW-bMBD9KxbRlERKY4jWfSA02n5H6QcXDmwVbMs-lnaI_z6D7TTt1moTAnPvzvfenQ_GpBEd2CS_HxPJekjy5IfWyS7BFz0b9id0CM62ajDVjBS2MkLjqZQldoBE9KwFS-7I_YyUOPqlRGuqnKw5orY5pVpUduj3mitUln5LU3pI0_UuBp9FjTwnDr9AHETLMSeHK6xiGoWSLi9yYT034cwSRjohn17zcQPNFTs8s153sK9Uv_Yh0-5_5d7-o9zbD-UqCaRWrllSYZQxLw_HUhb0ta-y4NnJUxfUvc7QuLJSaO0a3oh2MLBZat8u-wsPnXzKQvTtVVF34xK5d69ThFmHEQ4SpzeVRediTG8rjD5vBSeN3E5JyHh6R1DQK9-iml5kjzQUN_lSgVU8DhYLx7wwjSvRkMDvTjiwF4zM1kXZ7DlF1eN3A7IG85e-zRrYEjjm0FkI4Cc7Ripm0pHOChexhcWXzreeZyRMU6Mk3ljxC9w4QH-cQZ_b3T5pjHxk1VNr1CDrnJy5QDh6XLO6FrLNSRb2l9gz0wo3S6m7HErSgLsPGMHkpDZK31jOanXeHPQz-eruLHUP0z6yTbpbrn223V70uMXNSpQSZjpL0y8hcxxpNqA6_ql3BQAxlxvfpQ_uT9GrWjQC6iRHM8D0MP0GQsIi92QEAAA=): ```svelte {#snippet figure(image)}
    {image.caption}
    {image.caption}
    {/snippet} {#each images as image} {#if image.href} {@render figure(image)} {:else} {@render figure(image)} {/if} {/each} ``` 像函数声明一样,代码片段可以有任意数量的参数,这些参数可以有默认值,并且你可以对每个参数进行解构。然而,你不能使用剩余参数。 ## 代码片段作用域 代码片段可以在组件的任何地方声明。它们可以引用在自身之外声明的值,例如在 ` {#snippet hello(name)}

    你好 {name}! {message}!

    {/snippet} {@render hello('alice')} {@render hello('bob')} ``` ...并且它们对同一词法作用域中的所有内容都是"可见的"(即兄弟节点和这些兄弟节点的子节点): ```svelte
    {#snippet x()} {#snippet y()}...{/snippet} {@render y()} {/snippet} {@render y()}
    {@render x()} ``` 代码片段可以引用自身和其他片段 ([demo](/playground/untitled#H4sIAAAAAAAAE2WPTQqDMBCFrxLiRqH1Zysi7TlqF1YnENBJSGJLCYGeo5tesUeosfYH3c2bee_jjaWMd6BpfrAU6x5oTvdS0g01V-mFPkNnYNRaDKrxGxto5FKCIaeu1kYwFkauwsoUWtZYPh_3W5FMY4U2mb3egL9kIwY0rbhgiO-sDTgjSEqSTvIDs-jiOP7i_MHuFGAL6p9BtiSbOTl0GtzCuihqE87cqtyam6WRGz_vRcsZh5bmRg3gju4Fptq_kzQBAAA=)): ```svelte {#snippet blastoff()} 🚀 {/snippet} {#snippet countdown(n)} {#if n > 0} {n}... {@render countdown(n - 1)} {:else} {@render blastoff()} {/if} {/snippet} {@render countdown(10)} ``` ## 将代码片段传递给组件 在模板中,代码片段和其他值一样。这样,它们可以作为 props 传递给组件 ([demo](/playground/untitled#H4sIAAAAAAAAE3VS247aMBD9lZGpBGwDASRegonaPvQL2qdlH5zYEKvBNvbQLbL875VzAcKyj3PmzJnLGU8UOwqSkd8KJdaCk4TsZS0cyV49wYuJuQiQpGd-N2bu_ooaI1YwJ57hpVYoFDqSEepKKw3mO7VDeTTaIvxiRS1gb_URxvO0ibrS8WanIrHUyiHs7Vmigy28RmyHHmKvDMbMmFq4cQInvGSwTsBYWYoMVhCSB2rBFFPsyl0uruTlR3JZCWvlTXl1Yy_mawiR_rbZKZrellJ-5JQ0RiBUgnFhJ9OGR7HKmwVoilXeIye8DOJGfYCgRlZ3iE876TBsZPX7hPdteO75PC4QaIo8vwNPePmANQ2fMeEFHrLD7rR1jTNkW986E8C3KwfwVr8HSHOSEBT_kGRozyIkn_zQveXDL3rIfPJHtUDwzShJd_Qk3gQCbOGLsdq4yfTRJopRuin3I7nv6kL7ARRjmLdBDG3uv1mhuLA3V2mKtqNEf_oCn8p9aN-WYqH5peP4kWBl1UwJzAEPT9U7K--0fRrrWnPTXpCm1_EVdXjpNmlA8G1hPPyM1fKgMqjFHjctXGjLhZ05w0qpDhksGrybuNEHtJnCalZWsuaTlfq6nPaaBSv_HKw-K57BjzOiVj9ZKQYKzQjZodYFqydYTRN4gPhVzTDO2xnma3HsVWjaLjT8nbfwHy7Q5f2dBAAA)): ```svelte {#snippet header()} 水果 数量 价格 总计 {/snippet} {#snippet row(d)} {d.name} {d.qty} {d.price} {d.qty * d.price} {/snippet} ``` 把它想象成向组件传递内容而非数据。这个概念类似于 Web 组件中的插槽。 为了方便,直接在组件*内部*声明的代码片段会隐式成为组件的 props ([demo](/playground/untitled#H4sIAAAAAAAAE3VSTa_aMBD8Kyu_SkAbCA-JSzBR20N_QXt6vIMTO8SqsY29tI2s_PcqTiB8vaPHs7MzuxuIZgdBMvJLo0QlOElIJZXwJHsLBBvb_XUASc7Mb9Yu_B-hsMMK5sUzvDQahUZPMkJ96aTFfKd3KA_WOISfrFACKmcOMFmk8TWUTjY73RFLoz1C5U4SPWzhrcN2GKDrlcGEWauEnyRwxCaDdQLWyVJksII2uaMWTDPNLtzX5YX8-kgua-GcHJVXI3u5WEPb0d83O03TMZSmfRzOkG1Db7mNacOL19JagVALxoWbztq-H8U6j0SaYp2P2BGbOyQ2v8PQIFMXLKRDk177pq0zf6d8bMrzwBdd0pamyPMb-IjNEzS2f86Gz_Dwf-2F9nvNSUJQ_EOSoTuJNvngqK5v4Pas7n4-OCwlEEJcQTIMO-nSQwtb-GSdsX46e9gbRoP9yGQ11I0rEuycunu6PHx1QnPhxm3SFN15MOlYEFJZtf0dUywMbwZOeBGsrKNLYB54-1R9WNqVdki7usim6VmQphf7mnpshiQRhNAXdoOfMyX3OgMlKtz0cGEcF27uLSul3mewjPjgOOoDukxjPS9rqfh0pb-8zs6aBSt_7505aZ7B9xOi0T9YKW4UooVsr0zB1BTrWQJ3EL-oWcZ572GxFoezCk37QLe3897-B2i2U62uBAAA)): ```svelte
    {#snippet header()} {/snippet} {#snippet row(d)} {/snippet}
    水果 数量 价格 总计{d.name} {d.qty} {d.price} {d.qty * d.price}
    ``` 组件标签内的任何*不是*代码片段声明的内容都将隐式成为 `children` 代码片段的一部分 ([demo](/playground/untitled#H4sIAAAAAAAAE3WOQQrCMBBFrzIMggql3ddY1Du4si5sOmIwnYRkFKX07lKqglqX8_7_w2uRDw1hjlsWI5ZqTPBoLEXMdy3K3fdZDzB5Ndfep_FKVnpWHSKNce1YiCVijirqYLwUJQOYxrsgsLmIOIZjcA1M02w4n-PpomSVvTclqyEutDX6DA2pZ7_ABIVugrmEC3XJH92P55_G39GodCmWBFrQJ2PrQAwdLGHig_NxNv9xrQa1dhWIawrv1Wzeqawa8953D-8QOmaEAQAA)): ```svelte ``` ```svelte ``` > [!NOTE] 请注意,如果组件内部还有内容,你不能有名为 `children` 的 prop — 基于这个原因,应该避免使用这个名称作为 prop 你可以将代码片段 props 声明为可选的。你可以使用可选链,当代码片段未设置时不渲染任何内容... ```svelte {@render children?.()} ``` ...或者使用 `#if` 块来渲染后备内容: ```svelte {#if children} {@render children()} {:else} 后备内容 {/if} ``` ## 代码片段类型 代码片段实现了从 `'svelte'` 导入的 `Snippet` 接口: ```svelte ``` 通过这项改动,如果你尝试在没有提供 `data` prop 和 `row` 代码片段的情况下使用该组件,则会出现红色波浪线。请注意,提供给 `Snippet` 的类型参数是一个[元组](https://ts.yayujs.com/handbook/ObjectTypes.html#%E5%85%83%E7%BB%84%E7%B1%BB%E5%9E%8B-tuple-types),因为代码片段可以有多个参数。 我们可以通过声明泛型来进一步收窄类型,以便 `data` 和 `row` 引用相同的类型: ```svelte ``` ## 导出代码片段 在 `.svelte` 文件顶层声明的代码片段可以从 ` {#snippet add(a, b)} {a} + {b} = {a + b} {/snippet} ``` > [!NOTE] > 这需要 Svelte 5.5.0 或更新版本 ## 程序化代码片段 代码片段可以通过 [`createRawSnippet`](svelte#createRawSnippet) API 以编程方式创建。这适用于高级用例。 ## 代码片段和插槽 在 Svelte 4 中,可以使用 [插槽](legacy-slots) 将内容传递给组件。代码片段更强大、更灵活,因此在 Svelte 5 中插槽已被弃用。 # {@render ...} 要渲染一个[代码片段](snippet),请使用 `{@render ...}` 标签。 ```svelte {#snippet sum(a, b)}

    {a} + {b} = {a + b}

    {/snippet} {@render sum(1, 2)} {@render sum(3, 4)} {@render sum(5, 6)} ``` 表达式可以是像 `sum` 这样的标识符,也可以是任意的 JavaScript 表达式: ```svelte {@render (cool ? coolSnippet : lameSnippet)()} ``` ## 可选代码片段 如果代码片段可能未定义 — 例如,因为它是一个传入的 prop — 那么你可以使用可选链操作符,只在代码片段确实存在时才渲染它: ```svelte {@render children?.()} ``` 或者,使用[`{#if ...}`](if) 块配合 `:else` 子句来渲染后备内容: ```svelte {#if children} {@render children()} {:else}

    后备内容

    {/if} ``` # {@html ...} 要将原始 HTML 注入到组件中,请使用 `{@html ...}` 标签: ```svelte
    {@html content}
    ``` > [!NOTE] 请确保您要么转义传入的字符串,要么仅使用在您控制下的值来填充它,以防止 [XSS 攻击](https://owasp.org/www-community/attacks/xss/)。切勿渲染未经安全处理的内容。 表达式应该是有效的独立 HTML —— 以下示例将不起作用,因为 `
    ` 不是有效的 HTML: ```svelte {@html '
    '}content{@html '
    '} ``` 它也不会编译 Svelte 代码。 ## 样式 以这种方式渲染的内容对 Svelte 来说是"不可见的",因此不会接收[作用域样式](scoped-styles) —— 换句话说,以下代码不会生效,`a` 和 `img` 样式将被视为未使用: ```svelte
    {@html content}
    ``` 相反,使用 `:global` 修饰符来定位 `
    ` 内的所有内容: ```svelte ``` # {@const ...} `{@const ...}` 标签用于定义一个局部常量。 ```svelte {#each boxes as box} {@const area = box.width * box.height} {box.width} * {box.height} = {area} {/each} ``` `{@const}` 只允许作为块(block)的直接子元素 — `{#if ...}`、`{#each ...}`、`{#snippet ...}` 等 — 或者 ``。 # {@debug ...} `{@debug ...}` 标签提供了一个替代 `console.log(...)` 的方案。它会在特定变量发生变化时记录这些变量的值,并且如果你打开了开发者工具,它会暂停代码执行。 ```svelte {@debug user}

    Hello {user.firstname}!

    ``` `{@debug ...}` 接受一个以逗号分隔的变量名列表(不接受任意表达式)。 ```svelte {@debug user} {@debug user1, user2, user3} {@debug user.firstname} {@debug myArray[0]} {@debug !isReady} {@debug typeof user === 'object'} ``` 不带任何参数的 `{@debug}` 标签会插入一个 `debugger` 语句,该语句会在任何状态发生变化时触发,而不是仅在指定的变量发生变化时触发。 # bind: 数据通常是从父级流向子级。`bind:` 指令允许数据反向流动,从子级流向父级。 一般语法是 `bind:property={expression}`,其中 `expression` 是一个 _lvalue_(即一个变量或一个对象属性)。当表达式的标识符与属性同名时,我们可以省略表达式——换句话说,这两者是等价的: ```svelte ``` Svelte 创建了一个事件监听器来更新绑定值。如果元素已经有一个相同事件的监听器,那个监听器会在绑定值更新之前触发。 大多数绑定是 _双向的_,这意味着对值的更改会影响元素,反之亦然。有一些绑定是 _只读的_,这意味着更改它们的值不会对元素产生影响。 ## 函数绑定 您还可以使用 `bind:property={get, set}`,其中 `get` 和 `set` 是函数,允许您进行验证和转换: ```svelte value, (v) => value = v.toLowerCase()} /> ``` 对于只读绑定如[尺寸绑定](#Dimensions),`get` 值应为 `null`: ```svelte
    ...
    ``` > [!NOTE] > 函数绑定在 Svelte 5.9.0 及更新版本中可用。 ## `` 在 `` 元素上使用 `bind:value` 指令绑定输入的 `value` 属性: ```svelte

    {message}

    ``` 对于数字输入(`type="number"` 或 `type="range"`),该值将被强制转换为数字([demo](/playground/untitled#H4sIAAAAAAAAE6WPwYoCMQxAfyWEPeyiOOqx2w74Hds9pBql0IllmhGXYf5dKqwiyILsLXnwwsuI-5i4oPkaUX8yo7kCnKNQV7dNzoty4qSVBSr8jG-Poixa0KAt2z5mbb14TaxA4OCtKCm_rz4-f2m403WltrlrYhMFTtcLNkoeFGqZ8yhDF7j3CCHKzpwoDexGmqCL4jwuPUJHZ-dxVcfmyYGe5MAv-La5pbxYFf5Z9Zf_UJXb-sEMquFgJJhBmGyTW5yj8lnRaD_w9D1dAKSSj7zqAQAA)): ```svelte

    {a} + {b} = {a + b}

    ``` 如果输入为空或无效(在 `type="number"` 的情况下),值为 `undefined`。 自 5.6.0 版本开始,如果 `` 有 `defaultValue` 并且是表单的一部分,当表单被重置时,它将恢复为该值,而不是空字符串。请注意,对于初始渲染,除非绑定值为 `null` 或 `undefined`,否则绑定值优先。 ```svelte
    ``` > [!NOTE] > 请谨慎使用重置按钮,并确保用户在提交表单时不会意外点击它们。 ## `` 复选框和单选输入可以绑定 `bind:checked`: ```svelte ``` 自 5.6.0 版本开始,如果 `` 有 `defaultChecked` 属性并且是表单的一部分,当表单被重置时,它将恢复为该值,而不是 `false`。请注意,对于初始渲染,除非绑定值为 `null` 或 `undefined`,否则绑定值优先。 ```svelte
    ``` ## `` 一起工作的 input 元素,比如单选框、复选框,可以使用 `bind:group`。 ```svelte ``` > [!NOTE] `bind:group` 仅在 input 位于同一 Svelte 组件中时才有效。 ## `` 在 `` 元素上使用 `type="file"` 时,您可以使用 `bind:files` 获取所选文件的 [`FileList`](https://developer.mozilla.org/en-US/docs/Web/API/FileList)。当您想以编程方式更新文件时,您始终需要使用 `FileList` 对象。目前无法直接构造 `FileList` 对象,因此您需要创建一个新的 [`DataTransfer`](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer) 对象,并从中获取 `files`。 ```svelte ``` `FileList` 对象也不能被修改,因此如果您想从列表中删除单个文件,则需要创建一个新的 `DataTransfer` 对象,并添加您想保留的文件。 > [!NOTE] `DataTransfer` 在服务器端 JavaScript 运行时可能不可用。将绑定到 `files` 的状态保持未初始化可以防止组件在服务端渲染时出现潜在错误。 ## `` 值绑定对应于所选 ` ``` 当 ` ``` ## `