Skip to main content

$effect

Effects 使你的应用程序能够 做点事情。当 Svelte 运行一个 effect 函数时,它会跟踪被访问(除非在 untrack 中访问)的状态(和派生状态),并在该状态后续发生变化时重新运行该函数。

Svelte 应用程序中的大多数 effects 是由 Svelte 本身创建的——例如,当 name 变化时,更新 <h1>hello {name}!</h1> 中的文本。

但你也可以使用 $effect 符文创建自己的 effects,当你需要将外部系统(无论是库、<canvas> 元素,还是跨网络的某些东西)与 Svelte 应用程序内部的状态同步时,这非常有用。

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

你的 effects 在组件挂载到 DOM 之后运行,并在状态变化后的 微任务 中运行(demo):

<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);

		// 只要 `color` 或 `size` 发生变化,这段代码就会重新执行
		context.fillStyle = color;
		context.fillRect(0, 0, size, size);
	});
</script>

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

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

你可以将 $effect 放在任何地方,不仅仅在组件的顶层,只要在组件初始化时调用它(或者在父 effect 处于激活状态时)。它就会与组件(或父 effect)的生命周期绑定,因此当组件卸载(或父 effect 被销毁)时,它会自行销毁。

你可以从 $effect 返回一个函数,该函数将在 effect 重新运行之前立即运行,并在它被销毁之前运行(demo)。

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

	$effect(() => {
		// 每当 `milliseconds` 变化时,这段代码都会被重新创建
		const interval = setInterval(() => {
			count += 1;
		}, milliseconds);

		return () => {
			// 如果提供了回调,它将在
			// a) effect 重新运行之前立即被调用
			// b) 当组件被销毁时被调用
			clearInterval(interval);
		};
	});
</script>

<h1>{count}</h1>

<button onclick={() => (milliseconds *= 2)}>慢一点</button>
<button onclick={() => (milliseconds /= 2)}>快一点</button>

理解依赖关系

$effect 会自动获取在其函数体内 同步 读取的任何响应值($state$derived$props),并将它们注册为依赖关系。当这些依赖关系发生变化时,$effect 会安排重新运行。

await 之后或在 setTimeout 内部等情况下读取的值将不会被追踪。在这里,当 color 变化时,canvas 会重新绘制,但当 size 变化时将不会变化(demo):

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);
// 每当 `color` 发生变化时,这段代码都会重新运行... 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
(() => {
// ...但当 `size` 发生变化时却不会 const context: CanvasRenderingContext2Dcontext.CanvasRect.fillRect(x: number, y: number, w: number, h: number): voidfillRect(0, 0, let size: numbersize, let size: numbersize); }, 0); });

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

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

	// 这只会运行一次,因为 `state` 从未被重新分配(仅被修改)
	$effect(() => {
		state;
	});

	// 这将在每当 `state.value` 变化时运行...
	$effect(() => {
		state.value;
	});

	// ...这一点也是如此,因为 `derived` 每次都是一个新对象
	$effect(() => {
		derived;
	});
</script>

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

<p>{state.value} 的两倍是 {derived.value}</p>

effect 仅依赖于它上次运行时读取的值。如果 a 为真,则对 b 的更改不会 导致该 effect 重新运行:

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
(() => {
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
('运行中');
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
('在 if 块内');
} });

$effect.pre

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

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

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

	// ...

	$effect.pre(() => {
		if (!div) return; // 尚未挂载

		// 引用 `messages` 数组长度,以便当它改变时,此代码重新运行
		messages.length;

		// 当新消息被添加时自动滚动
		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 符文是一个高级特性,用于告知你代码是否在跟踪上下文中运行,例如 effect 或模板内部 (demo):

<script>
	console.log('在组件设置中:', $effect.tracking()); // false

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

<p>在模板中: {$effect.tracking()}</p> <!-- true -->

这允许你(例如)添加诸如订阅之类的内容而不会导致内存泄漏,方法是将它们放在子 effects 中。以下是一个 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.dev/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 (
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.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.dev/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.dev/docs/svelte/$effect

@paramfn The function to execute
$effect
(() => {
// ...且订阅者还没有 if (let subscribers: numbersubscribers === 0) { // ...调用函数并监听变化以更新状态 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++; // 返回的回调在监听器取消监听时调用 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 (let subscribers: numbersubscribers === 0) { // ...停止监听变化 let stop: (() => void) | nullstop?.(); let stop: (() => void) | nullstop = null; } }); }; }); } return let value: Tvalue; } }; }

$effect.root

$effect.root 符文是一个高级特性,它创建了一个不会自动清理的非跟踪作用域。这对于需要手动控制的嵌套 effects 很有用。这个符文还允许在组件初始化阶段之外创建 effects。

<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();

	// 不要这样做!
	$effect(() => {
		doubled = count * 2;
	});
</script>

...请这样做:

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

对于比像 count * 2 这样的简单表达式更复杂的内容,你也可以使用 $derived.by

你可能会想用 effects 以复杂的方式将一个值链接到另一个值。以下示例展示了两个输入框:”已花费金额”和"剩余金额”,它们彼此关联。如果你更新其中一个,另一个应该相应更新。不要为此使用 effects(demo):

<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} 已花费
</label>

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

相反,尽可能使用回调(demo):

<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} 已花费
</label>

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

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

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

如果您必须在 effect 中更新 $state 并且因为你读取和写入的是同一个 $state 而陷入无限循环,请使用 untrack

在 GitHub 编辑此页面

上一页 下一页