组件是 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>

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

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) 下面将导致 yDependent 在 x 更新时更新。

如果一个语句完全由一个未声明变量的分配组成,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>