- Structure
- Imports
- Variables
- Reactivity
- Props
- Logic
- Events
- Bindings
- Lifecycle
- Store
- Motion
- Transitions
- Dynamic classes
- Slots
- Contexts
- Debugging
- Integrations
Your components are inside .svelte
files.
Your component use few tags to be operating:
<script>
<style>
<script>
will describe how to animate the component.
<style>
how to render it
You can include a component inside another one by importing it:
<script>
import Nested from './Nested.svelte'
</script>
<Nested/>
You can define variables in your <script>
tag and then use it with brackets:
<script>
let foo = "bar"
</script>
<p class={bar}>Some text</p>
Tip: It also works inside double-quotes:
<p class="primary-{bar}">Some text</p>
Moreover, when var = attribute, (src={src}
) you can use it this way: {src}
.
Reactivity is triggered by assignments What does it mean? Every change will be triggered after an assignment has been done.
More on that later.
Sometimes a variable is result from a computation, you'll have to use a reactive declaration:
let count = 0
$: doubled = count * 2
We're not limited to variable declaration, we can also use reactive statements:
$: console.log(`the count is ${count}`);
Here console.log
is called whenever count
value changes.
Notice the usage of ` (backquotes).
Tip: You can group more statements by using
$: { ... }
.
Because Svelte's reactivity is triggered by assignments following code won't do anything when called:
function addNumber() {
numbers.push(numbers.length + 1);
}
Unless you add it this assignment:
function addNumber() {
numbers.push(numbers.length + 1);
numbers = numbers;
}
There's an easy way to pass props to a child component. Just export
it !
// Nested.svelte
<script>
export let answer = 42
</script>
<p>The answer is {answer}</p>
// App.svelte
<script>
import Nested from './Nested.svelte'
</script>
<Nested/> // Will display: The answer is 42
Tip: You can also use
<Nested answer={42}/>
to set a default value to a prop that hasn't been initialized into it's own component.
When passing multiple props typically as an object of properties, you can spread them to a component instead of specifying each one with ...
:
<script>
import Info from './Info.svelte';
const pkg = {
name: 'svelte',
version: 3,
speed: 'blazing',
website: 'https://svelte.dev'
};
</script>
<Info name={pkg.name} version={pkg.version} speed={pkg.speed} website={pkg.website}/>
<Info {...pkg}/>
Assuming we have export
ed name
, version
, and so on in the Info
component.
To conditionally render some markup, we wrap it in an if block:
{#if user.loggedIn}
<button on:click={toggle}>
Log out
</button>
{/if}
{#if !user.loggedIn}
<button on:click={toggle}>
Log in
</button>
{/if}
You can also make use of :
{:else}
{:else if .. }
Expressed this way:
{#each cats as cat, i}
<li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}">
{i}: {cat.name}
</a></li>
{/each}
Tip: You can also de-structure as:
{#each cats as { id, name }}
and then use{name}
instead of{cat.name}
When a value isn't exported but is computed by a prop, you can go into problems when updating values since it's a copy and not a reference. Thus, be sure to identify the right object when updating its value(s):
{#each cats as cat, i (cat)} // Notice the (cat)
<li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}">
{i}: {cat.name}
</a></li>
{/each}
You can deal with promises and display at each of the promise lifecycle:
{#await promise}
<p>Loading...</p>
{:then result}
<p>It returns {result}</p>
{:catch error}
<p>An error occurred: {error.message}</p>
{/await}
Tip: You can also skip loading step by using:
{#await promise then result}
You can bind functions / events to your tags:
<script>
let count = 0;
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
You can use all javascript event (click, mousemove, ...).
You can also modify event by piping modifiers to it:
<button on:click|once={handleClick}>
You can chain them by adding pipes.
You can create an event dispatcher inside a component. It must be called when component is first instantiated.
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function sayHello() {
dispatch('message', {
text: 'Hello!'
});
}
</script>
On the other side:
<script>
import Inner from './Inner.svelte';
function handleMessage(event) {
alert(event.detail.text);
}
</script>
<Inner on:message={handleMessage}/> // on:eventname
You can handle events from any other component by calling them with their own event:
// Outer.svelte
<script>
import Inner from './Inner.svelte';
</script>
<Inner on:message/>
Here we can intercept event message
from Inner
.
Then, when called, we can define how the component reacts:
<Outer on:message={handleMessage}/>
Data flow is top down, that means a parent component can set props on a child component but not the other way around.
Bindings can break that rule:
<script>
let name = 'world';
</script>
<input bind:value={name}> // name and value are tied together, they are bind
<h1>Hello {name}!</h1>
You can also use a group of bindings:
<script>
let flavourList = [
'Cookies and cream',
'Mint choc chip',
'Raspberry ripple'
];
</script>
<h2>Flavours</h2>
{#each flavourList as flavour}
<label>
<input type=checkbox bind:group={flavourList} value={flavour}>
{flavour}
</label>
{/each}
Is ruled by a bunch of functions:
onMount
<script>
import { onMount } from 'svelte';
let photos = [];
onMount(async () => {
const res = await fetch(`https://jsonplaceholder.typicode.com/photos?_limit=20`);
photos = await res.json();
});
</script>
onDestroy
beforeUpdate
afterUpdate
A store is simply an object with a subscribe method that allows interested parties to be notified whenever the store value changes:
<script>
let countValue;
const unsubscribe = count.subscribe(value => {
count_value = value;
});
onDestroy(unsubscribe);
</script>
<h1>The count is {count_value}</h1>
Then, you can update the stored value:
<script>
function increment() {
count.update(n => n + 1)
}
</script>
Or set a value:
<script>
function reset() {
count.set(0);
}
</script>
Best practice: Auto-subscription
You can use auto-subscription to get rid of subscribe
and onDestroy
methods:
<script>
import { storeValue } from './AnotherComponent'
// ...other imports come after
</script>
<h1>The count is {$storeValue}</h1>
Warning: The store value must be imported or declared at the top-level scope of a component !
It's a read-only store.
<script>
export const time = readable(new Date(), function start(set) {
// Implementation
set()
return function stop() {
// Implementation
}
})
</script>
The first argument can be null, it represents an initial value.
You then have to define a set
callback which is called when the store gets its first subscriber and a stop
callback when the last subscriber unsubscribes.
<script>
import { writable } from 'svelte/store';
const count = writable(0, () => {
console.log('got a subscriber');
return () => console.log('no more subscribers');
});
</script>
It can be altered with set
and update
. It can also be bind
to a tag.
...from another store.
<script>
import { derived } from 'svelte/store';
const delayed = derived(time, ($a, set) => {
setTimeout(() => set($a), 1000);
}, 'one moment...');
// or (read-only derived):
const elapsed = derived(time, $time =>
Math.round(($time - start) / 1000)
);
</script>
First argument is the original store. Second one is facultative and added if you want to write on the store.
// store.js
import { writable } from 'svelte/store';
function createCount() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update(n => n + 1),
decrement: () => update(n => n - 1),
reset: () => set(0)
};
}
export const count = createCount();
In the component:
<script>
import { count } from './store.js';
</script>
<h1>The count is {$count}</h1>
<button on:click={count.increment}>+</button>
<button on:click={count.decrement}>-</button>
<button on:click={count.reset}>reset</button>
Stores are cool but you can make them even smoother by adding animation when transposed to UI.
It's usable like an ordinary store:
<script>
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
const progress = tweened(0, {
duration: 400,
easing: cubicOut
});
</script>
<progress value={$progress}></progress>
<button on:click="{() => progress.set(0)}">0%</button>
...works better with frequently changing value.
<script>
import { spring } from 'svelte/motion';
let coords = spring({ x: 50, y: 50 }, {
stiffness: 0.1,
damping: 0.25
});
let size = spring(10);
</script>
There're a lot:
- Fade in/out:
transition:fade
(ie:<p transition:fade>Some text</p>
) - Fly (disappear in a given direction):
transition:fly
- ...
To affect a class
to a tag dynamically when condition are met:
<button
class:active="{current === 'foo'}"
>
Text
</button>
<style>
.active {
background-color: #ff3e00;
color: white;
}
</style>
Here we affect the class .active
when condition is true.
Slots give you the opportunity to shape a component inside another one.
<script>
import Box from './Box.svelte';
</script>
<Box>
<h2>Hello!</h2>
<p>This is a box. It can contain anything.</p>
</Box>
// Box.svelte
<div class="box">
<slot>A default value here</slot>
</div>
Slots can be named:
<script>
import ContactCard from './ContactCard.svelte';
</script>
<ContactCard>
<span slot="name">T. Pellegatta</span>
</ContactCard>
// ContactCard.svelte
<article class="contact-card">
<h2>
<slot name="name">
<span class="missing">Unknown name</span> // Will display T. Pellegatta because it has been defined
</slot>
</h2>
<div class="address">
<slot name="address">
<span class="missing">Unknown address</span>
</slot>
</div>
</article>
You can pass props to slots:
<!-- App.svelte -->
<FancyList {items}>
<div slot="item" let:item={item}>{item.text}</div>
<p slot="footer">Copyright (c) 2019 Svelte Industries</p>
</FancyList>
<!-- FancyList.svelte -->
<ul>
{#each items as item}
<li class="fancy">
<slot name="item" item={item}></slot>
</li>
{/each}
</ul>
<slot name="footer"></slot>
The let:
directive goes on the element with the slot attribute.
Almost the same as stores but less reactive. Documentation
Instead of using debugger;
use {@debug variable}
...with Apollo
You'll have to import some plugins:
apollo-boost
graphql
svelte-apollo
Then, in the main component instantiate a client:
<script>
import ApolloClient from 'apollo-boost'
import { setClient } from 'svelte-apollo'
const client = new ApolloClient({
uri: 'http://site/graphqlApi/endpoint'
})
setClient(client)
</script>
In another one (or the same) use it to fetch/push data:
<script>
import { getClient, query } from 'svelte-apollo'
import { gql } from 'apollo-boost'
const GET_TODOS = gql`
{
getTodos {
id
text
}
}
`
const client = getClient()
const todos = query(client, { query: GET_TODOS })
</script>
{#await $todos}
Loading...
{:then result}
<p>Total todos: {result.data.getTodos.length}</p>
<ul>
{each result.data.getTodos as todo}
<li>{todo.text}</li>
{/each}
</ul>
{:catch error}
<p>{error.message}</p>
{/await}