Svelte 初心者用チートシート
概要
- 自分用に整理しただけ
- 基本的には公式ドキュメントのチュートリアルから抜粋
- 普段はVueを書いていて Svelte はド素人の状態で作成したので内容に誤りがあるかも
- TypeScript, SvelteKit への言及ナシ
開発環境構築
多分 Vite が一番早い
yarn create vite my-svelte-app --template svelte
cd my-svelte-app/
yarn install
yarn dev
localhost:3000
でもう動いてる
vscode なら Svelte 用の拡張を入れておけば、シンタックスハイライトやコードスニペットが機能する
コンポーネントの基本構造
Vue の SFC 同様に、スクリプト、テンプレート、スタイルを一つのファイル (.svelte
) に記述する。
<script>
const name = 'Svelte'
</script>
<main>
<h1>Hello, {name}</h1>
</main>
<style>
h1 {
color: gray;
}
</style>
要素のプロパティへのバインド
{}
でデータバインディングできる
<script>
const src = "https://storage.googleapis.com/zenn-user-upload/avatar/845fa75ba8.jpeg"
</script>
<main>
<img src={src}>
</main>
プロパティ名とバインドする変数名が一致してる場合は省略可能
<main>
<img {src}>
</main>
スタイリング
style ブロックに記述した CSS はデフォルトでコンポーネントローカルとなり、タグセレクタを使用しても他のコンポーネントに干渉することはない
<p>This is a paragraph.</p>
<style>
p {
color: purple;
font-family: "Comic Sans MS", cursive;
font-size: 2em;
}
</style>
外部コンポーネントの利用
import するだけですぐに使用可能に
<script>
import Child from "./components/Child.svelte";
</script>
<Child />
サニタイズせずにテキスト出力
XSS には気をつけよう
<script>
const html = '<h1>Hello, World!</h1>'
</script>
<div>{@html html}</div>
DOMイベントハンドリング
<script>
let count = 0
function increment() {
count += 1
}
</script>
<button on:click={increment}>
{count}
</button>
インラインでも可
<div on:mousemove={e => m = { x: e.clientX, y: e.clientY }}>
The mouse position is {m.x} x {m.y}
</div>
モディファイア (once, preventDefault, stopPropagation など) は |
で設定可能
<button on:click|once|capture={handleClick}>
Click me
</button>
Reactive Declarations
日本語でなんて呼ぶのかわからないけど、 Vue の computed みたいなもの
let count = 0
$: doubled = count * 2
ブロック展開して複数行も書ける
$: {
console.log('count changed!!')
console.log(`the count is ${count}`)
}
なんなら条件式も書けちゃう
$: if (count > 10) {
alert('count is over 10')
}
配列やオブジェクトの更新方法
チートシートというか Tips だけど、 Vue (v2) と同様に、配列・オブジェクトの操作ではリアクティブが反映されないことがある。
Array#push
だとリアクティブにならないが、
<script>
let array = [1,2,3]
function push() {
array.push(array[array.length - 1] + 1) // ← NG
}
</script>
<button on:click={push}>
{array.join(',')}
</button>
Svelte だと代入がリアクティブのトリガーになるので、明示的な代入をすればOK
<script>
let array = [1,2,3]
function push() {
array = [...array, array[array.length - 1] + 1] // ← OK
}
</script>
<button on:click={push}>
{array.join(',')}
</button>
コンポーネントの props の定義
変数を export するだけで、 props として親コンポーネントから注入できるようになる
<script>
export let value
</script>
<p>props value is {value}</p>
<Child value=1 />
当然 props へのバインディングも可能
<Child value={count} />
props 側に初期値がついていると、デフォルト値になる
export let value = 1
props が複数ある場合、オブジェクトで一括指定が可能
export let a
export let b
export let c
<script>
import Child from './components/Child.svelte'
const props = {
a: 1,
b: 2,
c: 3
}
</script>
<Child {...props} />
テンプレートでの条件分岐
Vue の v-if みたいなもの
{#if value % 3 === 0 && value % 5 === 0}
<div>FizzBuzz</div>
{:else if value % 3 === 0}
<div>Fizz</div>
{:else if value % 5 === 0}
<div>Buzz</div>
{:else}
<div>{value}</div>
{/if}
テンプレートでのループ
Vue の v-for みたいなもの
{#each values as value}
<p>{value}</p>
{/each}
テンプレートで Promise の状態による分岐
Promise 変数の状態によってテンプレートで分岐できるため、外部データの読み込み中はメッセージを表示するなどができる
<script>
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolved');
}, 1000);
});
</script>
{#await promise}
<p>...waiting</p>
{:then value}
<p>{value}</p>
{:catch error}
<p>{error.message}</p>
{/await}
コンポーネントからイベントを発火する
Vue の emit に近いやつ
<script>
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
const message = 'Child Value'
function sayHello() {
dispatch('sayHello', { message })
}
</script>
<button on:click={sayHello}>clickMe</button>
<script>
import Child from './components/Child.svelte'
</script>
<Child on:sayHello={e => console.log(e.detail.message)}/>
カスタムイベントでなく、DOMイベントをそのまま親に伝搬する場合は省略記法が使える
<button on:click>ClickMe</button>
フォーム要素へのバインディング
input
<input bind:value={name}>
input[type=number] input[type=range] の場合は、数値に暗黙的に変換される
<input type=number bind:value={a} min=0 max=10>
<input type=range bind:value={a} min=0 max=10>
Checkbox
<input type=checkbox bind:checked={yes}>
Textarea
<textarea bind:value={value}></textarea>
Select
<select bind:value={selectedValue} on:change="{() => answer = ''}">
Select(multiple)
<select multiple bind:value={flavours}>
Radio(グループ)
複数のラジオをグループ化し、いずれか一つが選択された状態にする
<label>
<input type=checkbox bind:group={selectedValues} name="option" value="one">
One
</label>
<label>
<input type=checkbox bind:group={selectedValues} name="option" value="two">
Two
</label>
<label>
<input type=checkbox bind:group={selectedValues} name="option" value="three">
Three
</label>
Checkbox(グループ)
複数のチェックボックスをグループ化し、各チェックボックスの状態を配列で監視する
<label>
<input
type="checkbox"
bind:group={selectedValues}
name="option"
value="one"
/>
One
</label>
<label>
<input
type="checkbox"
bind:group={selectedValues}
name="option"
value="two"
/>
Two
</label>
<label>
<input
type="checkbox"
bind:group={selectedValues}
name="option"
value="three"
/>
Three
</label>
要素への特殊なバインディング
Readonly なプロパティへのバインディング
DOM の状態が変わった場合に変数に反映されるが、変数を書き換えても DOM の状態は変わらない
<div bind:clientWidth={w} bind:clientHeight={h}>Hello</div>
{ w } { h }
DOM に対する this
コンテキストをバインドすることで、要素を直接 JavaScript から参照可能にする
<canvas
bind:this={canvas}
width={32}
height={32}
></canvas>
コンポーネントライフサイクル
onMount/onDestroy は import が必要
import { onMount, onDestroy } from 'svelte'
onMount(() => console.log('Mounted'))
onDestroy(() => console.log('Destroyed'))
Vue の nextTick みたいに、次のレンダリングを待機するのには ticks
を使う
import { tick } from 'svelte'
await tick();
class 名のバインディング
だいたい Vue と同じで、状態を元に class を切り替えられる
<button
class="{current === 'foo' ? 'selected' : ''}"
on:click="{() => current = 'foo'}"
>foo</button>
クラス名と変数名が同じ場合は省略可能
<div class:big>
<!-- ... -->
</div>
Store
ストアは関連性のないコンポーネント間、モジュール間でステートを共有するための仕組み
writable
で書込み可能なストアを生成可能
import { writable } from 'svelte/store'
export const count = writable(0)
writable
インタフェースを用いた値の更新は set
update
で行える
function increment() {
count.update(n => n + 1)
}
function reset() {
count.set(0)
}
writable
インタフェースは subscribe
メソッドを通じて値の変更監視ができる。 subscribe
メソッドのは、 unsubscribe
するためのメソッドが戻り値で返ってくるので、適切に unsubscribe
してメモリリークを避ける
const unsubscribe = count.subscribe(value => localValue = value)
onDestroy(() => unsubscribe())
という、ストアの購読とライフサイクルの管理が面倒すぎるので、ショートハンドが用意されている。 $
を頭に付けて参照することで、 subscribe/unsubscribe を勝手にやってくれる
$count
読み取り専用ストアは readable
で作成可能
readable
は第一引数に初期値、第二引数に、初めて subscribe された際に実行されるコールバック関数を設定する。そのタイミングでのみ set
を通じて値の設定が可能。
また、コールバック関数の戻り値で、購読者が0になったときの後処理を記述できる
import { readable } from 'svelte/store'
const time = readable(new Date(), (set) => {
const interval = setInterval(() => {
set(new Date())
}, 1000)
return () => clearInterval(interval)
})
他のストアの値に依存したストアを derived
で作成可能。ストアの computed
みたいなものかな。
const count = writable(0)
const doubleCount = derived(count, $count => $count * 2)
subscribe
メソッドさえオブジェクトに含まれていれば、それはストアとして機能するため、 set
update
を隠蔽したカプセル化も可能。
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();
Motion
tweened
はなめらかな値の更新を行うオブジェクトを生成し、ストア同様に subscribe
set
メソッドで更新と購読を行う。
import { tweened } from 'svelte/motion'
const progress = tweened(0)
progress.subscribe(value => console.log(value))
progress.set(100)
0 から 100 に単純に更新するのでなく、何度も更新作業を行い少しずつ0から100に近づける。
spring
は tweened
とにてるが、より更新が頻繁に行われる場合に用いる
import { spring } from 'svelte/motion'
const progress = spring(0)
progress.subscribe(value => console.log(value))
progress.set(100)
Transitions
要素が表示されるとき、非表示になるときのアニメーションを容易に設定できる
<script>
import { fade } from 'svelte/transition'
let isShow = false
</script>
<label>
<input type="checkbox" bind:checked={isShow}>
show
</label>
{#if isShow}
<p transition:fade>トランジションサンプル</p>
{/if}
import する transition 関数に応じて異なるトランジションを表現できるほか、パラメータの付与によって微調整も行える
{#if isShow}
<p transition:fly="{{ y: 200, duration: 2000 }}">トランジションサンプル</p>
{/if}
表示するときと、非表示になるときで異なるトランジションを設定することも可能
<p in:fly="{{ y: 200, duration: 2000 }}" out:fade>トランジションサンプル</p>
各トランジションライフサイクルをフックする
<p
transition:fade
on:introstart={() => console.log("introstart")}
on:outrostart={() => console.log("outrostart")}
on:introend={() => console.log("introend")}
on:outroend={() => console.log("outroend")}
>
トランジションサンプル
</p>
要素が更新されるたびにトランジションを再実行したい場合は {#key}
ブロックを使用する
{#key value}
<div transition:fade>{value}</div>
{/key}
Actions
アクションは、要素単位のライフサイクルを定義するために使用できる。
export function outerClickable(node) {
const handleClick = (event) => {
if (!node.contains(event.target)) {
node.dispatchEvent(new CustomEvent("outclick"));
}
};
document.addEventListener("click", handleClick, true);
return {
destroy() {
document.removeEventListener("click", handleClick, true);
},
};
}
要素が生成されたタイミングと、破棄されたタイミングの処理を定義できるため、DOMイベントの安全な管理に活用できる。
<script>
import { outerClickable } from './actions/outerClickable'
</script>
<div use:outerClickable on:outclick={() => alert('outclick!')}>要素の外側をクリックするとアラートが鳴るよ</div>
アクションにはパラメータの指定も可能
export function longpress(node, duration) {
}
<button use:longpress={duration}>Push</button>
Slot
だいたい Vue ないし Web Components と一緒
<div class="box">
<slot></slot>
</div>
Slot に何も注入されなかった倍のフォールバックの指定
<div class="box">
<slot>
<em>no content was provided</em>
</slot>
</div>
名前付きスロット
<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>
<ContactCard>
<span slot="name"> P. Sherman </span>
<span slot="address">
42 Wallaby Way<br />
Sydney
</span>
</ContactCard>
注入されたスロットへのアクセス
{#if $$slots.comments}
<div class="discussion">
<h3>Comments</h3>
<slot name="comments"></slot>
</div>
{/if}
スロットへのプロパティ注入
<div>
<slot value={localValue}></slot>
</div>
<Child let:value>
{ value }
</Child>
Context API
React の Context とだいたい一緒
const key = Symbol();
setContext(key, {
getValue: () => value,
});
const { getValue } = getContext(key)
Special elements
自分自身を再帰的に描画する
<svelte:self>
描画するコンポーネントを動的に決定する
<svelte:component this={selected.component}/>
Window オブジェクトのイベントリスナーをバインド出来る抽象コンポーネント
<svelte:window bind:scrollY={y} on:keydown={handleKeydown}/>
同じく body
<svelte:body
on:mouseenter={handleMouseenter}
on:mouseleave={handleMouseleave}
/>
header へのタグ注入
<svelte:head>
<link rel="stylesheet" href="/tutorial/dark-theme.css">
</svelte:head>
複数要素を束ねる抽象要素
<Box>
<svelte:fragment slot="footer">
<p>All rights reserved.</p>
<p>Copyright (c) 2019 Svelte Industries</p>
</svelte:fragment>
</Box>
Module context
同じコンポーネントを複数使用する際に、コンポーネント間で共通のスコープを持てる。private static みたいな…?
<script context="module">
let current;
</script>
<script context="module">
const elements = new Set();
export function stopAll() {
elements.forEach(element => {
element.pause();
});
}
</script>
Debugging
レンダリングされる際にログを出力できる
{@debug user}
Discussion