跳至主要内容

模板语法

Svelte 组件

在 GitHub 上编辑此页面

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

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

<script>
	// logic goes here
</script>

<!-- markup (zero or more items) goes here -->

<style>
	/* styles go here */
</style>

<script>

<script> 块包含在创建组件实例时运行的 JavaScript。在顶级声明(或导入)的变量从组件的标记中“可见”。还有四条附加规则

1. export 创建组件属性

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

<script>
	export let foo;

	// Values that are passed in as props
	// are immediately available
	console.log({ foo });
</script>

您可以为 prop 指定默认初始值。如果组件的使用者在实例化组件时未在组件上指定 prop(或其初始值为 undefined),则将使用该值。请注意,如果随后更新 prop 的值,则任何未指定其值 prop 都将设置为 undefined(而不是其初始值)。

在开发模式下(请参阅 编译器选项),如果未提供默认初始值且使用者未指定值,则会打印警告。要消除此警告,请确保指定默认初始值,即使该值为 undefined

<script>
	export let bar = 'optional default initial value';
	export let baz = undefined;
</script>

如果您导出 constclassfunction,则它在组件外部是只读的。但是,函数是有效的 prop 值,如下所示。

App.svelte
<script>
	// these are readonly
	export const thisIs = 'readonly';

	/** @param {string} name */
	export function greet(name) {
		alert(`hello ${name}!`);
	}

	// this is a prop
	export let format = (n) => n.toFixed(2);
</script>
App.svelte
<script lang="ts">
	// these are readonly
	export const thisIs = 'readonly';
	
	export function greet(name: string) {
		alert(`hello ${name}!`);
	}
	
	// this is a prop
	export let format = (n) => n.toFixed(2);
</script>

可以使用 bind:this 语法 将只读 prop 作为元素上的属性访问,该属性与组件绑定。

您可以将保留字用作 prop 名称。

App.svelte
<script>
	/** @type {string} */
	let className;

	// creates a `class` property, even
	// though it is a reserved word
	export { className as class };
</script>
App.svelte
<script lang="ts">
	let className: string;
	
	// creates a `class` property, even
	// though it is a reserved word
	export { className as class };
</script>

2. 赋值是“响应式的”

要更改组件状态并触发重新渲染,只需赋值给本地声明的变量。

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

<script>
	let count = 0;

	function handleClick() {
		// calling this function will trigger an
		// update if the markup references `count`
		count = count + 1;
	}
</script>

由于 Svelte 的响应性基于赋值,因此使用数组方法(如 .push().splice())不会自动触发更新。需要后续赋值才能触发更新。您还可以在 教程 中找到此内容和更多详细信息。

<script>
	let arr = [0, 1];

	function handleClick() {
		// this method call does not trigger an update
		arr.push(2);
		// this assignment will trigger an update
		// if the markup references `arr`
		arr = arr;
	}
</script>

Svelte 的 <script> 块仅在创建组件时运行,因此当 prop 更新时,<script> 块内的赋值不会自动再次运行。如果您想跟踪 prop 的更改,请参阅下一节中的下一个示例。

<script>
	export let person;
	// this will only set `name` on component creation
	// it will not update when `person` does
	let { name } = person;
</script>

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

可以通过在顶级语句(即不在块或函数内)前加上 $: JS 标签语法使其成为响应式的。响应式语句在其他脚本代码之后和组件标记呈现之前运行,只要它们依赖的值发生更改。

<script>
	export let title;
	export let person;

	// this will update `document.title` whenever
	// the `title` prop changes
	$: document.title = title;

	$: {
		console.log(`multiple statements can be combined`);
		console.log(`the current title is ${title}`);
	}

	// this will update `name` when 'person' changes
	$: ({ name } = person);

	// don't do this. it will run before the previous line
	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++}> Increment X </button>

<button on:click={() => y++}> Increment 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++}> Increment X </button>

<button on:click={() => y++}> Increment Y </button>

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

<script>
	let x = 0;
	let y = 0;

	/** @param {number} value */
	function setY(value) {
		y = value;
	}

	$: yDependent = y;
	$: setY(x);
</script>

$: yDependent = y 行移动到 $: setY(x) 下方将导致在更新 x 时更新 yDependent

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

App.svelte
<script>
	/** @type {number} */
	export let num;

	// we don't need to declare `squared` and `cubed`
	// — Svelte does it for us
	$: squared = num * num;
	$: cubed = squared * num;
</script>
App.svelte
<script lang="ts">
	export let num: number;
	
	// we don't need to declare `squared` and `cubed`
	// — Svelte does it for us
	$: 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); // logs 0

	count.set(1);
	console.log($count); // logs 1

	$count = 2;
	console.log($count); // logs 2
</script>

存储契约

ts
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 脚本中定义的变量不是响应式的——重新赋值它们不会触发重新渲染,即使变量本身会更新。对于在多个组件之间共享的值,请考虑使用 存储

<script context="module">
	let totalComponents = 0;

	// the export keyword allows this function to imported with e.g.
	// `import Example, { alertTotal } from './Example.svelte'`
	export function alertTotal() {
		alert(totalComponents);
	}
</script>

<script>
	totalComponents += 1;
	console.log(`total number of times this component has been created: ${totalComponents}`);
</script>

<style>

<style> 块中的 CSS 将限定为该组件。

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

<style>
	p {
		/* this will only affect <p> elements in this component */
		color: burlywood;
	}
</style>

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

<style>
	:global(body) {
		/* this will apply to <body> */
		margin: 0;
	}

	div :global(strong) {
		/* this will apply to all <strong> elements, in any
			 component, that are inside <div> elements belonging
			 to this component */
		color: goldenrod;
	}

	p:global(.red) {
		/* this will apply to all <p> elements belonging to this
			 component with a class of red, even if class="red" does
			 not initially appear in the markup, and is instead
			 added at runtime. This is useful when the class
			 of the element is dynamically applied, for instance
			 when updating the element's classList property directly. */
	}
</style>

如果您想制作可全局访问的 @keyframes,则需要在关键帧名称前加上 -global-

编译时 -global- 部分将被移除,然后在代码中的其他位置使用 my-animation-name 引用关键帧。

<style>
	@keyframes -global-my-animation-name {
		/* code goes here */
	}
</style>

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

但是,可以在其他元素或逻辑块内嵌套 <style> 标记。

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

<div>
	<style>
		/* this style tag will be inserted as-is */
		div {
			/* this will apply to all `<div>` elements in the DOM */
			color: red;
		}
	</style>
</div>
上一个 简介
下一步 基本标记