This is a note as I wrote down as I was going through Svelte tutorial.
Might be of helpful for some but foremost, this is a note for myself :)
1. Introduction
Creating a new Svelte project
https://svelte.dev/blog/svelte-for-new-developers
npx degit sveltejs/template new-project-name
VS Code
Install following extensions
2. Reactivity
a. Assignments
https://svelte.dev/tutorial/reactive-assignments
on:click
on
looks like a directive and click
is the event name.
States are reactive, closure under script
tag re-renders whenever the state value changes.
<script>
let count = 0;
function handleClick() {
count++;
}
</script>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
b. Declarations
https://svelte.dev/tutorial/reactive-declarations
Computed/derived states need to be declared using a special syntax, $:
.
let count = 0;
$: doubled = count * 2;
Useful when it needs to be access multiple times.
Instead of {count * 2}
everywhere, you can use {doubled}
instead.
c. Statements
https://svelte.dev/tutorial/reactive-statements
$:
isn't limited to expressions (reactive values) but also to statements.
<script>
let count = 0;
$: console.log(`the count is {count}`);
$: if (count >= 10) {
alert(`count is too high!`)
count = 9;
}
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
d. Updating arrays and objects
https://svelte.dev/tutorial/updating-arrays-and-objects
A simple rule of thumb: the name of the updated variable must appear on the left hand side of the assignment.
Or assign a new reference like you do in React.
// Instead of this
function addNumber() {
numbers.push(numbers.length + 1);
numbers = numbers
}
// Do this
function addNumber() {
numbers = [...numbers, numbers.length + 1]
}
3. Props
a. Declaring props
https://svelte.dev/tutorial/declaring-props
For passing data to another component(s). Same concept as it does in React.
In React, components receive props
but in Svelte, you export a variable.
Nested.svelte
<script>
export let answer;
</script>
<p>The answer is {answer}</p>
App.svelte
imports Nested
component and passes the answer like following.
<script>
import Nested from './Nested.svelte'
</script>
<Nested answer={42}>
b. Default values
https://svelte.dev/tutorial/default-values
You can set the default prop value during declaration
Nested.svelte
<script>
export let answer = 'is unknown!';
</script>
<p>The answer is {answer}</p>
If no props passed to Nested
like <Nested>
, then the default value is used.
c. Spread props
https://svelte.dev/tutorial/spread-props
As you can do in React, you can pass multiple props with object spread operator.
<Info {...pkg}>
4. Logic
a. If blocks
https://svelte.dev/tutorial/if-blocks
The markup is avaiable in Svelte only, not in HTML.
{#if user.loggedIn}
<button on:click={toggle}>Log Out</button>
{/if}
{#if !user.loggedIn}
<button on:click={toggle}>Log In</button>
{/if}
b. Else blocks
https://svelte.dev/tutorial/else-blocks
Mutually exclusive condition can use {:else}
block
{#if user.loggedIn}
<button on:click={toggle}>Log Out</button>
{:else}
<button on:click={toggle}>Log In</button>
{/if}
c. Else-if blocks
https://svelte.dev/tutorial/else-if-blocks
Additional condition can be checked with {:else if condition}
{#if x > 10}
<p>{x} is greater than 10!</p>
{:else if x < 5 }
<p>{x} is less than 5
{:else}
<p>{x} is 'teween 5 and 10</p>
{/if}
d. Each blocks
https://svelte.dev/tutorial/each-blocks
You can iterate an iterable object with {#each iterable as alias, index}
<ul>
{#each cats as cat, index}
<li>{index + 1}th cat is {cat.name}</li>
{/each}
</ul>
The alias can be destructured like
{#each cats as {name, id, age}, index}
<li>{index + 1}th cat is {name} and is {age} years old</li>
{/each}
e. Keyed each blocks
https://svelte.dev/tutorial/keyed-each-blocks
In React, creating an iterable element requires key
per each element.
e.g.)
{things.map(thing => <li key={thing.id}>{thing.color}</li>)}
In Svelte, you specify the key in the markup.
{#each things as thing (thing.id)}
<li>{thing.color}</li>
{/each}
Or you can destructure thing
{#each things as {id, color} (id)}
<Thing current={color}/>
{/each}
f. Await blocks
https://svelte.dev/tutorial/await-blocks
Svelte markup has a way to await
promises.
Race condition is handled automatically because Svelte only grabs the latest/most recent promise only.
{#await promise}
<p>Loading...</p>
{:then number}
<p>The value is {number}<p>
{:catch error}
<p class="error">{error.message}</p>
{/await}
You can decide not to show the intermediate "loading" message and wait 'til the promise resolves.
{#await promise then number}
<p>The value is {number}<p>
{/await}
This is much cleaner abstraction than in React, in which one needs to use useEffect
to resolve promise in an async method and set the state.
5. Events
a. DOM events
https://svelte.dev/tutorial/dom-events
Use on:
directive, followed by DOM event name.
e.g.) mousemove
<script>
let m = { x: 0, y: 0 };
function handleMousemove(event) {
m.x = event.clientX;
m.y = event.clientY;
}
</script>
<style>
div { width: 100%; height: 100%; }
</style>
<div on:mousemove={handleMousemove}>
The mouse position is {m.x} x {m.y}
</div>
b. Inline handlers
https://svelte.dev/tutorial/inline-handlers
โ Inline event handlers does not cause any performance issues unlike in React, as Svelte knows how to optimize.
Instead of,
<div on:mousemove={handleMousemove}>
The mouse position is {m.x} x {m.y}
</div>
You can inline handleMousemove
as shown below.
<div on:mousemove={e => m = {x: e.clientX, y: e.clientY}}>
The mouse position is {m.x} x {m.y}
</div>
Or, wrap the inline method in quotes for syntax highlighting in some editors
<div on:mousemove="{e => m = {x: e.clientX, y: e.clientY}}">
The mouse position is {m.x} x {m.y}
</div>
c. Event modifiers
https://svelte.dev/tutorial/event-modifiers
You can "decorate" (my intepretaion) event with modifiers such as
-
once
: run the handler once -
prevetnDefault
:event.preventDefault()
before calling the handler -
stopPropagation
:event.stopPropagation()
to stop the event bubble/capture -
passive
: for touch/wheel scrolling performance (Google added it as a non-standard but it's supported widely) -
capture
: DOM events "bubble-up" by default. This reverses it ascapture
(Refer to MDN Event.eventPhase) -
self
:event.target === current element
.
e.g.) using once
to run an event handler only once on a button
<button on:click|once={handleClick}>Click me</button>
Modifiers are chainable. on:click|once|capture|preventDefault
handleClick
will be called once once no matter how many times you press the button.
โ Space is significant! The code below is not valid as there are spaces between |
.
<button on:click | once={handleClick}>Click me</button>
d. Component events
https://svelte.dev/tutorial/component-events
Unlike custom event dispatch in vanilla JS, where you pass custom data as detail
property,
// add an appropriate event listener
obj.addEventListener("cat", function(e) { process(e.detail) });
// create and dispatch the event
let event = new CustomEvent("cat", {
๐
detail: {
hazcheeseburger: true
}
});
obj.dispatchEvent(event);
you dispatch an event with data and it will be available as part of event.detail
automatically.
Inner.svelte
<script>
import {createEventDispatcher} from 'svelte'
const dispatch = createEventDispatcher()
function sayHello() {
// NOT THIS!
// dispatch('message', {detail: {text: 'hi!'}})
// But pass the data as it is
dispatch('message', { text: 'Hello!' });
}
</script>
<button on:click={sayHello}>
Click to say hello
</button>
You can then use the component and subscribe to the event, message
like following.
App.svelte
<script>
import Inner from './Inner.svelte';
function handleMessage(event) {
// Access "text" via ๐ event.detail
alert(event.detail.text);
}
</script>
<Inner on:message={handleMessage}/>
This pattern is different from React where an inner component receives an "event handler" as a function and calls it, not declare an event.
const App = () => <Inner onMessage={handleMessage}>
const Inner = ({onMessage}) => <button onClick={onMessage}>Click</button>
So it seems that in Svelte, event handlers are declared using vanilla JavaScript's CustomEvent interface.
e. Event forwarding
https://svelte.dev/tutorial/event-forwarding
DOM events are bubbled up while Svelte events aren't. Explicit event forwarding can be done by creating event dispatcher in each level.
Svelte can forward events with a shortcut where you specify the on:eventname
directive without a value.
<Inner on:message>
Then all on:message
event handlers will be forwarded up and made available to the calling component.
Note: this is tough to grasp. Need to come back later.
f. DOM event forwarding
https://svelte.dev/tutorial/dom-event-forwarding
Svelte requires you explicitly decide whether to expose an event or not.
When there is more than one element in inner component exposing the same event, say two buttons with on:click
,
CustomButton.svelte
<button id="b1" on:click>
Click me
</button>
<button id="b2" on:click>
Click me2
</button>
Then you can tell which one was fired by examining event.target
App.svelte
<script>
import CustomButton from './CustomButton.svelte'
function handleClick(event) {
console.log(`e =>`, event.target)
}
</script>
<CustomButton on:click={handleClick}> />
CustomButton click on #b1
and #b2
results in,
e => <button id=โ"b1">โClick meโ</button>โ
e => <button id=โ"b2">โClick me2โ</button>โ
6. Bindings
a. Text inputs
https://svelte.dev/tutorial/text-inputs
Sorta like a two-way binding, where changes in an element updates the state and the current state.
<script>
let name = 'world!'
</script>
<input bind:value={name}>
<h1>Hello {name}!</h1>
Updating values in input
will update name
state as well as the input's value.
b. Numeric inputs
https://svelte.dev/tutorial/numeric-inputs
batteries included
Svelte auto converts input of type number
& range
to numeric values.
React requires explicit conversion as it's metal.
c. Checkbox inputs
https://svelte.dev/tutorial/checkbox-inputs
Checkbox input type value is bound to bind:checked
instead of bind:value
.
<script>
let isChecked = false
</script>
<input type="checkbox" bind:checked={isChecked}>
d. Group inputs
https://svelte.dev/tutorial/group-inputs
In vanilla JS, you use name
to group related checkboxes and radio.
MDN Reference: <input type="radio">
<form>
๐
<input type="radio" name="scoops" value="1">
<input type="radio" name="scoops" value="2">
<input type="radio" name="scoops" value="3">
</form>
but in Svelte, you bind a group using bind:group
directive.
<form>
๐
<input type="radio" bind:group="scoops" value="1">
<input type="radio" bind:group="scoops" value="2">
<input type="radio" bind:group="scoops" value="3">
</form>
When bound to a radio group, then the bound value is one value, but on checkboxes, the bound value is an array.
<script>
let scoops = 1;
let flavours = [];
</script>
<!-- Radio `scopes` bound to a single value -->
<label>
<input type=radio bind:group={scoops} value={1}>
One scoop
</label>
<label>
<input type=radio bind:group={scoops} value={2}>
Two scoops
</label>
<label>
<input type=radio bind:group={scoops} value={3}>
Three scoops
</label>
<!-- Checkbox group value, `favlours` is an array -->
<label>
<input type=checkbox bind:group={flavours} value="Cookies and cream">
Cookies and cream
</label>
<label>
<input type=checkbox bind:group={flavours} value="Mint choc chip">
Mint choc chip
</label>
<label>
<input type=checkbox bind:group={flavours} value="Raspberry ripple">
Raspberry ripple
</label>
e. Textarea inputs
https://svelte.dev/tutorial/textarea-inputs
Same as <input type="text">
. You bind value using bind:value={value}
. If the value variable name matches value
, then you can leave out the assignment, like,
<textarea bind:value></textarea>
f. Select bindings
https://svelte.dev/tutorial/select-bindings
Like Textarea, you can use bind:value={value}
and leave out the assignment, bind:value
if the variable name is value
.
<script>
let value;
let answer = ""
const questions = [
{id: 1, 'question #1'},
{id: 2, 'question #2'},
{id: 3, 'question #3'},
]
</script>
<!-- this works too ๐ -->
<!-- <select bind:value={value} on:change="{() => answer = ""}"> -->
<select bind:value on:change="{() => answer = ""}">
{#each questions as question}
<option value={question}>{question.text}</option>
{/each}
</select>
<input bind:value={answer}>
g. Select multiple
https://svelte.dev/tutorial/multiple-select-bindings
I've already mentioned this in d. Group inputs
- https://svelte.dev/tutorial/group-inputs
Binding to a select with multiple
directive sets the value to an array.
flavours
is an array.
<select multiple bind:value={flavours}>
{#each menu as flavour}
<option value={flavour}>
{flavour}
</option>
{/each}
</select>
h. Contenteditable bindings
https://svelte.dev/tutorial/contenteditable-bindings
You can bind to either textContent
or innerHTML
<div
contenteditable="true"
bind:innerHTML={html}
></div>
<!-- or -->
<div
contenteditable="true"
bind:textContent={html}
></div>
Check out the difference between
textContent
&innerHTML
& why one should consider usingtextContent
overinnerHTML
.
i. Each block bindings
https://svelte.dev/tutorial/each-block-bindings
Don't use this if you plan to go with immutable data (React style).
Familiar with imperative style? go with this.
j. Medial elements
https://svelte.dev/tutorial/media-elements
Media elements' (video/audio
) are updated more frequently using requestAnimationFrame
.
k. Dimensions
https://svelte.dev/tutorial/dimensions
Every block-level elements, such as div
, section
, article
, etc have bindings to following props.
l. This
https://svelte.dev/tutorial/bind-this
bind:this={variable}
returns a reference to rendered elements.
variable
will be undefined
until the component has mounted.
Use onMount lifecycle to refer to the variable.
Note: This looks like ref
in React.
m. Component bindings
https://svelte.dev/tutorial/component-bindings
As mentioned previously, you can bind:value
for custom components to provide a two-way binding.
Changes in child component will be available in the parent element.
Keypad.svelte
<script>
export let value;
</script>
...
Suppose that in App.svelte
,
<script>
import Keypad from './Keypad.svelte'
let pin;
const handleSubmit = () => console.log(`pin => ${pin}`)
</script>
<input bind:value={pin} />
<Keypad bind:value={pin} on:submit={handleSubmit}>
You can bind to Keypad
with bind:value={pin}
. It works as both an input and output to Keypad
component.
It can be demo'ed by changing values in <input bind:value={pin} />
.
Awesome! Very convinient. But you have to be careful as you can lose track of the state flow.
In React, one would have to pass a callback function to call it whenever child value changes and the parent would update the state via the callback.
App.jsx
function App() {
const [pin, setPin] = React.useState(null)
return <Keypad onChange={setPin} />
}
7. Lifecycle
a. onMount
https://svelte.dev/tutorial/onmount
It's comparable to the mix of componentDidMount and useEffect because it's called when a component is mounted, and cleans up with a callback function returned from it (that's how useEffect does a clean up).
And also, componentDidMount
can be async
and useEffect
cannot call an async method.
As it's the recommended way to call fetch
in React, onMount
is normally where one should make a network request.
<script>
import { onMount } from 'svelte';
onMount(async () => {
const response = await fetch('https://www...');
photos = await response.json();
return () => {
// clean up resources here
};
});
</script>
b. onDestroy
https://svelte.dev/tutorial/ondestroy
onDestroy
is like React's componentWillUnmount. Use it clean up resources on component's unmount phase.
<script>
import { onDestroy } from 'svelte'
let seconds = 1;
const id = setInterval(() => seconds++, 1000)
onDestroy(() => void clearInterval(id))
</script>
c. beforeUpdate and afterUpdate
https://svelte.dev/tutorial/update
Flows like,
beforeUpdate
-> onMount
-> beforeUpdate
-> state changes -> afterUpdate
-> onDestroy
As beforeUpdate
runs BEFORE onMount
, one needs to check for existence of elements
d. tick
https://svelte.dev/tutorial/tick
To get around batch processing (state updates, DOM updates, etc)
<script>
import { tick } from 'svelte'
</script>
8. Stores
a. Writable stores
https://svelte.dev/tutorial/writable-stores
Svelte has batteries included. It comes with a global state management library.
svelte/store
has writable
method to create a global state.
store.js
import { writable } from 'svelte/store'
export const count = writable(0)
Then one can import count
in store.js
, either to read, update, or set the value.
- Reading via subscription
-
writable
returns a state, which you cansubscribe()
for the value change - It is a HoF (higher-order function), which returns a function to unsubscribe - It's the same as how Redux store's subscribe returns unsubscribe method - My guess is that you need to callunsubscribe
inonDestroy
normally to clean up
<script>
import { onDestroy } from 'svelte'
import { count } from './store'
let countValue;
const unsubscribe = count.subscribe(value => { countValue = value });
// Clean up after your business!
onDestroy(unsubscribe);
</script>
- Updating the state
-
writable
returns a state, which you canupdate
values for - It requires a callback, which is given the current value to update with
<script>
import { count } from './store.js'
const incrementCount = () => count.update(currentValue => currentValue + 1)
</script>
<button on:click={incrementCount}>Increment Count by One/button>
- Setting the state (convinience method for update)
-
set
method looks like a convinience method toupdate
- as you can simply set a value without a callback function
<script>
import { count } from './store.js'
const reset = () => count.set(0)
</script>
<button on:click={reset}>Reset Count</button>
b. Auto-subscriptions
https://svelte.dev/tutorial/auto-subscriptions
Svelte has yet another convinient way to subscribe to the global state change.
With $
prefixed to a variable, Svelte takes care of both un/subscription out of the box.
Instead of this verbose un/subscribe for count
,
<script>
import { onDestroy } from 'svelte'
import { count } from './store'
let countValue;
const unsubscribe = count.subscribe(value => { countValue = value });
// Clean up after your business!
onDestroy(unsubscribe);
</script>
<p>Count value is {countValue}</p>
You can simply prefix count
with $
like $count
.
<script>
import { onDestroy } from 'svelte'
import { count } from './store'
</script>
<p>Count value is {$count}</p>
Make sure to read notes in the linked page.
c. Readable stores
https://svelte.dev/tutorial/readable-stores
Readable store provides, duh, read-only store, for which one can initialize but can't update.
It looks similar to useEffect
that the returned function is called when "the last subscriber unsubscribes".
store.js
import { readable } from 'svelte';
const initialValue = new Date();
const valueUpdator = set => {
const id = setInterval(() => set(new Date()), 1000);
// called when the last subscriber unsubscribes.
return () => clearInterval(id);
}
export const time = readable(initialValue, valueUpdator);
And the same as wriable
store, you can refer to it with $
prefix, like $time
in another file.
d. Derived stores
The tutorial prefixes time
with $
like $time
in the callback.
Auto-subscriptions tutorial states that
Any name beginning with $ is assumed to refer to a store value. It's effectively a reserved character โ Svelte will prevent you from declaring your own variables with a $ prefix.
But I tried it without $
prefix as shown below but still works.
export const elapsed = derived(
time,
t => Math.round((t - start) / 1000)
);
Not sure if $
is required. Left a question on Reddit.
https://www.reddit.com/r/sveltejs/comments/hblmxa/question_derived_callback_in_tutorial_uses_a/
e. Custom stores
https://svelte.dev/tutorial/custom-stores
One can create a custom store by implementing subscribe
method.
Tutorial uses wriable
's subscribe
to expose the interface and doesn't show how to implement one yourself.
f. Store bindings
https://svelte.dev/tutorial/store-bindings
Store value referred to with $
prefix can be bound as if it's a local state.
<script>
import { name } from './store.js';
</script>
<input bind:value={$name}>
Typing in the input will update $name
and will trigger update itself and all dependents.
9. Motion
a. Tweened
https://svelte.dev/tutorial/tweened
Svelte has a built-in motion library without having to install a 3rd party library.
In React, you'd use react-spring
, or react-motion
, etc.
b. Spring
https://svelte.dev/tutorial/spring
Use this instead of tweened
for frequently changing values
10. Transitions
a. The transition directive
https://svelte.dev/tutorial/transition
Another batteries-included way to provide transition in JavaScript.
According to Chrome Devtools, <p transition:fade>
injects an inline style to fade in/out.
<script>
import { fade } from 'svelte/transition';
let visible = true;
</script>
{#if visible}
<p transition:fade>Fade in and out</p>
{/if}
b. Adding parameters
https://svelte.dev/tutorial/adding-parameters-to-transitions
You can also pass in-line parameters to transition functions in the markup.
<script>
import { fly } from 'svelte/transition';
let visible = true;
</script>
<input type=checkbox bind:checked={visible}>
{#if visible}
<p transition:fly="{{ y: 200, duration: 2000 }}">Flies in and out</p>
{/if}
Transitions are "reversible".
Toggling visibility doesn't abruptly starts transition from beinging or the end.
It reverses where it left off.
Refer to the linked tutorial page to see it in action! Cool stuff.
c. In and out
https://svelte.dev/tutorial/in-and-out
You can granularly contorl transition with in
& out
directives instead of transition
.
d. Custom CSS transitions
https://svelte.dev/tutorial/custom-css-transitions
Looks simple so long as you undersand CSS transition and motions etc.
I know neither well so it's tough.
To learn first: Using CSS transitions on MDN.
e. Custom JS transitions
https://svelte.dev/tutorial/custom-js-transitions
Use tick
callback to implement JS transitions for effects not possible by CSS transitions.
f. Transition events
https://svelte.dev/tutorial/transition-events
Monitor transition
directive events with following directives
on:introstart
on:outrostart
on:introend
on:outroend
g. Local transitions
https://svelte.dev/tutorial/local-transitions
local
transition makes transitions to occur on individual elements, not for a group of items.
Honestly, I really haven't found a use for this.
h. Deferred transitions
https://svelte.dev/tutorial/deferred-transitions
More advanced transition concept I'd have to learn later.
11. Animations
a. The animate directive
https://svelte.dev/tutorial/animate
Oh boy. come back later...
12. Actions
a. The use directive
https://svelte.dev/tutorial/actions
Use use:
directive to specify the action.
<script>
import { pannable } from './pannable.js';
</script>
<div use:pannable></div>
pannable
is a function, which accepts a DOM node.
// Fires following custom events
// 1. panstart
// 2. panmove
// 3. panend
export function pannable(node) {}
When the pannable
dispatches a custom event, the parent component can subscribe to it in the markup.
<script>
import { pannable } from './pannable.js';
// These functions have access to `event` dispatched from `pannable`
const handlePanStart = event => {}
const handlePanMove = event => {}
const handlePanEnd = event => {}
</script>
<div
use:pannable
on:panstart={handlePanStart}
on:panmove={handlePanMove}
on:panend={handlePanEnd}
style="transform:
translate({$coords.x}px,{$coords.y}px)
rotate({$coords.x * 0.2}deg)"
></div>
Clean up of the action can be done by exposing onDestroy
.
export function pannable(node) {
return {
onDesotry() {
// clean up the mess
}
}
}
b. Adding parameters
https://svelte.dev/tutorial/adding-parameters-to-actions
Just like transitions, actions can accept arguments.
<script>
import { longpress } from './longpress.js';
</script>
<div use:longpress={duration}></div>
When the duration is changed, longpress.js
won't know that the duration
has changed.
To subscribe to the duration
change, implement update
function in the action
longpress.js
export function longpress(node, duration) {
return {
update(newDuration) {
duration = newDuration
}
}
}
Multiple arguments can be passed to the action as an object
<script>
import { longpress } from './longpress.js';
</script>
<div use:longpress={{duration, spiciness}}></div>
and accept the object in the action.
longpress.js
export function longpress(node, { duration, spiciness }) {}
13. Classes
a. The class directive
https://svelte.dev/tutorial/classes
Svelt provides a shortcut for class toggle.
It's just
class
in Svelte notclassName
as it is in React.
<script>let current = 'foo';</script>
<style>
.someActiveClass {
background-color: red;
color: white
}
</style>
<button
class:someActiveClass="{current='foo'}"
on:click="{() => current = 'foo'}">
>foo</button>
<button
class:someActiveClass="{current='bar'}"
on:click="{() => current = 'bar'}">
>bar</button>
<button
class:someActiveClass="{current='baz'}"
on:click="{() => current = 'baz'}">
>baz</button>
Whenever the condition matches, the custom class append after class:
is added.
b. Shorthand class directive
https://svelte.dev/tutorial/class-shorthand
The shorthand for the shortcut (whew, what a mouthful) is that you can leave out the directive assignment if the class to toggle matches the variable name.
<div class:big={big}></div>
can be shortened to
<div class:big></div>
14. Component composition
a. Slots
https://svelte.dev/tutorial/slots
This is just like React's children
to specify where to put child components in the current one.
Svelte component is not a function, but more like a markup w/ scripts and styles.
So to access children, you need to specify <slot></slot>
or <slot />
.
You can specify multiple <slot />
, which will show the children multiple times.
box.svelte
<style>
.box {}
</style>
<div class="box">
<slot></slot>
<!-- or -->
<slot />
</div>
And pass the children to the box component.
<script>
import Box from './box.svelte';
</script>
<Box>
<h1>Here is the child header</h1>
<p> this is the content <p>
</Box>
Personal note: This is more to how React should have been as React's supposed to be declarative.
Svelte correctly uses the markup declration for the child, while React is imperative with children
. (Not to mention children
can be anything like a function to implement render props).
b. Slot fallbacks
https://svelte.dev/tutorial/slot-fallbacks
If you weren't specifying any fallback, you could use <slot />
but to provide a fallback (when a user didn't specify a child), then you can use a longer <slot>fallback content</slot>
.
box.svelte
<style>
.box {}
</style>
<div class="box">
<slot>Fallback content!!!</slot>
</div>
The example of none-child passed to Box
is as shown below
<script>
import Box from './Box.svelte';
</script>
<Box>
<h2>Hello!</h2>
<p>This is a box. It can contain anything.</p>
</Box>
<Box></Box>
<Box />
c. Named slot
https://svelte.dev/tutorial/named-slots
In React, one would expose seprate components or static child components like this.
function App() {
return (
<ContactCard>
<ContactCard.Name>Sung Kim</ContactCard.Name>
<ContactCard.Address />
</ContactCard>
)
}
// or
function App() {
return (
<ContactCard>
<ContactCardName>Sung Kim</ContactCardName>
<ContactCardAddress />
</ContactCard>
)
}
It requires to create seprate component for ContactCardName
or ContactCardAddress
, each of which accepts its own children
function.
This is where things get interesting.
You can specify which "slot" you want to insert the child content into!
ContactCard.svelte
<style>
.missing {}
</style>
<article class="contact-card">
<h2>
<slot name="name">
<span class="missing">Unknown name</span>
</slot>
</h2>
<div class="address">
<slot name="address">
<span class="missing">Unknown address</span>
</slot>
</div>
<div class="email">
<slot name="email">
<span class="missing">Unknown email</span>
</slot>
</div>
</article>
As shown in the previous section, each named slots contain fallbacks.
The calling component specifies the slot in the child component
App.svelte
<script>
import ContactCard from './ContactCard.svelte';
</script>
<ContactCard>
<span slot="name">Sung</span>
<span slot="email">Sung@sung.com</span>
</ContactCard>
c. Slot props
https://svelte.dev/tutorial/slot-props
Passing data from slot
to the parent component, one needs to declare the exposed state (via slot) while declaring the component
You don't declare a variable in the parent component but just sorta like "bind" using let
.
Hovering.svelte
: a component containg a slot.
<script>
let hovering;
const enter = () => hovering = true;
const leave = () => hovering = false;
</script>
<div on:mouseenter={enter} on:mouseleave={leave}>
<slot hovering={hovering}></slot>
<!-- or use the hsort hand -->
<!-- <slot hovering></slot> -->
</div>
To access hovering
in the parent component, use let
as mentioend before.
Parent.svelte
<script>
import Hoverable from './Hoverable.svelte';
</script>
<Hoverable let:hovering={hovering}>
<div class:active={hovering}>
{#if hovering}
<p>I am being hovered upon.</p>
{:else}
<p>Hover over me!</p>
{/if}
</div>
</Hoverable>
Note that hovering
variable is not declared in the script
but could be used inside Hovering
.
15. Context API
a. setContext and getContext
https://svelte.dev/tutorial/context-api
Svelte's Context API is similar to that of React;
Only decendant child components can access context data using getContext
expoed via setContext
in the parent.
store
is more like Zustand where state is avaiable anywhere in the component hierachy.
Difference between React & Svelte Context API is that, React's API is declarative using a markup, Svelte imperative, using setContext
during component initialization.
React
function App() {
return (
<Context.Provider value={value}>
children can access context value here
</Context.Provider>
)
}
16. Special elements
a. svelte:self
https://svelte.dev/tutorial/svelte-self
To recursively refer the current component.
There is a typo in the documentation so filed an issue: https://github.com/sveltejs/svelte/issues/5044
Update: "a file" refers to the current file, not the File
component. So the documentation is correct. Clsoed the issue.
b. svelte:component
https://svelte.dev/tutorial/svelte-component
Use <svelte:component this={component}>
to load a component dynamically.
To pass props, pass it to <svelte:component>
.
<svelte:component text="custom text" this={selected.component}/>
text
is then passed to selected.component
(not documented in the tutorial just found out by mistake).
Make sure that the dynamic component accepts the prop.
e.g.) RedThing.svelte
<style>
strong { color: red; }
</style>
<script>
export let text = "red thing";
</script>
<strong>{text}</strong>
c. svelte:window
https://svelte.dev/tutorial/svelte-window
It's a declarative way to add events to window
object.
d. svelte:window bindings
https://svelte.dev/tutorial/svelte-window-bindings
Turns out, you can also bind to some of window
's properties, not just events.
e. svelte:body
https://svelte.dev/tutorial/svelte-body
This lets you bind events declaratively in the document.body
.
f. svelte:head
https://svelte.dev/tutorial/svelte-head
Injecting content inside <html><head>
.
No need for react-helmet
like 3rd party library.
g. svelte:options
https://svelte.dev/tutorial/svelte-options
advanced Svelte compiler options.
Most notably, you can specify immutibility to optimize component render in a list.
17. Module context
a. Sharing code
https://svelte.dev/tutorial/sharing-code
This looks like a "static" variable avaiable throughout the all the instances of a component.
Possibly a prototype value.
b. Exports
https://svelte.dev/tutorial/module-exports
Exporting within module level script can be imported from another Svelte component.
18. Debugging
a. The @debug tag
https://svelte.dev/tutorial/debug
The better "console.log" :p
Photo by William Krause on Unsplash
Top comments (3)
But I tried it without $ prefix as shown below but still works.
The $ prefix is used to show that the variable is observable, you can omit it
Thank you for the confirmation of the behavior.
Oh yes. as that got me confused, I asked and someone replied that even Svelte test code uses the callback without
$
.reddit.com/r/sveltejs/comments/hbl...
Great post. Svelte looks awesome, I could get used to it easily.