跳至主要内容

$effect

效果使您的应用程序“执行操作”。当 Svelte 运行效果函数时,它会跟踪访问了哪些状态(和派生状态)(除非在 untrack 内部访问),并在该状态稍后更改时重新运行该函数。

Svelte 应用程序中的大多数效果都是由 Svelte 本身创建的——例如,当 name 更改时,它们是更新 <h1>hello {name}!</h1> 中文本的部分。

但是,您还可以使用 $effect 符文创建自己的效果,当您需要将外部系统(无论是库、<canvas> 元素还是网络上的某些内容)与 Svelte 应用程序内的状态同步时,这很有用。

避免过度使用 $effect!当您在效果中执行太多工作时,代码通常会变得难以理解和维护。请参阅 何时不使用 $effect 以了解替代方法。

您的效果在组件已挂载到 DOM 之后运行,并在状态更改后的 微任务 中运行 (演示)。

<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" />

重新运行是批量进行的(即,在同一时刻更改 colorsize 不会导致两次单独的运行),并且在应用所有 DOM 更新后发生。

您可以将 $effect 放在任何位置,而不仅仅是组件的顶层,只要它在组件初始化期间(或在父级效果处于活动状态时)被调用即可。然后它与组件(或父级效果)的生命周期绑定,因此当组件卸载(或父级效果被销毁)时,它将自行销毁。

您可以从 $effect 返回一个函数,该函数将在效果重新运行之前以及在它被销毁之前立即运行 (演示)。

<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 callback 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 会安排重新运行。

异步读取的值(例如,在 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.net.cn/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, ms?: 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, 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.
@paramargs Optional arguments to pass when the callback is called.
@returnfor use with {@link 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>

效果仅取决于它上次运行时读取的值。如果 a 为真,则 b 的更改将 不会导致此效果重新运行

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.net.cn/docs/svelte/$effect

@paramfn The function to execute
$effect
(() => {
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without calling require('console').

Warning: The global console object’s methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
//   Error: Whoops, something bad happened
//     at [eval]:5:15
//     at Script.runInThisContext (node:vm:132:18)
//     at Object.runInThisContext (node:vm:309:38)
//     at node:internal/process/execution:77:19
//     at [eval]-wrapper:6:22
//     at evalScript (node:internal/process/execution:76:60)
//     at node:internal/main/eval_string:23:3

const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);

myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err

const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
@seesource
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100
log
('running');
if (let a: falsea || let b: falseb) { var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without calling require('console').

Warning: The global console object’s methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
//   Error: Whoops, something bad happened
//     at [eval]:5:15
//     at Script.runInThisContext (node:vm:132:18)
//     at Object.runInThisContext (node:vm:309:38)
//     at node:internal/process/execution:77:19
//     at [eval]-wrapper:6:22
//     at evalScript (node:internal/process/execution:76:60)
//     at node:internal/main/eval_string:23:3

const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);

myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err

const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
@seesource
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100
log
('inside if block');
} });

$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

$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 -->

这允许您(例如)添加诸如订阅之类的内容,而不会导致内存泄漏,方法是将它们放在子效果中。这是一个 readable 函数,只要它在跟踪上下文中,就会侦听来自回调函数的更改

import { function tick(): Promise<void>

Returns a promise that resolves once any pending state changes have been applied.

tick
} from 'svelte';
export default function
function readable<T>(initial_value: T, start: (callback: (update: (v: T) => T) => T) => () => void): {
    readonly value: T;
}
readable
<
function (type parameter) T in readable<T>(initial_value: T, start: (callback: (update: (v: T) => T) => T) => () => void): {
    readonly value: T;
}
T
>(
initial_value: Tinitial_value:
function (type parameter) T in readable<T>(initial_value: T, start: (callback: (update: (v: T) => T) => T) => () => void): {
    readonly value: T;
}
T
,
start: (callback: (update: (v: T) => T) => T) => () => voidstart: (callback: (update: (v: T) => T) => Tcallback: (update: (v: T) => Tupdate: (v: Tv:
function (type parameter) T in readable<T>(initial_value: T, start: (callback: (update: (v: T) => T) => T) => () => void): {
    readonly value: T;
}
T
) =>
function (type parameter) T in readable<T>(initial_value: T, start: (callback: (update: (v: T) => T) => T) => () => void): {
    readonly value: T;
}
T
) =>
function (type parameter) T in readable<T>(initial_value: T, start: (callback: (update: (v: T) => T) => T) => () => void): {
    readonly value: T;
}
T
) => () => void
) { let let value: Tvalue =
function $state<T>(initial: T): T (+1 overload)
namespace $state

Declares reactive state.

Example:

let count = $state(0);

https://svelte.net.cn/docs/svelte/$state

@paraminitial The initial value
$state
(initial_value: Tinitial_value);
let let subscribers: numbersubscribers = 0; let let stop: (() => void) | nullstop: null | (() => void) = null; return { get value: Tvalue() { // If in a tracking context ... if (
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.net.cn/docs/svelte/$effect

@paramfn The function to execute
$effect
.function $effect.tracking(): boolean

The $effect.tracking rune is an advanced feature that tells you whether or not the code is running inside a tracking context, such as an effect or inside your template.

Example:

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

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

&#x3C;p>in template: {$effect.tracking()}&#x3C;/p> &#x3C;!-- true -->

This allows you to (for example) add things like subscriptions without causing memory leaks, by putting them in child effects.

https://svelte.net.cn/docs/svelte/$effect#$effect.tracking

tracking
()) {
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.net.cn/docs/svelte/$effect

@paramfn The function to execute
$effect
(() => {
// ...and there's no subscribers yet... if (let subscribers: numbersubscribers === 0) { // ...invoke the function and listen to changes to update state let stop: (() => void) | nullstop = start: (callback: (update: (v: T) => T) => T) => () => voidstart((fn: (v: T) => Tfn) => (let value: Tvalue = fn: (v: T) => Tfn(let value: Tvalue))); } let subscribers: numbersubscribers++; // The return callback is called once a listener unlistens return () => { function tick(): Promise<void>

Returns a promise that resolves once any pending state changes have been applied.

tick
().Promise<void>.then<void, never>(onfulfilled?: ((value: void) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

@paramonfulfilled The callback to execute when the Promise is resolved.
@paramonrejected The callback to execute when the Promise is rejected.
@returnsA Promise for the completion of which ever callback is executed.
then
(() => {
let subscribers: numbersubscribers--; // If it was the last subscriber... if (let subscribers: numbersubscribers === 0) { // ...stop listening to changes let stop: (() => void) | nullstop?.(); let stop: (() => void) | nullstop = null; } }); }; }); } return let value: Tvalue; } }; }

$effect.root

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

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

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

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

何时不使用 $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

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

<script>
	let 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>

相反,在可能的情况下使用回调 (演示)

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

	function updateSpent(e) {
		spent = +e.target.value;
		left = total - spent;
	}

	function updateLeft(e) {
		left = +e.target.value;
		spent = total - left;
	}
</script>

<label>
	<input type="range" value={spent} oninput={updateSpent} max={total} />
	{spent}/{total} spent
</label>

<label>
	<input type="range" value={left} oninput={updateLeft} max={total} />
	{left}/{total} left
</label>

如果您出于任何原因需要使用绑定(例如,当您想要某种“可写的 $derived”时),请考虑使用 getter 和 setter 来同步状态 (演示)

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

	let left = {
		get value() {
			return total - spent;
		},
		set value(v) {
			spent = total - v;
		}
	};
</script>

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

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

如果您绝对必须在效果内更新 $state 并由于读取和写入相同的 $state 而陷入无限循环,请使用 untrack

在 GitHub 上编辑此页面

上一页 下一页