Svelte
In Svelte you build apps declaratively out of components that combine combine markup, styles and behaviors. Components are then compiled into small efficient JavaScript modules.
The basic syntax features of a Svelte component are:
- You can insert JavaScript variables into HTML with curly braces.
- You can set HTML attributes with these curly braces, and if your JavaScript variable happens to have the same name as the attribute you're setting, there's a nice shorthand syntax where you can just write:
<img {alt} />
instead of<img alt={alt} />
. - Styles (set in
<style>
tags) are scoped to components. - Components can be imported (in a
<script>
tag) likeimport Comp from './path/to/Comp.svelte';
. - You can (dangerously) render HTML directly into a component from JavaScript with the special
{@html '...'}
tag.
Here's an that example uses all of these features:
// Nested.svelte
<p>This is a another paragraph.</p>
// App.svelte
<script>
import Nested from './Nested.svelte';
let name = 'Sam';
let src = '/image.png';
let greenParagraph = '<p style="color: green;">Set my paragraph.</p>'
</script>
<style>
p {
color: red;
}
</ style>
<h1>Hi {name.toUpperCase()}</h1>
<img {src} alt="{name} dances." />
<p>This is a paragraph.</p> // This will have red font
<Nested /> // This will have black font
{@html greenParagraph} // This will have green font
Reactivity
Svelte has another special syntax for a feature called reactive declarations, that allows you to tell the Svelte compiler to re-run declarations or statements whenever any variable referenced in the reactive declaration changes. The syntax for it prefaces your delclaration or statement with a $:
symbol, which is actually valid JavaScript ($
is a valid identifier and the colon is part of a labeled statement).
Reactivity is triggered by assignment though, so for reactive declarations that depend on arrays or objects, to trigger the reactivity, you need to reassign the variable completely instead of using a method that modifies it in-place (like slice
or push
):
<script>
let count = 0;
$: doubled = count * 2;
$: console.log('count is ' + count);
$: if (count >= 10) {
console.error('count is crazy, crazy high');
count = 0;
}
const increment = () => count += 1;
let numbers = [1, 2, 3, 4];
$: sum = numbers.reduce((total, curr) => total + curr, 0);
const addNumber = () => numbers = [...numbers, numbers.length + 1];
</script>
<button on:click={increment}>
Clicked {count}
{count === 1 ? 'time' : 'times'}
</button>
<p>
{count} doubled is {doubled}.
</p>
<button on:click={addNumber}>
Add a number man
</button>
<p>
The sum {numbers.join(' + ')} is {sum}.
</p>
Props
Props are for passing data from parent to child. You declare props in the receiving (child) component using the (confusing) syntax: export let prop;
. Like function parameters you can have default values, and there's a special spread syntax for spreading an object of props if the properties are the same as the prop names:
// DataCard.svelte
<script>
export let name = 'Defaulto';
export let version;
$: href = 'https://npmjs.com/package/' + name + '/' + version;
</script>
<div>
The name is {name}. Here's a link to it: {link}.
</div>
// App.svelte
<script>
import DataCard from './DataCard.svelte';
const props = {
name: 'Crapoid',
version: 13,
};
</script>
// All valid:
<DataCard name="Crapoid2" version="24" />
<DataCard />
<DataCard {...props} />
Logic
Svelte has lots of syntax for expressing logic in HTML, mainly special {#if}
, {:else}
, {#each}
and {#await}
tags.
<script>
let count = 0;
let colors = ['red', 'green', 'blue'];
let promise = fetch('/json');
</script>
{#if count < 10}
<p>Count is defo less than ten.</p>
{/if}
{#if count < 5}
<p>Actually, it's like in the zero to five range.</p>
{:else}
<p>Actually, it's like in the six to ten range.</p>
{/if}
{#if count > 8}
<p>It's so much greater than eight.</p>
{:else if count < 8 and count > 5}
<p>It's giving five to eight</p>
{:else}
<p>Yeah, it's less than five alright.</p>
{/if}
{#each colors as color}
<div style="background-color: {color}">{color}</div>
{/each}
{#each colors as color, i}
<div style="background-color: {color}">{i}</div>
{/each}
{#await promise}
<p>Waiting...</p>
{:then fetchedJson}
<p>Cool. The JSON is {JSON.stringify(fetchedJson)}</p>
{:catch}
<p>Dang man, it messed up. Idk man...</p>
{/await}
There is a thing where you will need to pass a key to an {#each}
block like {#each colors as color {color.id}}
for Svelte to update the children of the {#each}
block properly.
Events
Svelte also has some special syntax for declaring event handlers:
- Instead of setting an
onclick="..."
attribute, you pass a function to anon
attribute:on:click={handler}
. - Setting event handlers inline isn't frowned upon.
- Event handlers can have modifiers, which can be strung together, and are shorthand ways of indicating things like:
once
: call this handler once and then remove it.preventDefault
: callevent.preventDefault()
before running the handler.
<script>
const sayHowdy = () => console.log('Howdy.');
</script>
<button on:click={() => console.log('Hey.')}>
Say hey
</button>
<button on:click|once={sayHowdy}>
Say howdy once
</button>
Components can fire special component events using a event dispatcher, which can be caught by a handler set in a parent. The object passed in the call to dispatch
is stored at the .detail
property of the event. Component events don't automatically bubble, so to catch one a few layers of components deep, the intermediate components must forward the event (although there is shorthand syntax for forwarding events of a certain type). This syntax also works for plain old DOM events.
// Inner.svelte
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
const sayHello = () => dispatch(
'message',
{
text: 'Hello!'
}
);
</script>
<button on:click={sayHello}>Say hello</button>
// Outer.svelte
<script>
import Inner from './Inner.svelte';
</script>
// This 'on:message' attribute is the shorthand for forwarding 'message' events
<Inner on:message />
// This DOM event gets forwarded up to App
<button on:click>Handle this click in App</button>
// App.svelte
<script>
import Outer from './Outer.svelte';
</script>
<Outer
on:message={event => console.log(event.detail.text)}
on:click={event => console.log('This is a normal DOM event', event)}
/>
Bindings
Say you set an <input>
's value with value={variable}
. When the user changes the value of the input, your variable
doesn't get updated unless you register an on:input
handler to update your variable
-- Svelte has a way to avoid this boilerplate. You can keep the value
of your <input>
bound to your variable
with a bind, which you set with
<input bind:value={variable}>
Changes to your variable
will also be automatically reflected in your <input>
, too. If you bind
to an <input>
with type="number"
, Svelte will automatically convert the bound variable to a number for you. For checkboxes, remember to bind to checked
, not value
. Binding works for <select>
elements.
For radio buttons, you can bind:group={selectedRadioButtonValue}
, so selectedRadioButtonValue
will be synchronized with the value bound to the currenly selected radio button. Similarly for checkboxes, bind:group
will update the bound array with the values bound to each currently checked inputs. <select multiple ... >
will populate an array. You can omit a value
for the <option>
s if the elements' contents match what the value would have been otherwise.
Finally, if the name of your variable matches the attribute you're binding too, there's a shorthand syntax: bind:value
(where value
is the attribute you're binding to).
Lifecycle
Components all have a lifecycle that starts when they're created and ends when they're destroyed. You can register a callback to be called when the component is created with onMount
, and your callback can return another callback to to be called when the component is destroyed.
beforeUpdate
and afterUpdate
register callbacks to be called (respectively) right before and after the DOM gets updated.
The tick
function returns a promise that resolves as soon as any pending state changes are applied to the DOM. Updating component state doesn't happen immeadiately, instead Svelte waits for the next microtask to see if there are any other state changes that need to be applied. Foing it this way allows the browser to batch things more effectively and avoid duplicate work.