はじめに
今後爆発的人気になるかもしれないし、ならないかもしれないSvelteの基本的な記述のやり方についてまとめてみました。
フロント開発に関わっている方や、フロント開発を勉強中の方に読んでいただけたらと思います。
※ReactやVueでの開発経験があったほうが、よりわかりやすい内容となっています。
注意
この記事はSvelteの基本的な記述のみをまとめているため、Svelteの概要や、アニメーションの使い方などは割愛していますのでご了承ください。
「やりたいことから」リンク集
■基本情報
プロジェクト作成
HTML、CSS、JSの記述
■データ
reactive値を扱う
再計算、再実行させる
■コンポーネント
コンポーネントの作成、呼び出し(通常、slot)
propsを扱う
■イベント
DOMイベントと関数を紐づける
コンポーネントからイベントを発信させる
■ロジック
表示を分岐させる(表示、非表示)
表示を繰り返す
非同期処理を扱う
■バインド
テキスト入力欄を使う
チェックボックスを使う
ラジオボタンを使う
セレクトボックスを使う
子コンポーネントの入力要素にバインドさせる
■ライフサイクル
mount時に処理を実行させる
unMount時に処理を実行させる
DOM更新前に処理を実行させる
DOM更新後に処理を実行させる
■Store
Storeを作成する
Storeの値を取得する
Storeの値を変更する
プロジェクト作成
test-project
の部分を任意のプロジェクト名に変えてください。
※この記事はTypeScript
での記述を前提に書いています。
$ npx degit sveltejs/template test-project
$ cd test-project
$ node scripts/setupTypeScript.js
$ npm install
$ npm run dev
VSCodeのパッケージを追加
VSCode
を使用する方は、任意で入れてみてください。
HTML、CSS、JSの記述
svelteは、JS
、HTML
、CSS
の順番で記述する。
-
script内に宣言した変数は
{}
を使用することでHTML内に反映できる。 -
CSSは
scoped
となっている。
<script lang="ts">
let text = 'Hello World'
</script>
<h1 class="title">{text}</h1>
<style>
.title {
color: red;
}
</style>
属性名と変数名が同じ場合は省略できる。
<script lang="ts">
let src = './○○.png'
</script>
<img src={src} alt="image"> <!-- ↓と同じ -->
<img {src} alt="image">
reactive値を扱う
svelteではlet
で変数宣言をすると、自動的にreactive
な値となる。
<script lang="ts">
let count = 0
</script>
<h1>{count}</h1>
注意
svelteのリアクティビティは代入によってトリガーされるため、pushやspliceのような配列メソッドを使っても自動的には更新されない。
再計算、再実行させる
変数や関数の前に$:
を記述することで、再計算、再実行の対象となる。
<script lang="ts">
let count = 0
// countの値が変更されると再計算される
$: doubled = count * 2
// 任意の処理を再実行することもできる
$: console.log('the count is ' + count);
// if文の前にも記述できる
$: if (count >= 10) {
alert('count is dangerously high!');
count = 9;
}
</script>
コンポーネントの作成、呼び出し(通常、slot)
通常のコンポーネントの場合
<script lang="ts">
import Nested from './Nested.svelte';
</script>
<Nested />
<script lang="ts">
let text = 'Hello World'
</script>
<p>{text}</p>
slotの場合
<script lang="ts">
import Box from './Box.svelte';
</script>
<Box>
<p>Hello World</p>
</Box>
<div>
<slot></slot>
</div>
propsを扱う
propsとして渡される値の前にexport
を記述することで、「propsとして渡される値」と認識される。
<script lang="ts">
import Nested from './Nested.svelte';
</script>
<Nested answer={42} />
<script lang="ts">
export let answer;
export let name = 'Taro'; // あらかじめ値を代入しておくとデフォルト値にできる
</script>
<p>name: {name}</p>
<p>The answer is {answer}</p>
プロパティを持っているオブジェクトを渡す場合、...
で簡略化できる
<script lang="ts">
import Nested from './Nested.svelte';
const user = {
name: 'Taro',
age: 20
}
</script>
<Nested {...user} />
<script lang="ts">
export let name;
export let age;
</script>
<p>名前: {name}</p>
<p>年齢: {age}</p>
DOMイベントと関数を紐づける
イベント名の前にon:
を記述する。
<script lang="ts">
let count = 0;
const countUp = () => {
count += 1
}
</script>
<h2>{count}</h2>
<button on:click={countUp}>
Count Up
</button>
子、孫コンポーネントのDOMイベントの場合イベントフォワーディング
をする。
<script lang="ts">
import CustomButton from './CustomButton.svelte';
const handleClick = () => {
alert('Button Clicked');
}
</script>
<CustomButton on:click={handleClick}/>
<button on:click>
Click me
</button>
|
を使用し、イベントに修飾子をつけられる。複数指定可能。
<script lang="ts">
const handleClick = () => {
alert('alerts')
}
</script>
<!-- once の場合、1回のみ実行される -->
<button on:click|once={handleClick}>
Click me
</button>
<!-- 複数指定する場合 -->
<button on:click|once|self={handleClick}>
Click me
</button>
イベント修飾子の一覧
-
preventDefault
・・・実行する前にevent.preventDefault()
を呼び出す。 -
stopPropagation
・・・event.stopPropagation()
を呼び出す。 -
passive
・・・タッチ/ホイール
イベントでスクロールのパフォーマンスを向上させる。(svelteが自動的に追加) -
nonpassive
・・・passive: false
を明示的に設定。 -
capture
・・・バブリングフェーズではなく、キャプチャフェーズ中に実行する。 -
once
・・・ハンドラを最初に実行した後に削除する。 -
self
・・・設定した要素がevent.target
の場合にのみ、実行する。 -
trusted
・・・event.isTrusted
がtrue
の場合にのみ、実行する。
コンポーネントからイベントを発信させる
Vueのemit
みたいなもの。
createEventDispatcher
を使用し、イベントを発信。
<script lang="ts">
import Child from './Child.svelte';
const handleMessage = (event) => {
alert(event.detail.text);
}
</script>
<Child on:message={handleMessage}/>
<script lang="ts">
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
const sayHello = () => {
dispatch('message', {
text: 'Hello!'
});
}
</script>
<button on:click={sayHello}>
Click to say hello
</button>
中間コンポーネントがある場合はフォワーディング
する必要がある。
<script lang="ts">
import Child from './Child.svelte';
const handleMessage = (event) => {
alert(event.detail.text);
}
</script>
<Child on:message={handleMessage}/>
<script lang="ts">
import Grandchild from './Grandchild.svelte';
</script>
<Grandchild on:message/>
<script lang="ts">
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
const sayHello = () => {
dispatch('message', {
text: 'Hello!'
});
}
</script>
<button on:click={sayHello}>
Click to say hello
</button>
表示を分岐させる(表示、非表示)
#
の文字は常にブロックの開始タグを示す。
:
の文字は {:else}
のようにブロックの継続タグを示す。
/
の文字は常にブロックの終了タグを示す。
<script lang="ts">
let isLogin = false;
const toggle = () => {
isLogin = !isLogin;
}
</script>
{#if isLogin}
<button on:click={toggle}>
Log out
</button>
{:else}
<button on:click={toggle}>
Log in
</button>
{/if}
else if
ももちろん使える
<script lang="ts">
let x = 7;
</script>
{#if x > 10}
<p>{x} is greater than 10</p>
{:else if 5 > x}
<p>{x} is less than 5</p>
{:else}
<p>{x} is between 5 and 10</p>
{/if}
表示を繰り返す
svelteで表示の繰り返しをする場合はeach
を使用する。
<script lang="ts">
let users = [
{ name: 'Taro', age: 20 },
{ name: 'Hana', age: 23 },
{ name: 'Ken', age: 25 }
];
</script>
<ul>
{#each users as user}
<li>
<p>name: {user.name}</p>
<p>age: {user.age}</p>
</li>
{/each}
</ul>
第2引数でindexを指定できる。
{#each users as user, i}
<li>
<p>number: {i}</p>
<p>name: {user.name}</p>
<p>age: {user.age}</p>
</li>
{/each}
一意な識別子(key)
{#each users as user (user.name)}
{/each}
非同期処理を扱う
<script lang="ts">
const fetchData = async() => {
const res = await fetch('https://○○○');
if (res) {
return 'OK';
} else {
throw new Error('Error');
}
}
let result = fetchData();
const promiseTest = () => {
result = fetchData();
}
</script>
<button on:click={promiseTest}>
fetch
</button>
{#await result}
<!-- 処理中の表示を記述 -->
{:then string}
<!-- resolved時の表示を記述 -->
{:catch error}
<!-- rejected時の表示を記述 -->
{/await}
reject
できないことがわかっている場合は、catch ブロックを省略することが可能。
{#await result}
<p>...waiting</p>
{:then string}
<p>The text is {string}</p>
{/await}
resolve
するまで何も表示したくない場合は、最初のブロックを省略することもできる。
{#await result then value}
<p>The text is {value}</p>
{/await}
テキスト入力欄を使う
bind:value
を使用することで、reactive値
とinput値
を紐づけることができる。
vueのv-model
と同じ。
※bindの名前と変数名が一致する場合は省略できる。
textareaタグでも同様。
<script lang="ts">
let value = '世界';
</script>
<input bind:value={value}> <!-- ↓と同じ -->
<input bind:value>
<h1>Hello {value}!</h1>
type="number"
やtype="range"
を使用したときは、数値として解釈してくれる。
<script lang="ts">
let num = 2;
</script>
<input type=number bind:value={num}>
<h1>{num * num}</h1>
チェックボックスを使う
チェックボックスの場合は、bind:checked
を使用する。
<script lang="ts">
let isChecked = false;
</script>
<label>
<input type=checkbox bind:checked={isChecked}>
check box
</label>
<button disabled={!isChecked}>
Subscribe
</button>
ラジオボタンを使う
ラジオボタンのようにグループ化が必要な場合は、bind:group
を使用する。
<script lang="ts">
let radioValue = 1
</script>
<label>
<input type=radio bind:group={radioValue} name="radioValue" value={1}>
No.1
</label>
<label>
<input type=radio bind:group={radioValue} name="radioValue" value={2}>
No.2
</label>
<label>
<input type=radio bind:group={radioValue} name="radioValue" value={3}>
No.3
</label>
セレクトボックスを使う
セレクトボックスもbind:value
を使用する。
<script lang="ts">
let questions = [
{ id: 1, text: 'question1' },
{ id: 2, text: 'question2' },
{ id: 3, text: 'question3' }
];
let selected;
</script>
<select bind:value={selected}>
{#each questions as question}
<option value={question}>
{question.text}
</option>
{/each}
</select>
子コンポーネントの入力要素にバインドさせる
DOM要素のプロパティにバインドできるのと同様に、コンポーネントのprops
にもバインドできる。
<script lang="ts">
import Child from "./Child.svelte";
let name = '';
</script>
<Child bind:name={name} />
<h1>{name}</h1>
<script lang="ts">
export let name: string
</script>
<input type="text" bind:value={name}>
mount時に処理を実行させる
onMount
を使用することで、コンポーネントが最初にDOM
にレンダリングされた後に処理を実行できる。
<script lang="ts">
import { onMount } from 'svelte';
onMount(() => {
console.log('Mounted!!')
});
</script>
<h1>onMount</h1>
unMount時に処理を実行させる
onDestroy
を使用することで、コンポーネントが破棄されるときに処理を実行できる。
<script lang="ts">
import { onDestroy } from 'svelte';
onDestroy(() => {
console.log('Destroy!!')
});
</script>
<h1>onDestroy</h1>
DOM更新前に処理を実行させる
beforeUpdate
を使用することで、コンポーネントが最初にマウントされる前と、DOM
が更新される直前に処理を実行できる。
<script lang="ts">
import { beforeUpdate } from 'svelte';
beforeUpdate(() => {
console.log('Update!!')
});
</script>
<h1>beforeUpdate</h1>
DOM更新後に処理を実行させる
afterUpdate
を使用することで、DOM
がデータと同期した後に処理を実行できる。
<script lang="ts">
import { afterUpdate } from 'svelte';
afterUpdate(() => {
console.log('Updated!!')
});
</script>
<h1>afterUpdate</h1>
Storeを作成する
外部から読み書き可能なwritable
と、外部からは読み取り専用のreadable
がある。
readable
は第1引数に初期値、第2引数に値を変更するための関数が必要。
import { writable, readable } from 'svelte/store';
export const count = writable(0); // 読み書き可能
// 読み取り専用
export const time = readable(new Date(), function start(set) {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return function stop() {
clearInterval(interval);
};
});
Storeの値を取得する
Storeから読み取る値には$
の記述が必要。
<script lang="ts">
import { count } from './stores';
</script>
<h1>The count is {$count}</h1>
import { writable } from 'svelte/store';
export const count = writable(0);
Storeの値を変更する
現在のStoreの値を利用するupdate
と、直接変更するset
がある。
<script lang="ts">
import { count } from './stores';
import Counter from './Counter.svelte'
</script>
<h1>The count is {$count}</h1>
<Counter />
<script lang="ts">
import { count } from './stores';
const increment = () => {
count.update(n => n + 1); // storeの現在の値を引数に取り更新
}
const reset = () => {
count.set(0); // storeの値を直接変更
}
</script>
<button on:click={increment}>increment</button>
<button on:click={reset}>reset</button>
import { writable } from 'svelte/store';
export const count = writable(0);
おわりに
ここまでお読みいただきありがとうございました。
以上がSvelteの基本的な記述のやり方です。
Svelteはとてもシンプルで使いやすいので、これからどんどん需要が高まっていくことを期待しています!
記述ミスやわかりにくい点などございましたら、お手数ですが、コメントや編集リクエストなどで送っていただけたら幸いです。
これからも少しずつSvelteをキャッチアップしていきたいと思います。