Svelte
Source — thinking reactivity — https://www.youtube.com/watch?v=AdNJ3fydeao
Key points — Svelte calls itself as a compiler, It compiles the to vanilla js, and without the code which is not used by the program. Its also fast and obviously small code footprint.
Reactive programming —
Its essence is that at the time of declaration, we declare dynamic behavior of a value.
Eg — In a spread sheet , we define rules for each cell.If value of one cell changes, it may also change another value depending on the behavior rules.
Comparison with React
React introduced virtual DOM, however it does a lot of work to compare changes with the actual DOM.
We know that virtual DOM is not fast enough because it has to reload the method when any of the prop or state changes itself. It provides following to avoid too many reloads/faster reloads —
- Abstraction leaks like ShouldComponentUpdate, PureComponenet, useMemo etc
- Amortization like ConcurrentMode ( not sure what it means right now )
Triggering Updates in Svelte
In old svelete — it used similar syntax as React — this.set
( or this.setState
in React)
In newer versions of Svelte, it uses assignment operator =
(Vue.js
figured it out first) to know what value has changed.
In a simple svelte code of -
<script>
let name = ‘world’;
let count = 0;
</script><h1>Hello {name}</h1>
<input bind:value=”{name}” ><button on:click={() => count +=1 }>
Clicks: {count}
</button>
We see there are 2 updates happening with user actions —
- In the input text value
- In the count integer value
Svelte changes this code to —
/* App.svelte generated by Svelte v3.7.1 */
import {
SvelteComponent,
append,
detach,
element,
init,
insert,
listen,
noop,
run_all,
safe_not_equal,
set_data,
space,
text
} from “svelte/internal”;function create_fragment(ctx) {
var h1, t0, t1, t2, input, t3, button, t4, t5, dispose; return {
c() {
h1 = element(“h1”);
t0 = text(“Hello “);
t1 = text(ctx.name);
t2 = space();
input = element(“input”);
t3 = space();
button = element(“button”);
t4 = text(“Clicks: “);
t5 = text(ctx.count);
dispose = [
listen(input, “input”, ctx.input_input_handler),
listen(button, “click”, ctx.click_handler)
];
}, m(target, anchor) {
insert(target, h1, anchor);
append(h1, t0);
append(h1, t1);
insert(target, t2, anchor);
insert(target, input, anchor); input.value = ctx.name; insert(target, t3, anchor);
insert(target, button, anchor);
append(button, t4);
append(button, t5);
}, p(changed, ctx) {
if (changed.name) {
set_data(t1, ctx.name);
} if (changed.name && (input.value !== ctx.name)) input.value = ctx.name; if (changed.count) {
set_data(t5, ctx.count);
}
}, i: noop,
o: noop, d(detaching) {
if (detaching) {
detach(h1);
detach(t2);
detach(input);
detach(t3);
detach(button);
} run_all(dispose);
}
};
}function instance($$self, $$props, $$invalidate) {
let name = ‘world’;
let count = 0; function input_input_handler() {
name = this.value;
$$invalidate(‘name’, name);
} function click_handler() {
const $$result = count +=1;
$$invalidate(‘count’, count);
return $$result;
} return {
name,
count,
input_input_handler,
click_handler
};
}class App extends SvelteComponent {
constructor(options) {
super();
init(
this,
options,
instance,
create_fragment,
safe_not_equal,
[]
);
}
}export default App;
The above code is big, but it shows how seemingly simple functionality like binding an input text to a variable , and a click counter can be tricky.
On close inspection of the output code, we see that Svelte creates a method called instance
which leaves the code nearly the same, just adding $$invalidate
method which will invalidate the result when the value changes, and also at the end of event loop.
Making Svelte Reactive
React is not really reactive because it reruns the method on every prop/state change. However, to make Svelte reactive, we need to have values / variables bound to another.
let a = 10;
$: b = a + 5;
here, b is defined as a bound variable.
Comparing React to Svelte in a simple ToDo example —
<script>
let todos =[
{done: false, text: 'eat'},
{done: false, text: 'sleep'},
{done: false, text: 'code'},
{done: false, text: 'repeat'},
];
function toggleDone(t) {
todos = todos.map(todo => {
if (todo == t) {
return {done: true, text:t.text}
}
return todo;
})
}
let hideDone = false;
$: filtered = hideDone ?
todos.filter(todo => !todo.done):
todos$: showing = filtered.length;
</script><label>
<input type="checkbox" bind:checked={hideDone}/>
hide done
</label><p>
Showing {showing} out of {todos.length}
</p>
<ul>
{#each filtered as todo}
<li on:click={()=>toggleDone(todo)}>
{todo.done} {todo.text}
</li>
{/each}
</ul>
We can see that the bound variables ( filtered
and showing
) are changed to the following code in Svelte —
$$self.$$.update = ($$dirty = { hideDone: 1, todos: 1, filtered: 1 }) => {
if ($$dirty.hideDone || $$dirty.todos) { $$invalidate('filtered', filtered = hideDone ?
todos.filter(todo => !todo.done):
todos) }
if ($$dirty.filtered) { $$invalidate('showing', showing = filtered.length); }
};
Which means, Svelte understands dependency graph and works by first changing the depending variable before changing the dependent variable.