组件是 Svelte 应用程序的构建块。它们写入 .svelte 文件,使用 HTML 的超集。

所有三个部分 —— 脚本、样式和标记 —— 都是可选的。

  1. <script>
  2. // 逻辑放在这里
  3. </script>
  4. <!-- 标记(零个或多个项目)放在这里 -->
  5. <style>
  6. /* 样式放在这里 */
  7. </style>

<script>

<script> 块包含在创建组件实例时运行的 JavaScript。在顶层声明(或导入)的变量可以从组件的标记中 “看到”。这里还有四个额外的规则:

1. export 创建一个组件属性

Svelte 使用 export 关键字标记一个变量声明为 属性prop,这意味着它对组件的消费者变得可访问(有关更多信息,请参阅 属性和属性 部分)。

  1. <script>
  2. export let foo;
  3. // 作为属性传入的值
  4. // 立即可用
  5. console.log({ foo });
  6. </script>

你可以为属性指定一个默认初始值。如果组件的消费者在实例化组件时没有指定属性(或者其初始值为 undefined),则将使用它。请注意,如果属性的值随后更新,则任何未指定值的属性将被设置为 undefined(而不是其初始值)。

在开发模式下(见 编译器选项),如果未提供默认初始值且消费者没有指定值,将打印警告。要消除此警告,请确保指定了默认初始值,即使它是 undefined

  1. <script>
  2. export let bar = '可选的默认初始值';
  3. export let baz = undefined;
  4. </script>

如果你导出一个 constclassfunction,它从组件外部是只读的。然而,如下所示,函数是有效的属性值。

App.svelte

  1. <script>
  2. // 这些都是只读的
  3. export const thisIs = 'readonly';
  4. /** @param {string} name */
  5. export function greet(name) {
  6. alert(`hello ${name}!`);
  7. }
  8. // 这是一个属性
  9. export let format = (n) => n.toFixed(2);
  10. </script>

App.svelte

  1. <script lang="ts">
  2. // 这些都是只读的
  3. export const thisIs = 'readonly';
  4. export function greet(name: string) {
  5. alert(`hello ${name}!`);
  6. }
  7. // 这是一个属性
  8. export let format = (n) => n.toFixed(2);
  9. </script>

只读属性可以作为属性在元素上访问,使用 bind:this 语法 绑定到组件。

你可以使用保留字作为属性名称。

App.svelte

  1. <script>
  2. /** @type {string} */
  3. let className;
  4. // 即使它是保留字,也创建了一个 `class` 属性
  5. export { className as class };
  6. </script>

App.svelte

  1. <script lang="ts">
  2. let className: string;
  3. // 即使它是保留字,也创建了一个 `class` 属性
  4. export { className as class };
  5. </script>

2. 变量赋值是 ‘响应式的’

要改变组件状态并触发重新渲染,只需分配给一个本地声明的变量。

更新表达式(count += 1)和属性分配(obj.x = y)具有相同的效果。

  1. <script>
  2. let count = 0;
  3. function handleClick() {
  4. // 调用此函数将在标记引用 `count` 时触发更新
  5. count = count + 1;
  6. }
  7. </script>

因为 Svelte 的反应性基于赋值,使用数组方法如 .push().splice() 不会自动触发更新。需要后续分配来触发更新。这和其他更多细节也可以在 教程 中找到。

  1. <script>
  2. let arr = [0, 1];
  3. function handleClick() {
  4. // 此方法调用不会触发更新
  5. arr.push(2);
  6. // 如果标记引用 `arr`,这个分配将触发更新
  7. arr = arr;
  8. }
  9. </script>

Svelte 的 <script> 块仅在创建组件时运行,因此 <script> 块内的分配不会在属性更新时自动再次运行。如果你想跟踪属性的变化,请见下一节的下一个示例。

  1. <script>
  2. export let person;
  3. // 这只在组件创建时设置 `name`
  4. // 当 `person` 更新时它不会更新
  5. let { name } = person;
  6. </script>

3. $: 将语句标记为响应式的

任何顶级语句(即不在块或函数内部)都可以通过在其前缀 $: JS 标签语法 来使其成为响应式的。反应性语句在其他脚本代码之后和组件标记渲染之前运行,每当它们依赖的值发生变化时。

  1. <script>
  2. export let title;
  3. export let person;
  4. // 这将在 `title` 属性更改时更新 `document.title`
  5. $: document.title = title;
  6. $: {
  7. console.log(`可以将多个语句组合起来`);
  8. console.log(`当前标题是 ${title}`);
  9. }
  10. // 当 'person' 更改时,这将更新 `name`
  11. $: ({ name } = person);
  12. // 不要这样做。它将在前一行之前运行
  13. let name2 = name;
  14. </script>

只有直接出现在 $: 块内的值才会成为反应性语句的依赖项。例如,在下面的代码中 total 只有在 x 更改时才会更新,但不是 y

App.svelte

  1. <script>
  2. let x = 0;
  3. let y = 0;
  4. /** @param {number} value */
  5. function yPlusAValue(value) {
  6. return value + y;
  7. }
  8. $: total = yPlusAValue(x);
  9. </script>
  10. Total: {total}
  11. <button on:click={() => x++}> 增加 X </button>
  12. <button on:click={() => y++}> 增加 Y </button>

App.svelte

  1. <script lang="ts">
  2. let x = 0;
  3. let y = 0;
  4. function yPlusAValue(value: number) {
  5. return value + y;
  6. }
  7. $: total = yPlusAValue(x);
  8. </script>
  9. Total: {total}
  10. <button on:click={() => x++}> 增加 X </button>
  11. <button on:click={() => y++}> 增加 Y </button>

重要的是要注意,反应性块通过编译时的简单静态分析进行排序,并且编译器查看的只是块内分配和使用的变量,而不是它们调用的任何函数中的变量。这意味着在以下示例中,当 x 更新时 yDependent 不会更新:

App.svelte

  1. <script>
  2. let x = 0;
  3. let y = 0;
  4. /** @param {number} value */
  5. function setY(value) {
  6. y = value;
  7. }
  8. $: yDependent = y;
  9. $: setY(x);
  10. </script>

App.svelte

  1. <script lang="ts">
  2. let x = 0;
  3. let y = 0;
  4. function setY(value: number) {
  5. y = value;
  6. }
  7. $: yDependent = y;
  8. $: setY(x);
  9. </script>

$: yDependent = y 移动到 $: setY(x) 下面将导致 yDependentx 更新时更新。

如果一个语句完全由一个未声明变量的分配组成,Svelte 将代表你注入一个 let 声明。

App.svelte

  1. <script>
  2. /** @type {number} */
  3. export let num;
  4. // 我们不需要声明 `squared` 和 `cubed`
  5. // — Svelte 为我们做了
  6. $: squared = num * num;
  7. $: cubed = squared * num;
  8. </script>

App.svelte

  1. <script lang="ts">
  2. export let num: number;
  3. // 我们不需要声明 `squared` 和 `cubed`
  4. // — Svelte 为我们做了
  5. $: squared = num * num;
  6. $: cubed = squared * num;
  7. </script>

4. 使用 $ 前缀存储来访问它们的值

一个 存储 是一个允许通过一个简单的 存储合同 反应性访问值的对象。svelte/store 模块 包含实现此合同的最小存储实现。

任何时候你有一个存储的引用,你可以通过在其前缀加上 $ 字符 来在组件内访问它的值。这导致 Svelte 声明前缀变量,在组件初始化时订阅存储,并在适当的时候取消订阅。

$ 前缀变量的分配要求变量是可写的存储,并将导致调用存储的 .set 方法。

注意,存储必须在组件的顶层声明 —— 例如,不在 if 块或函数内部。

不代表存储值的局部变量必须 $ 前缀。

  1. <script>
  2. import { writable } from 'svelte/store';
  3. const count = writable(0);
  4. console.log($count); // 日志 0
  5. count.set(1);
  6. console.log($count); // 日志 1
  7. $count = 2;
  8. console.log($count); // 日志 2
  9. </script>

store 契约

  1. store = { subscribe: (subscription: (value: any) => void) => (() => void), set?: (value: any) => void }

你可以通过实现 存储合同 来创建自己的存储,而不依赖于 svelte/store

  1. 存储必须包含一个 .subscribe 方法,它必须接受一个订阅函数作为其参数。此订阅函数必须在调用 .subscribe 时立即并同步地使用存储的当前值调用。每当存储的值更改时,所有存储的活跃订阅函数都必须稍后同步调用。
  2. .subscribe 方法必须返回一个取消订阅函数。调用取消订阅函数必须停止其订阅,其对应的订阅函数不得再次被存储调用。
  3. 存储可能 选择性 包含一个 .set 方法,它必须接受存储的新值作为其参数,并且同步调用所有存储的活跃订阅函数。这样的存储称为 可写存储

为了与 RxJS Observables 互操作,.subscribe 方法也允许返回一个带有 .unsubscribe 方法的对象,而不是直接返回取消订阅函数。但请注意,除非 .subscribe 同步调用订阅(这不符合 Observable 规范的要求),否则 Svelte 将看到存储的值为 undefined,直到它这样做。

<script context="module">

带有 context="module" 属性的 <script> 标签在模块首次评估时运行一次,而不是为每个组件实例。在该块中声明的值可以从常规 <script>(和组件标记)中访问,但不能反向访问。

你可以从这个块中 export 绑定,它们将成为编译模块的导出。

你不能 export default,因为默认导出是组件本身。

module 脚本中定义的变量不是响应式的 —— 即使变量本身会更新,重新分配它们也不会触发重新渲染。对于在多个组件之间共享的值,请考虑使用 存储

  1. <script context="module">
  2. let totalComponents = 0;
  3. // export 关键字允许此函数被导入,例如
  4. // `import Example, { alertTotal } from './Example.svelte'`
  5. export function alertTotal() {
  6. alert(totalComponents);
  7. }
  8. </script>
  9. <script>
  10. totalComponents += 1;
  11. console.log(`这个组件被创建的总次数:${totalComponents}`);
  12. </script>

<style>

<style> 块内的 CSS 将被限定在该组件范围内。

这是通过向受影响的元素添加一个类来实现的,该类基于组件样式的哈希(例如 svelte-123xyz)。

  1. <style>
  2. p {
  3. /* 这只会影响此组件中的 <p> 元素 */
  4. color: burlywood;
  5. }
  6. </style>

要全局应用样式,请使用 :global(...) 修饰符。

  1. <style>
  2. :global(body) {
  3. /* 这将应用于 <body> */
  4. margin: 0;
  5. }
  6. div :global(strong) {
  7. /* 这将应用于此组件中属于 <div> 元素内部的所有 <strong> 元素 */
  8. color: goldenrod;
  9. }
  10. p:global(.red) {
  11. /* 这将应用于此组件中所有具有 red 类的 <p> 元素,即使 class="red" 最初没有出现在标记中,而是在运行时添加的。当元素的类直接动态应用时,例如直接更新元素的 classList 属性时,这很有用。 */
  12. }
  13. </style>

如果你想制作全局可访问的 @keyframes,你需要将你的 keyframe 名称前缀为 -global-

编译时 -global- 部分将被移除,然后 keyframe 就可以在代码的其他部分使用 my-animation-name 引用。

  1. <style>
  2. @keyframes -global-my-animation-name {
  3. /* 代码放在这里 */
  4. }
  5. </style>

每个组件应该只有一个顶级 <style> 标签。

然而,可以在其他元素或逻辑块内嵌套 <style> 标签。

在这种情况下,<style> 标签将原样插入到 DOM 中,不会对 <style> 标签进行限定或处理。

  1. <div>
  2. <style>
  3. /* 这个样式标签将原样插入 */
  4. div {
  5. /* 这将应用于 DOM 中的所有 `<div>` 元素 */
  6. color: red;
  7. }
  8. </style>
  9. </div>