フロントエンドの開発をする際のフレームワークは、Vue.jsを使うことがほとんどだったのですが、先日、知人よりSvelteなるものがあると教えてもらったので、チュートリアルをやってみました。
Svelteのv1.0.0は2016年12月30日にリリースされており、現在では、v3.31.0までリリースされています。
はじめに、Svelteについて、さっと紹介します。
Svelte is a tool for building fast web applications.
と公式にあるように、高速なWebアプリケーションを構築するためのフレームワークです。乱暴な言い方をすると、ReactやVue.jsなどと似た方なものです。ただし、これらとはアプローチが異なるので、その辺りを次で説明します。
公式曰く、Svelteの特徴は以下の3つです。
Svelteは、書かなければならないコード量を減らすことを目標としています。公式曰く、あるSvelteのコンポーネントと同等のReactのコンポーネントのコード量は、Svelteのコード量と比べて、だいたい4割ほど多いそうです。
実際に公式サイトに掲載されていたサンプルを見比べてみましょう。初めに、Svelteで書かれたコードです。
<script> let a = 1; let b = 2; </script> <input type="number" bind:value={a}> <input type="number" bind:value={b}> <p>{a} + {b} = {a + b}</p>
2つの入力欄とその合計を表示するだけのシンプルな画面です。
次にReactで書かれたコードです。
import React, { useState } from 'react'; export default () => { const [a, setA] = useState(1); const [b, setB] = useState(2); function handleChangeA(event) { setA(+event.target.value); } function handleChangeB(event) { setB(+event.target.value); } return ( <div> <input type="number" value={a} onChange={handleChangeA}/> <input type="number" value={b} onChange={handleChangeB}/> <p>{a} + {b} = {a + b}</p> </div> ); };
見比べてみると、Svelteのコードの方がコード量が少ないことが見て取れます。
ReactやVue.jsなどとは異なり、SvelteはVirtual DOMを使わない代わりに、ビルド時にフレームワークのないJavaScriptのコードに変換されます。これにより、DOMを更新するためにVirtual DOMの差分をチェックする必要がないため、高速に動作することができます。
No Virtual DOMの説明と重複するのですが、Virtual DOMを使わず、ビルド時にDOMを直接変更するコードに変換されるため、高速に動作することができます。
また、Svelteでは状態を変化させるために状態管理ライブラリを使用する必要はありません。変数の値を変更するだけで画面に反映されます。
Svelteのコードは.svelte
ファイルに記述します。記述の仕方はVue.jsに似ており、<script>
タグの中に変数や関数などを定義し、<style>
タグの中にCSSを記述します。なお、CSSはコンポーネントにスコープされているので、他のコンポーネントには影響しません。
<script> let name; </script> <h1>Hello1 {name}!</h1> <style> h1 { color: #ff3e00; text-transform: uppercase; font-size: 4em; font-weight: 100; } </style>
基本的なシンタックスをいくつか紹介します。
変数は<script>
タグの中にそのまま定義するだけです。
<script> let name; </script>
画面に表示するには、{}
で囲うだけです。
<h1>{name}</h1>
props
は変数定義と似ていますが、export
をつけて宣言します。
<script> export let firstname; export let lastname = 'Yamada'; </script>
値渡しはこのようにします。
<script> import MyComponent from './MyComponent.svelte'; </script> <MyComponent firstname={'Taro'}/>
また、オブジェクトを展開して渡すこともできます。
<script> const name = { firstname: 'Taro', lastname: 'Yamada', }; import MyComponent from './MyComponent.svelte'; </script> <MyComponent {...name}/>
例えば、ボタンクリック時にカウントアップさせるには、このように記述します。
<script> let count = 0; function handleClick() { count += 1; } </script> <button on:click={handleClick}> Count up </button> <p> {count} </p>
また、|
で繋ぐことで、イベントの振る舞いを修飾することができます。
<button on:click|once|capture={handleClick}> Count up </button>
svelteのデータフローはトップダウン型のため、親のコンポーネントは子のコンポーネントにpropsを渡すことができますが、その逆はできません。しかし、bind:value
を使うことで、これを実現することができます。
<script> let name = 'taro'; </script> <input bind:value={name}/> <h1>{name}</h1>
条件に応じて表示を切り替えたい場合は{#if}
を使用します。
<script> let number = 1; </script> {#if number % 2 == 0} <p> even </p> {:else if number % 2 != 0} <p> odd </p> {:else} <p> error </p> {/if}
繰り返しで処理したい場合は{#each}
を使用します。
<script> let persons = [ { id: 1, name: 'Taro',} { id: 2, name: 'Hanako',}, { id: 3, name: 'Ichiro',} ]; </script> <ul> {#each persons as person} <li>{name}</li> {/each} </ul>
また、(person.id)
のように識別する値を指定することもできます。
<ul> {#each persons as person (person.id)} <li>{name}</li> {/each} </ul>
非同期で処理した値を表示したい場合は{#await}
を使います。
<script> async function someAsyncFunc() { // 何らかの処理 return 1; } let promise = someAsyncFunc(); </script> {#await promise} <p>...waiting</p> {:then val} <p>{val}</p> {:catch error} <p>{error.message}</p> {/await}
コンポーネントのlifecycleにはonMount
、onDestroy
、beforeUpdate
、afterUpdate
、Tick
があります。
onMount
はコンポーネントがレンダリングされた直後に実行されます。
<script> import { onMount } from 'svelte'; onMount(async () => { await someAsyncFunc(); }); </script>
onDestroy
はコンポーネントが破棄される時に実行されます。
<script> import { onDestroy } from 'svelte'; onDestroy(() => someFunc()); </script>
onDestroy
に限らず、lifecycle関数はコンポーネントの初期化時に呼び出す必要がありますが、ReactやVue.jsなどとは異なり、どこでも呼び出すことができます。以下のように関数内に書けるため、clearInterval()
に渡すidを保持しておく必要がありません。
<script> import { onDestroy } from 'svelte'; function someFunc(callback, ms) { const interval = setInterval(callback, ms); onDestroy(() => { clearInterval(interval); }); } </script>
beforeUpdate
はDOMが更新される直前に実行されます。また、afterUpdate
はDOMが更新された直後に実行されます。
<script> import { beforeUpdate, afterUpdate } from 'svelte'; beforeUpdate(() => someBeforeFunc()); afterUpdate(() => someAfterFunc()); </script>
Tick
はほかのlifecycleとは異なり、いつでも呼び出すことができます。Svelteでは、コンポーネントを更新してもすぐにDOMには反映されず、次のマイクロタスクが実行されるタイミングでまとめて更新されます。
await tick()
とすることで、次のマイクロタスクが実行されるタイミングで任意の処理を実行することができます。
<script> async function someAsyncFunc() { await tick(); // 任意の処理 } </script>
Svelteの環境構築は非常に簡単です。任意のバージョンのNode.jsをインストールした後にnpx degit sveltejs/template project-name
を実行し、npm install
を実行するだけです。
サーバーを起動するにはnpm run dev
を実行します。その後、http://localhost:5000/
にアクセスすると、表示の確認ができます。
本番環境などで使用する場合は、npm run build
でビルドし、npm run start
でサーバを立ち上げます。
TypeScriptで書きたい場合は、node scripts/setupTypeScript.js
を実行します。
Svelteについて、ざっと紹介しました。チュートリアルでは実際にブラウザで実際にコードを書きながら学習できるので、始めやすいと思います。