组件是 Svelte 应用程序的构建块。它们写入 .svelte
文件,使用 HTML 的超集。
所有三个部分 —— 脚本、样式和标记 —— 都是可选的。
<script>
// 逻辑放在这里
</script>
<!-- 标记(零个或多个项目)放在这里 -->
<style>
/* 样式放在这里 */
</style>
<script>
<script>
块包含在创建组件实例时运行的 JavaScript。在顶层声明(或导入)的变量可以从组件的标记中 “看到”。这里还有四个额外的规则:
1. export 创建一个组件属性
Svelte 使用 export
关键字标记一个变量声明为 属性 或 prop,这意味着它对组件的消费者变得可访问(有关更多信息,请参阅 属性和属性 部分)。
<script>
export let foo;
// 作为属性传入的值
// 立即可用
console.log({ foo });
</script>
你可以为属性指定一个默认初始值。如果组件的消费者在实例化组件时没有指定属性(或者其初始值为 undefined
),则将使用它。请注意,如果属性的值随后更新,则任何未指定值的属性将被设置为 undefined
(而不是其初始值)。
在开发模式下(见 编译器选项),如果未提供默认初始值且消费者没有指定值,将打印警告。要消除此警告,请确保指定了默认初始值,即使它是 undefined
。
<script>
export let bar = '可选的默认初始值';
export let baz = undefined;
</script>
如果你导出一个 const
、class
或 function
,它从组件外部是只读的。然而,如下所示,函数是有效的属性值。
App.svelte
<script>
// 这些都是只读的
export const thisIs = 'readonly';
/** @param {string} name */
export function greet(name) {
alert(`hello ${name}!`);
}
// 这是一个属性
export let format = (n) => n.toFixed(2);
</script>
App.svelte
<script lang="ts">
// 这些都是只读的
export const thisIs = 'readonly';
export function greet(name: string) {
alert(`hello ${name}!`);
}
// 这是一个属性
export let format = (n) => n.toFixed(2);
</script>
只读属性可以作为属性在元素上访问,使用 bind:this
语法 绑定到组件。
你可以使用保留字作为属性名称。
App.svelte
<script>
/** @type {string} */
let className;
// 即使它是保留字,也创建了一个 `class` 属性
export { className as class };
</script>
App.svelte
<script lang="ts">
let className: string;
// 即使它是保留字,也创建了一个 `class` 属性
export { className as class };
</script>
2. 变量赋值是 ‘响应式的’
要改变组件状态并触发重新渲染,只需分配给一个本地声明的变量。
更新表达式(count += 1
)和属性分配(obj.x = y
)具有相同的效果。
<script>
let count = 0;
function handleClick() {
// 调用此函数将在标记引用 `count` 时触发更新
count = count + 1;
}
</script>
因为 Svelte 的反应性基于赋值,使用数组方法如 .push()
和 .splice()
不会自动触发更新。需要后续分配来触发更新。这和其他更多细节也可以在 教程 中找到。
<script>
let arr = [0, 1];
function handleClick() {
// 此方法调用不会触发更新
arr.push(2);
// 如果标记引用 `arr`,这个分配将触发更新
arr = arr;
}
</script>
Svelte 的 <script>
块仅在创建组件时运行,因此 <script>
块内的分配不会在属性更新时自动再次运行。如果你想跟踪属性的变化,请见下一节的下一个示例。
<script>
export let person;
// 这只在组件创建时设置 `name`
// 当 `person` 更新时它不会更新
let { name } = person;
</script>
3. $: 将语句标记为响应式的
任何顶级语句(即不在块或函数内部)都可以通过在其前缀 $:
JS 标签语法 来使其成为响应式的。反应性语句在其他脚本代码之后和组件标记渲染之前运行,每当它们依赖的值发生变化时。
<script>
export let title;
export let person;
// 这将在 `title` 属性更改时更新 `document.title`
$: document.title = title;
$: {
console.log(`可以将多个语句组合起来`);
console.log(`当前标题是 ${title}`);
}
// 当 'person' 更改时,这将更新 `name`
$: ({ name } = person);
// 不要这样做。它将在前一行之前运行
let name2 = name;
</script>
只有直接出现在 $:
块内的值才会成为反应性语句的依赖项。例如,在下面的代码中 total
只有在 x
更改时才会更新,但不是 y
。
App.svelte
<script>
let x = 0;
let y = 0;
/** @param {number} value */
function yPlusAValue(value) {
return value + y;
}
$: total = yPlusAValue(x);
</script>
Total: {total}
<button on:click={() => x++}> 增加 X </button>
<button on:click={() => y++}> 增加 Y </button>
App.svelte
<script lang="ts">
let x = 0;
let y = 0;
function yPlusAValue(value: number) {
return value + y;
}
$: total = yPlusAValue(x);
</script>
Total: {total}
<button on:click={() => x++}> 增加 X </button>
<button on:click={() => y++}> 增加 Y </button>
重要的是要注意,反应性块通过编译时的简单静态分析进行排序,并且编译器查看的只是块内分配和使用的变量,而不是它们调用的任何函数中的变量。这意味着在以下示例中,当 x
更新时 yDependent
不会更新:
App.svelte
<script>
let x = 0;
let y = 0;
/** @param {number} value */
function setY(value) {
y = value;
}
$: yDependent = y;
$: setY(x);
</script>
App.svelte
<script lang="ts">
let x = 0;
let y = 0;
function setY(value: number) {
y = value;
}
$: yDependent = y;
$: setY(x);
</script>
将 $: yDependent = y
移动到 $: setY(x)
下面将导致 yDependent
在 x
更新时更新。
如果一个语句完全由一个未声明变量的分配组成,Svelte 将代表你注入一个 let
声明。
App.svelte
<script>
/** @type {number} */
export let num;
// 我们不需要声明 `squared` 和 `cubed`
// — Svelte 为我们做了
$: squared = num * num;
$: cubed = squared * num;
</script>
App.svelte
<script lang="ts">
export let num: number;
// 我们不需要声明 `squared` 和 `cubed`
// — Svelte 为我们做了
$: squared = num * num;
$: cubed = squared * num;
</script>
4. 使用 $ 前缀存储来访问它们的值
一个 存储 是一个允许通过一个简单的 存储合同 反应性访问值的对象。svelte/store
模块 包含实现此合同的最小存储实现。
任何时候你有一个存储的引用,你可以通过在其前缀加上 $
字符
来在组件内访问它的值。这导致 Svelte 声明前缀变量,在组件初始化时订阅存储,并在适当的时候取消订阅。
对 $
前缀变量的分配要求变量是可写的存储,并将导致调用存储的 .set
方法。
注意,存储必须在组件的顶层声明 —— 例如,不在 if
块或函数内部。
不代表存储值的局部变量必须 不 有 $
前缀。
<script>
import { writable } from 'svelte/store';
const count = writable(0);
console.log($count); // 日志 0
count.set(1);
console.log($count); // 日志 1
$count = 2;
console.log($count); // 日志 2
</script>
store 契约
store = { subscribe: (subscription: (value: any) => void) => (() => void), set?: (value: any) => void }
你可以通过实现 存储合同 来创建自己的存储,而不依赖于 svelte/store
:
- 存储必须包含一个
.subscribe
方法,它必须接受一个订阅函数作为其参数。此订阅函数必须在调用.subscribe
时立即并同步地使用存储的当前值调用。每当存储的值更改时,所有存储的活跃订阅函数都必须稍后同步调用。 .subscribe
方法必须返回一个取消订阅函数。调用取消订阅函数必须停止其订阅,其对应的订阅函数不得再次被存储调用。- 存储可能 选择性 包含一个
.set
方法,它必须接受存储的新值作为其参数,并且同步调用所有存储的活跃订阅函数。这样的存储称为 可写存储。
为了与 RxJS Observables 互操作,.subscribe
方法也允许返回一个带有 .unsubscribe
方法的对象,而不是直接返回取消订阅函数。但请注意,除非 .subscribe
同步调用订阅(这不符合 Observable 规范的要求),否则 Svelte 将看到存储的值为 undefined
,直到它这样做。
<script context="module">
带有 context="module"
属性的 <script>
标签在模块首次评估时运行一次,而不是为每个组件实例。在该块中声明的值可以从常规 <script>
(和组件标记)中访问,但不能反向访问。
你可以从这个块中 export
绑定,它们将成为编译模块的导出。
你不能 export default
,因为默认导出是组件本身。
在
module
脚本中定义的变量不是响应式的 —— 即使变量本身会更新,重新分配它们也不会触发重新渲染。对于在多个组件之间共享的值,请考虑使用 存储。
<script context="module">
let totalComponents = 0;
// export 关键字允许此函数被导入,例如
// `import Example, { alertTotal } from './Example.svelte'`
export function alertTotal() {
alert(totalComponents);
}
</script>
<script>
totalComponents += 1;
console.log(`这个组件被创建的总次数:${totalComponents}`);
</script>
<style>
<style>
块内的 CSS 将被限定在该组件范围内。
这是通过向受影响的元素添加一个类来实现的,该类基于组件样式的哈希(例如 svelte-123xyz
)。
<style>
p {
/* 这只会影响此组件中的 <p> 元素 */
color: burlywood;
}
</style>
要全局应用样式,请使用 :global(...)
修饰符。
<style>
:global(body) {
/* 这将应用于 <body> */
margin: 0;
}
div :global(strong) {
/* 这将应用于此组件中属于 <div> 元素内部的所有 <strong> 元素 */
color: goldenrod;
}
p:global(.red) {
/* 这将应用于此组件中所有具有 red 类的 <p> 元素,即使 class="red" 最初没有出现在标记中,而是在运行时添加的。当元素的类直接动态应用时,例如直接更新元素的 classList 属性时,这很有用。 */
}
</style>
如果你想制作全局可访问的 @keyframes,你需要将你的 keyframe 名称前缀为 -global-
。
编译时 -global-
部分将被移除,然后 keyframe 就可以在代码的其他部分使用 my-animation-name
引用。
<style>
@keyframes -global-my-animation-name {
/* 代码放在这里 */
}
</style>
每个组件应该只有一个顶级 <style>
标签。
然而,可以在其他元素或逻辑块内嵌套 <style>
标签。
在这种情况下,<style>
标签将原样插入到 DOM 中,不会对 <style>
标签进行限定或处理。
<div>
<style>
/* 这个样式标签将原样插入 */
div {
/* 这将应用于 DOM 中的所有 `<div>` 元素 */
color: red;
}
</style>
</div>