$effect
Effects are functions that run when state updates, and can be used for things like calling third-party libraries, drawing on <canvas>
elements, or making network requests. They only run in the browser, not during server-side rendering.
Generally speaking, you should not update state inside effects, as it will make code more convoluted and will often lead to never-ending update cycles. If you find yourself doing so, see when not to use $effect
to learn about alternative approaches.
You can create an effect with the $effect
rune (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"></canvas>
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.
If you’re having difficulty understanding why your
$effect
is rerunning or is not running see understanding dependencies. Effects are triggered differently than the$:
blocks you may be used to if coming from Svelte 4.
Understanding lifecycle
Your effects run after the component has been mounted to the DOM, and in a microtask after state changes. 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.
You can use $effect
anywhere, not just at the top level of a component, as long as it is called while a parent effect is running.
Svelte uses effects internally to represent logic and expressions in your template — this is how
<h1>hello {name}!</h1>
updates whenname
changes.
An effect can return a teardown function which will run immediately before the effect re-runs (demo).
<script>
let count = $state(0);
let milliseconds = $state(1000);
$effect(() => {
// これは `milliseconds` が変更されるたびに再作成されます
const interval = setInterval(() => {
count += 1;
}, milliseconds);
return () => {
// teardown 関数が提供される場合、以下のときに実行されます
// a) effect が再実行される直前
// b) コンポーネントが破棄される前
clearInterval(interval);
};
});
</script>
<h1>{count}</h1>
<button onclick={() => (milliseconds *= 2)}>slower</button>
<button onclick={() => (milliseconds /= 2)}>faster</button>
Teardown functions also run when the effect is destroyed, which happens when its parent is destroyed (for example, a component is unmounted) or the parent effect re-runs.
依存関係を理解する
$effect
automatically picks up any reactive values ($state
, $derived
, $props
) that are synchronously read inside its function body (including indirectly, via function calls) and registers them as dependencies. When those dependencies change, the $effect
schedules a re-run.
If $state
and $derived
are used directly inside the $effect
(for example, during creation of a reactive class), those values will not be treated as dependencies.
非同期的に読み取られる値 (例えば、await
の後や setTimeout
内) は追跡されません。この例では、color
が変更されると canvas が再描画されますが、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);
// これは `color` が変更されるたびに再実行されます...
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(() => {
// ...しかしこれは `size` が変更されても再実行されません
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);
});
effect は、読み取るオブジェクトそのものが変更された場合にのみ再実行され、その中のプロパティが変更された場合には再実行されません。(オブジェクト内部の変更を開発時に観察したい場合は、$inspect
を使用できます。)
<script>
let state = $state({ value: 0 });
let derived = $derived({ value: state.value * 2 });
// これは一度だけ実行されます。なぜなら `state` は再代入されていないからです (変異(mutated)のみです)
$effect(() => {
state;
});
// これは `state.value` が変更されるたびに実行されます...
$effect(() => {
state.value;
});
// ...これも `state.value` が変更されるたびに実行されます。なぜなら `derived` は毎回新しいオブジェクトになるからです
$effect(() => {
derived;
});
</script>
<button onclick={() => (state.value += 1)}>
{state.value}
</button>
<p>{state.value} doubled is {derived.value}</p>
effect は、前回実行されたときに読み取られた値のみに依存します。これは、条件付きコードを持つ effect にとって興味深い含意があります。
例えば、以下のコードスニペットで condition
が true
の場合、if
ブロック内のコードが実行され、color
が評価されます。そのため、condition
または color
の変更が effect を再実行を引き起こします。
一方で、condition
が false
の場合、color
は評価されず、 effect は condition
が変更されたときに のみ 再実行されます。
import function confetti(opts?: ConfettiOptions): void
confetti from 'canvas-confetti';
let let condition: boolean
condition = function $state<true>(initial: true): true (+1 overload)
namespace $state
$state(true);
let let color: string
color = function $state<"#ff3e00">(initial: "#ff3e00"): "#ff3e00" (+1 overload)
namespace $state
$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.
$effect(() => {
if (let condition: true
condition) {
function confetti(opts?: ConfettiOptions): void
confetti({ ConfettiOptions.colors: string[]
colors: [let color: string
color] });
} else {
function confetti(opts?: ConfettiOptions): void
confetti();
}
});
$effect.pre
稀なケースでは、DOM 更新の 前に コードを実行する必要がある場合があります。そのためには $effect.pre
rune を使用できます:
<script>
import { tick } from 'svelte';
let div = $state();
let messages = $state([]);
// ...
$effect.pre(() => {
if (!div) return; // まだマウントされていない場合
// `messages` 配列の length が変わるたびにこのコードを再実行するため、ここで参照する
messages.length;
// 新しい message が追加されるたびに自動でスクロールする
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
rune は高度な機能で、コードが effect やテンプレートなどの tracking context の中でで実行されているかどうかを示します (デモ):
<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.root
$effect.root
rune は高度な機能で、自動クリーンアップされない非トラッキングスコープ (non-tracked scope) を作成します。これは、手動で制御したいネストされた effect に便利です。また、この rune は、コンポーネント初期化フェーズの外部で effect を作成することも可能にします。
const const destroy: () => void
destroy = 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.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:
<script>
let count = $state(0);
const cleanup = $effect.root(() => {
$effect(() => {
console.log(count);
})
return () => {
console.log('effect root cleanup');
}
});
</script>
<button onclick={() => cleanup()}>cleanup</button>
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.
$effect(() => {
// setup
});
return () => {
// cleanup
};
});
// later...
const destroy: () => void
destroy();
$effect を使うべきでないとき
一般的に、$effect
は脱出口 (エスケープハッチ) のようなものと考えるのが最善です。たとえば、アナリティクス や直接的な DOM 操作などに便利ですが、頻繁に使用するツールではありません。特に、state を同期するために使用するのは避けてください。このように使用するのではなく...
<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
を使用することもできます。
(例えば楽観的 UI を作るために) derived な値に再代入するために effect を使用しているなら、Svelte 5.25 からは derived を直接オーバーライドできる ようになったことにご留意ください。
effect を使ってある値を別の値に関連付けるような複雑な処理をしたくなるかもしれません。次の例は、「支出金額(money spent)」と「残高(money left)」という2つの入力が互いに連動していることを示しています。一方を更新すると、もう一方もそれに応じて更新されるべきです。このようなケースで effect を使用しないでください (デモ):
<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>
代わりに、可能な場合は oninput
コールバック か — better still — function bindings を使用してください (デモ):
<script>
let total = 100;
let spent = $state(0);
let left = $state(total);
function updateSpent(value) {
spent = value;
left = total - spent;
}
function updateLeft(value) {
left = value;
spent = total - left;
}
</script>
<label>
<input type="range" bind:value={() => spent, updateSpent} max={total} />
{spent}/{total} spent
</label>
<label>
<input type="range" bind:value={() => left, updateLeft} max={total} />
{left}/{total} left
</label>
どうしても effect 内で $state
を更新する必要があり、同じ $state
を読み書きすることで無限ループに陥った場合は、untrack を使用してください。