Skip to main content

$effect

效果是当状态更新时运行的函数,可用于调用第三方库、在<canvas>元素上绘图或发起网络请求。它们仅在浏览器中运行,不在服务器端渲染期间运行。

一般来说,你应不要在效果中更新状态,因为这会使代码更加复杂,并常常导致无限循环更新。如果你发现自己正在这样做,请参阅何时不应使用$effect以了解替代方法。

您可以使用$effect符文(演示)创建效果:

<script>
	let size = $state(50);
	let color = $state('#ff3e00');

	let canvas;

	$effect(() => {
		const context = canvas.getContext('2d');
		context.clearRect(0, 0, canvas.width, canvas.height);

		// this will re-run whenever `color` or `size` change
		context.fillStyle = color;
		context.fillRect(0, 0, size, size);
	});
</script>

<canvas bind:this={canvas} width="100" height="100"></canvas>

當 Svelte 运行效果函数時,它會追踪哪些狀態(以及導出狀態)被訪問(除非在 untrack 內部訪問),當該狀態之後發生變化時,會重新運行該函數。

[!注意] 如果您难以理解为什么您的 $effect 重新运行或没有运行,请参阅 理解依赖关系。与您从 Svelte 4 过来可能习惯的 $: 块相比,效果触发方式不同。

理解生命周期

您的效果在组件挂载到 DOM 之后运行,并在状态变化后的一个微任务中运行。重新运行是分批进行的(即在同一时刻更改颜色大小不会导致两次单独的运行),并且发生在任何 DOM 更新之后。

您可以在任何地方使用$effect,而不仅仅是组件的顶层,只要在父级效果运行时调用即可。

[!注意] Svelte 在模板内部使用效果来表示逻辑和表达式 — 这就是当 <h1>hello {name}!</h1> 更新时 name 发生变化的方式。

一个效果可以返回一个在效果重新运行前立即执行的 拆卸函数演示)。

<script>
	let count = $state(0);
	let milliseconds = $state(1000);

	$effect(() => {
		// This will be recreated whenever `milliseconds` changes
		const interval = setInterval(() => {
			count += 1;
		}, milliseconds);

		return () => {
			// if a teardown function is provided, it will run
			// a) immediately before the effect re-runs
			// b) when the component is destroyed
			clearInterval(interval);
		};
	});
</script>

<h1>{count}</h1>

<button onclick={() => (milliseconds *= 2)}>slower</button>
<button onclick={() => (milliseconds /= 2)}>faster</button>

拆解函数在效果被销毁时也会运行,这发生在其父级被销毁(例如,组件被卸载)或父级效果重新运行时。

理解依赖关系

$effect 自动获取其函数体内(包括间接地,通过函数调用)同步读取的任何响应式值($state$derived$props)并将它们注册为依赖项。当这些依赖项发生变化时,$effect 将安排重新运行。

如果直接在 $effect 中使用 $state$derived(例如,在创建一个 reactive 类 时),这些值将 不会被 作为依赖项处理。

值在异步读取*(例如,在await之后或setTimeout内部)时不会被跟踪。在此,当color改变时,画布将被重绘,但size改变时不会(演示):*

function $effect(fn: () => void | (() => void)): void
namespace $effect

Runs code when a component is mounted to the DOM, and then whenever its dependencies change, i.e. $state or $derived values. The timing of the execution is after the DOM has been updated.

Example:

$effect(() => console.log('The count is now ' + count));

If you return a function from the effect, it will be called right before the effect is run again, or when the component is unmounted.

Does not run during server side rendering.

https://svelte.dev/docs/svelte/$effect

@paramfn The function to execute
$effect
(() => {
const const context: CanvasRenderingContext2Dcontext =
let canvas: {
    width: number;
    height: number;
    getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D;
}
canvas
.function getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2DgetContext('2d');
const context: CanvasRenderingContext2Dcontext.CanvasRect.clearRect(x: number, y: number, w: number, h: number): voidclearRect(0, 0,
let canvas: {
    width: number;
    height: number;
    getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D;
}
canvas
.width: numberwidth,
let canvas: {
    width: number;
    height: number;
    getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D;
}
canvas
.height: numberheight);
// this will re-run whenever `color` changes... const context: CanvasRenderingContext2Dcontext.CanvasFillStrokeStyles.fillStyle: string | CanvasGradient | CanvasPatternfillStyle = let color: stringcolor; function setTimeout<[]>(callback: () => void, delay?: number): NodeJS.Timeout (+2 overloads)

Schedules execution of a one-time callback after delay milliseconds.

The callback will likely not be invoked in precisely delay milliseconds. Node.js makes no guarantees about the exact timing of when callbacks will fire, nor of their ordering. The callback will be called as close as possible to the time specified.

When delay is larger than 2147483647 or less than 1 or NaN, the delay will be set to 1. Non-integer delays are truncated to an integer.

If callback is not a function, a TypeError will be thrown.

This method has a custom variant for promises that is available using timersPromises.setTimeout().

@sincev0.0.1
@paramcallback The function to call when the timer elapses.
@paramdelay The number of milliseconds to wait before calling the callback. Default: 1.
@paramargs Optional arguments to pass when the callback is called.
@returnsfor use with clearTimeout()
setTimeout
(() => {
// ...but not when `size` changes const context: CanvasRenderingContext2Dcontext.CanvasRect.fillRect(x: number, y: number, w: number, h: number): voidfillRect(0, 0, let size: numbersize, let size: numbersize); }, 0); });

仅当读取的对象发生变化时,效果才会重新运行,而不是当它内部的属性发生变化时。(如果您想在开发时观察对象内部的更改,可以使用$inspect。)

<script>
	let state = $state({ value: 0 });
	let derived = $derived({ value: state.value * 2 });

	// this will run once, because `state` is never reassigned (only mutated)
	$effect(() => {
		state;
	});

	// this will run whenever `state.value` changes...
	$effect(() => {
		state.value;
	});

	// ...and so will this, because `derived` is a new object each time
	$effect(() => {
		derived;
	});
</script>

<button onclick={() => (state.value += 1)}>
	{state.value}
</button>

<p>{state.value} doubled is {derived.value}</p>

一个效果只依赖于它上次运行时读取的值。这对具有条件代码的效果有有趣的含义。

例如,如果以下代码片段中的conditiontrue,则if块内的代码将执行,并且color将被评估。因此,对conditioncolor的更改将导致效果重新运行。

相反,如果 条件颜色 将不会被评估,并且效果将 条件 发生变化时再次运行。

import function confetti(opts?: ConfettiOptions): voidconfetti from 'canvas-confetti';

let let condition: booleancondition = 
function $state<true>(initial: true): true (+1 overload)
namespace $state

Declares reactive state.

Example:

let count = $state(0);

https://svelte.dev/docs/svelte/$state

@paraminitial The initial value
$state
(true);
let let color: stringcolor =
function $state<"#ff3e00">(initial: "#ff3e00"): "#ff3e00" (+1 overload)
namespace $state

Declares reactive state.

Example:

let count = $state(0);

https://svelte.dev/docs/svelte/$state

@paraminitial The initial value
$state
('#ff3e00');
function $effect(fn: () => void | (() => void)): void
namespace $effect

Runs code when a component is mounted to the DOM, and then whenever its dependencies change, i.e. $state or $derived values. The timing of the execution is after the DOM has been updated.

Example:

$effect(() => console.log('The count is now ' + count));

If you return a function from the effect, it will be called right before the effect is run again, or when the component is unmounted.

Does not run during server side rendering.

https://svelte.dev/docs/svelte/$effect

@paramfn The function to execute
$effect
(() => {
if (let condition: truecondition) { function confetti(opts?: ConfettiOptions): voidconfetti({ ConfettiOptions.colors: string[]colors: [let color: stringcolor] }); } else { function confetti(opts?: ConfettiOptions): voidconfetti(); } });

$effect.pre

在罕见情况下,您可能需要在 DOM 更新之前运行代码*。*为此,我们可以使用$effect.pre符文:

<script>
	import { tick } from 'svelte';

	let div = $state();
	let messages = $state([]);

	// ...

	$effect.pre(() => {
		if (!div) return; // not yet mounted

		// reference `messages` array length so that this code re-runs whenever it changes
		messages.length;

		// autoscroll when new messages are added
		if (div.offsetHeight + div.scrollTop > div.scrollHeight - 20) {
			tick().then(() => {
				div.scrollTo(0, div.scrollHeight);
			});
		}
	});
</script>

<div bind:this={div}>
	{#each messages as message}
		<p>{message}</p>
	{/each}
</div>

除了时间之外,$effect.pre 的功能与 $effect 完全相同。

$effect.tracking

The $effect.tracking 符文是一个高级功能,它告诉您代码是否在跟踪上下文中运行,例如在效果或您的模板中(示例):

<script>
	console.log('in component setup:', $effect.tracking()); // false

	$effect(() => {
		console.log('in effect:', $effect.tracking()); // true
	});
</script>

<p>in template: {$effect.tracking()}</p> <!-- true -->

用于实现如 createSubscriber 这样的抽象,这将创建监听器以更新响应式值,但 当这些值正在被跟踪(而不是,例如,在事件处理程序内部读取)时。

$effect.pending

当在组件中使用await时,$effect.pending()函数告诉你当前边界(不包括子边界示例)中有多少个待处理的承诺:

<button onclick={() => a++}>a++</button>
<button onclick={() => b++}>b++</button>

<p>{a} + {b} = {await add(a, b)}</p>

{#if $effect.pending()}
	<p>pending promises: {$effect.pending()}</p>
{/if}

$effect.root

The $effect.root 符文是一个高级功能,它创建一个非跟踪作用域,不会自动清理。这对于您想手动控制嵌套效果非常有用。此符文还允许在组件初始化阶段之外创建效果。

const const destroy: () => voiddestroy = 
namespace $effect
function $effect(fn: () => void | (() => void)): void

Runs code when a component is mounted to the DOM, and then whenever its dependencies change, i.e. $state or $derived values. The timing of the execution is after the DOM has been updated.

Example:

$effect(() => console.log('The count is now ' + count));

If you return a function from the effect, it will be called right before the effect is run again, or when the component is unmounted.

Does not run during server side rendering.

https://svelte.dev/docs/svelte/$effect

@paramfn The function to execute
$effect
.function $effect.root(fn: () => void | (() => void)): () => void

The $effect.root rune is an advanced feature that creates a non-tracked scope that doesn’t auto-cleanup. This is useful for nested effects that you want to manually control. This rune also allows for creation of effects outside of the component initialisation phase.

Example:

&#x3C;script>
  let count = $state(0);

  const cleanup = $effect.root(() => {
	$effect(() => {
			console.log(count);
		})

	 return () => {
	   console.log('effect root cleanup');
			}
  });
&#x3C;/script>

&#x3C;button onclick={() => cleanup()}>cleanup&#x3C;/button>

https://svelte.dev/docs/svelte/$effect#$effect.root

root
(() => {
function $effect(fn: () => void | (() => void)): void
namespace $effect

Runs code when a component is mounted to the DOM, and then whenever its dependencies change, i.e. $state or $derived values. The timing of the execution is after the DOM has been updated.

Example:

$effect(() => console.log('The count is now ' + count));

If you return a function from the effect, it will be called right before the effect is run again, or when the component is unmounted.

Does not run during server side rendering.

https://svelte.dev/docs/svelte/$effect

@paramfn The function to execute
$effect
(() => {
// setup }); return () => { // cleanup }; }); // later... const destroy: () => voiddestroy();

當何時不使用$effect

通常,$effect最好被视为一种逃生门——适用于像分析和对 DOM 的直接操作这样的用途——而不是你应该频繁使用的工具。特别是,避免用它来同步状态。而不是这样做……

<script>
	let count = $state(0);
	let doubled = $state();

	// don't do this!
	$effect(() => {
		doubled = count * 2;
	});
</script>

...这样做:

<script>
	let count = $state(0);
	let doubled = $derived(count * 2);
</script>

[!注意] 对于比简单表达式如count * 2更复杂的情况,您也可以使用$derived.by

如果您使用效果是因为您想要能够重新分配派生值(例如构建乐观的用户界面),请注意,从 Svelte 5.25 开始,派生值可以直接覆盖。

您可能会想用一些复杂的效果将一个值链接到另一个值。以下示例显示了两个输入:“花费的金额”和“剩余的金额”,它们彼此相连。如果您更新其中一个,另一个也应该相应更新。不要为此使用效果(演示):

<script>
	const total = 100;
	let spent = $state(0);
	let left = $state(total);

	$effect(() => {
		left = total - spent;
	});

	$effect(() => {
		spent = total - left;
	});
</script>

<label>
	<input type="range" bind:value={spent} max={total} />
	{spent}/{total} spent
</label>

<label>
	<input type="range" bind:value={left} max={total} />
	{left}/{total} left
</label>

相反,使用oninput回调或——更好的选择——尽可能使用函数绑定演示):

<script>
	const total = 100;
	let spent = $state(0);
	let left = $derived(total - spent);

	function updateLeft(left) {
		spent = total - left;
	}
</script>

<label>
	<input type="range" bind:value={spent} max={total} />
	{spent}/{total} spent
</label>

<label>
	<input type="range" bind:value={() => left, updateLeft} max={total} />
	{left}/{total} left
</label>

如果您绝对需要在效果中更新$state并遇到无限循环,因为您读取和写入相同的$state,请使用untrack

Edit this page on GitHub llms.txt

previous next