In Svelte 4, reactivity centres on the component and the top-level state declared therein. What this means is that in a situation like this...
<script>
let todos = [];
function remaining(todos) {
console.log('recalculating');
return todos.filter((todo) => !todo.done).length;
}
function addTodo(event) {
if (event.key !== 'Enter') return;
todos = [
...todos,
{
done: false,
text: event.target.value
}
];
event.target.value = '';
}
</script>
<input onkeydown={addTodo} />
{#each todos as todo}
<div>
<input bind:value={todo.text} />
<input type="checkbox" bind:checked={todo.done} />
</div>
{/each}
<p>{remaining(todos)} remaining</p>
...editing any individual todo
will invalidate the entire list. You can see this for yourself by opening the playground, adding some todos, and watching the console in the bottom right. remaining(todos)
is recalculated every time we edit the text
of a todo, even though it can't possibly affect the result.
Worse, everything inside the each
block needs to be checked for updates. When a list gets large enough, this behaviour has the potential to cause performance headaches.
With runes, it's easy to make reactivity fine-grained, meaning that things will only update when they need to:
<script>
let todos = [];
let todos = $state([]);
function remaining(todos) {
console.log('recalculating');
return todos.filter(todo => !todo.done).length;
}
function addTodo(event) {
if (event.key !== 'Enter') return;
todos = [
...todos,
{
done: false,
text: event.target.value
}
];
todos.push({
done: false,
text: event.target.value
});
event.target.value = '';
}
</script>
In this version of the app, editing the text
of a todo won't cause unrelated things to be updated.