這是用戶在 2024-12-17 5:18 為 https://svelte.dev/docs/svelte/$effect 保存的雙語快照頁面,由 沉浸式翻譯 提供雙語支持。了解如何保存?
Skip to main content

$effect

Effects are what make your application do things. When Svelte runs an effect function, it tracks which pieces of state (and derived state) are accessed (unless accessed inside untrack), and re-runs the function when that state later changes.
效果是讓您的應用程式執行操作的關鍵。當 Svelte 執行一個效果函數時,它會追蹤哪些狀態(以及衍生狀態)被存取(除非在 untrack 內存取),並在該狀態後續變更時重新執行該函數。

Most of the effects in a Svelte app are created by Svelte itself — they’re the bits that update the text in <h1>hello {name}!</h1> when name changes, for example.
Svelte 應用中的大部分效果都是由 Svelte 本身創建的——例如,當 name 變化時更新 <h1>hello {name}!</h1> 中的文本。

But you can also create your own effects with the $effect rune, which is useful when you need to synchronize an external system (whether that’s a library, or a <canvas> element, or something across a network) with state inside your Svelte app.
但你也可以使用 $effect 符號創建自己的效果,這在你需要將外部系統(無論是庫、 <canvas> 元素,還是網絡上的某個東西)與 Svelte 應用內的狀態同步時非常有用。

Avoid overusing $effect! When you do too much work in effects, code often becomes difficult to understand and maintain. See when not to use $effect to learn about alternative approaches.
避免過度使用 $effect !當你在效果中做太多工作時,代碼往往會變得難以理解和維護。查看何時不應使用 $effect 以了解替代方法。

Your effects run after the component has been mounted to the DOM, and in a microtask after state changes (demo):
你的效果在組件掛載到 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" />

Re-runs are batched (i.e. changing color and size in the same moment won’t cause two separate runs), and happen after any DOM updates have been applied.
重新運行是批量進行的(即,在同一時刻更改 colorsize 不會導致兩次單獨的運行),並在任何 DOM 更新應用後發生。

You can place $effect anywhere, not just at the top level of a component, as long as it is called during component initialization (or while a parent effect is active). It is then tied to the lifecycle of the component (or parent effect) and will therefore destroy itself when the component unmounts (or the parent effect is destroyed).
你可以將 $effect 放在任何地方,不僅僅是組件的頂層,只要它在組件初始化期間(或在父效果激活時)被調用即可。然後它將與組件(或父效果)的生命週期綁定,因此當組件卸載(或父效果被銷毀)時,它將自動銷毀。

You can return a function from $effect, which will run immediately before the effect re-runs, and before it is destroyed (demo).
你可以從 $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>

Understanding dependencies
理解依賴關係

$effect automatically picks up any reactive values ($state, $derived, $props) that are synchronously read inside its function body and registers them as dependencies. When those dependencies change, the $effect schedules a rerun.
$effect 自動檢測其函數體內同步讀取的任何響應式值( $state$derived$props ),並將它們註冊為依賴項。當這些依賴項變化時, $effect 會安排重新運行。

Values that are read asynchronously — after an await or inside a setTimeout, for example — will not be tracked. Here, the canvas will be repainted when color changes, but not when size changes (demo):
異步讀取的值——例如在 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, 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); });

An effect only reruns when the object it reads changes, not when a property inside it changes. (If you want to observe changes inside an object at dev time, you can use $inspect.)
效果只會在它讀取的對象變化時重新運行,而不是當對象內的屬性變化時。(如果你想在開發時觀察對象內的變化,可以使用 $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>

An effect only depends on the values that it read the last time it ran. If a is true, changes to b will not cause this effect to rerun:
效果只依賴於它上次運行時讀取的值。如果 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.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
('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

In rare cases, you may need to run code before the DOM updates. For this we can use the $effect.pre rune:
在極少數情況下,你可能需要在 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>

Apart from the timing, $effect.pre works exactly like $effect.
除了時機之外, $effect.pre 的工作方式與 $effect 完全相同。

$effect.tracking

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 (demo):
$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 -->

This allows you to (for example) add things like subscriptions without causing memory leaks, by putting them in child effects. Here’s a readable function that listens to changes from a callback function as long as it’s inside a tracking context:
這允許你(例如)在不導致內存洩漏的情況下添加訂閱,方法是將它們放在子效果中。這是一個 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 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.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
(() => {
// ...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

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 the creation of effects outside of the component initialisation phase.
$effect.root 符號是一個高級功能,它創建一個不會自動清理的非追蹤範圍。這對於你想要手動控制的嵌套效果非常有用。此符號還允許在組件初始化階段之外創建效果。

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

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

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

When not to use $effect
何時不應使用 $effect

In general, $effect is best considered something of an escape hatch — useful for things like analytics and direct DOM manipulation — rather than a tool you should use frequently. In particular, avoid using it to synchronise state. Instead of this...
一般來說, $effect 最好被視為一種逃生艙——對於分析和直接 DOM 操作等事情很有用——而不是你應該經常使用的工具。特別是,避免使用它來同步狀態。相反,不要這樣做...

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

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

...do this:

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

For things that are more complicated than a simple expression like count * 2, you can also use $derived.by.
對於比 count * 2 這樣的簡單表達式更複雜的事情,你也可以使用 $derived.by

You might be tempted to do something convoluted with effects to link one value to another. The following example shows two inputs for “money spent” and “money left” that are connected to each other. If you update one, the other should update accordingly. Don’t use effects for this (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} spent
</label>

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

Instead, use callbacks where possible (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} spent
</label>

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

If you need to use bindings, for whatever reason (for example when you want some kind of “writable $derived”), consider using getters and setters to synchronise state (demo):
如果你出於某種原因需要使用綁定(例如,當你想要某種“可寫的 $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>

If you absolutely have to update $state within an effect and run into an infinite loop because you read and write to the same $state, use untrack.
如果你絕對必須在效果內更新 $state 並且因為讀取和寫入同一個 $state 而陷入無限循環,請使用 untrack。

Edit this page on GitHub

previous next
$effect • Docs • Svelte