$effect
效果 (Effects) 是讓您的應用程式執行動作的關鍵。當 Svelte 執行效果函式時,它會追蹤存取了哪些狀態 (state) (以及衍生狀態 (derived state)) (除非是在 untrack
內部存取),並在該狀態稍後變更時重新執行該函式。
Svelte 應用程式中的大多數效果都是由 Svelte 本身建立的 — 例如,它們是在 <h1>hello {name}!</h1>
中當 name
變更時更新文字的部分。
但是您也可以使用 $effect
符號建立您自己的效果,當您需要將外部系統 (無論是函式庫、<canvas>
元素,或網路上的某個東西) 與 Svelte 應用程式內的狀態同步時,這非常有用。
避免過度使用
$effect
!當您在效果中執行太多工作時,程式碼通常會變得難以理解和維護。請參閱何時不應使用$effect
,以了解替代方法。
您的效果會在元件掛載到 DOM 後執行,並在狀態變更後的微任務 (microtask)中執行 (範例)
<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" />
重新執行會批次處理 (batch) (也就是說,在同一時間變更 color
和 size
不會導致兩個單獨的執行),並且會在所有 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.
$effect(() => {
const const context: CanvasRenderingContext2D
context = let canvas: {
width: number;
height: number;
getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D;
}
canvas.function getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D
getContext('2d');
const context: CanvasRenderingContext2D
context.CanvasRect.clearRect(x: number, y: number, w: number, h: number): void
clearRect(0, 0, let canvas: {
width: number;
height: number;
getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D;
}
canvas.width: number
width, let canvas: {
width: number;
height: number;
getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D;
}
canvas.height: number
height);
// this will re-run whenever `color` changes...
const context: CanvasRenderingContext2D
context.CanvasFillStrokeStyles.fillStyle: string | CanvasGradient | CanvasPattern
fillStyle = let color: string
color;
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()
.
setTimeout(() => {
// ...but not when `size` changes
const context: CanvasRenderingContext2D
context.CanvasRect.fillRect(x: number, y: number, w: number, h: number): void
fillRect(0, 0, let size: number
size, let size: number
size);
}, 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
為 true,則對 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.
$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
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.
log('running');
if (let a: false
a || let b: false
b) {
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
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.
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: T
initial_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) => () => void
start: (callback: (update: (v: T) => T) => T
callback: (update: (v: T) => T
update: (v: T
v: 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: T
value = function $state<T>(initial: T): T (+1 overload)
namespace $state
Declares reactive state.
Example:
let count = $state(0);
$state(initial_value: T
initial_value);
let let subscribers: number
subscribers = 0;
let let stop: (() => void) | null
stop: null | (() => void) = null;
return {
get value: T
value() {
// 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.
$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:
<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.
https://svelte.dev.org.tw/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.
$effect(() => {
// ...and there's no subscribers yet...
if (let subscribers: number
subscribers === 0) {
// ...invoke the function and listen to changes to update state
let stop: (() => void) | null
stop = start: (callback: (update: (v: T) => T) => T) => () => void
start((fn: (v: T) => T
fn) => (let value: T
value = fn: (v: T) => T
fn(let value: T
value)));
}
let subscribers: number
subscribers++;
// 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.
then(() => {
let subscribers: number
subscribers--;
// If it was the last subscriber...
if (let subscribers: number
subscribers === 0) {
// ...stop listening to changes
let stop: (() => void) | null
stop?.();
let stop: (() => void) | null
stop = null;
}
});
};
});
}
return let value: T
value;
}
};
}
$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。