はじめに
私が、Next.js, Nuxt.jsの他にモダンだと勝手に決めつけたのがSvelte.jsです。
理由は最近よく聞くからです。
少し調べてみたら、「Svelte.jsはコンパイラ」だとか
「Svelte.jsは軽くて早い」という、とてもワクワクするような言葉がたくさん目に付き、
これはみんなに共有しなきゃ!!と思ったので、この記事を書くことにしました。
この記事では、Svelte.jsの魅力や特徴を知るため概要から、実際にTODOを作って
使い方を一緒に勉強していきたいと思います。
記事内によく「コンパイル」という文字が出てきますが、理解してる想定で記事が進みます。
わからないという方はこちらの説明がわかりやすいため、参考にしてみてください
svelte.jsとは
Svelteは、ユーザーインターフェイスを構築するための急進的な新しいアプローチです。React.jsやVue.jsのような従来のフレームワークは、ほとんどの作業をブラウザで行いますが、Svelte.jsは、その作業を皆さんがアプリを構築するときのコンパイルステップに移動します。
引用元:公式ドキュメント
まず、Svelte.jsはWebアプリケーションやUIを構築するためのライブラリですが、
従来のフレームワークとは異なり、UIライブラリではなくコンパイラです。(詳細については後述します。)
ファイル構成もvue.jsのように単一ファイルコンポーネントで構成されています。
一つのファイルにHTML、CSS、Javascriptが集約される構成でプログラムを書いていきます。
Svelte.jsの特徴
Svelteには従来のフレームワークとの大きな違いや特徴・コンセプトとして3点あげています。
1, Write less code(より記述量を少なく)
svelte.jsが目指す明確な目標として、「記述しなければならないコードの量を減らすこと」を掲げています。
Svelte.jsはUIの構築をするコードの記述量を少なくするために、双方向バインディングやstateの管理などがシンプルに実装できるなど、様々な仕組みが備わっていて、実行時のファイルサイズもJavascriptフレームワークの中では最小と言われています。以下が、RealWorldから引用したグラフになります。
めちゃくちゃファイルサイズ小さくて逆に怖いです。
2, No virtual DOM(仮想DOMを使用しない)
このセクションを理解するには、仮想DOMへの理解が必要です。
公式ドキュメントがわかりやすかったため引用します。
仮想DOMって何?(公式ドキュメントから引用)
JSXを使用した以下のような、Reactコンポーネントがあります。
function HelloMessage(props) {
return (
<div className="greeting">
Hello {props.name}
</div>
);
}
JSXを使用しなかった場合は以下のようになります。
function HelloMessage(props) {
return React.createElement(
'div',
{ className: 'greeting' },
'Hello ',
props.name
);
}
上記のコードは、ページがどのように見えるかを表現するオブジェクトになります。
このオブジェクトが仮想DOMです。
仮想DOMは、stateの値が更新されるたびに(例えば props.name
が変わったとき)、新たに作成されます。
フレームワークの仕事は、どのような更新が必要か把握して、DOMにオブジェクト(仮想DOM)を適用することです。
そのため、必要に応じてDOM API等を使用すれば、
仮想DOMを使用したフレームワークよりも、パフォーマンス面で勝てます。
仮想DOMとSvelte.js
本題は戻り、Svelte.jsは仮想DOMを使用しません。
その代わりに、Svelte.jsは、先ほど説明した通りコンパイラのため、生のJavascriptとHTMLとCSSにコンパイルします。
また、ビルド時に差分を検知し、その差分のコードをコンパイルをするため、ランタイムもより高速になります。
3, Truly reactive(Reactive)
ある値に更新が検知されると、DOMも更新されるような仕組みになっていて、reactiveな処理ができます。
これに関しては、実際にコードを見た方がわかりやすいかと思いますので、早速セットアップから始めていきましょう。
Svelte.jsでTODOアプリを作ってみた。
Svelte.jsのセットアップ
前提
Node.js・npmがインストールされている必要があります。
またプロジェクト作成には、npxコマンドを使用します。
環境
M1 Macを使用しています。
MacOS: 11.5.1(macOS Big Sur)
Node.js: 14.17.6
npm: 6.14.15
Svelte.js: 3.0.0
セットアップ手順
Svelte.jsのインストールは、以下のコマンド達をコマンドラインで実行してください。
npx degit sveltejs/template {training-svelte} ←ここは任意のプロジェクト名を入力してください。
コマンド内で使用されている「npx」コマンドについてはこちらを参考にしてください。
上記コマンドを実行した後の、プロジェクトはこのような構造になっています。
この状態でも、動作するためブラウザの表示確認をしてインストールを完了としたいと思います。
以下のコマンドを実行して、ブラウザでの表示確認をしていきます。
npm run dev
http://localhost:5000にアクセスして、表示が確認できました。
TODOアプリ作成
インストールとセットアップが完了したので、
Svelte.jsの使い方を把握するために簡単なTODOアプリを作成していきます。
Svelte.jsは単一ファイルコンポーネントで書けるため、コンポーネント毎に説明して行きます。
注: CSS(Sass)についての説明は、目的と逸れるため割愛します。
※ベースのコンポーネントは以下のようになります。
このコンポーネントに作成したコンポーネントをインポートして、アプリケーションを構築していきます。
<script>
</script>
<main>
</main>
<style>
</style>
ヘッダーコンポーネント作成
以下が作成完了したヘッダーコンポーネントの内容です。
<header class="header">
<a href="/" class="header_logo">
<img src="img/logo.png" alt="LOGO">
</a>
</header>
<style>
.header {
width: 100%;
background-color: #fff;
padding: 20px 0;
}
.header_logo {
width: 50px;
display: block;
margin: 0 auto;
}
</style>
・・・普通のコーディングです。
↑ちなみに表示は、上のようになってます。(ロゴは自作しました。...といってもこちらのサイトから自分で生成しました。)
コンテンツ部分の作成 - フォームコンポーネント -
次にコンテンツ部分の中の、TODOを追加するフォームコンポーネントを作成します。
TODOを追加する機能を実装するためTODOの内容をグローバルで使用できるよう、状態管理を使用します。
vueで言うと、Vuex。reactで言うと、reduxに当たるものです。
Svelte.jsでは、標準搭載のsvelte/storeを使用することで、Storeパターンでの状態管理を開始できます。
今回はTODO全体を管理するだけなので、以下のように書きました。
import { writable } from 'svelte/store'
export const todos = writable([])
svelte/storeのwritableメソッドを使用することで、読み取り、更新可能なstoreを作成できます。
今回はtodoという名前をつけて、配列を初期値として設定しています。このstore(配列)にTodoを追加していくイメージです。
やっとTodoを追加するフォーム部分を、着手していきます。
以下が完成したフォームコンポーネントのコードです。
<script>
import { todos } from '../stores/todo'
let text = ''
let errorMessage = ''
const registerTodo = () => {
// errorメッセージ出力
if(text === '') {
errorMessage = 'TODOが入力されていません。TODOを入力してください。'
return
}
errorMessage = ''
const id = $todos.length + 1
todos.update((todo) => {
return [...todo, {id, text, 'isDone': false}]
})
// 初期化
text = ''
}
</script>
<div class="main_form">
{#if errorMessage}
<p class="main_form_error">{errorMessage}</p>
{/if}
<form class="main_form_body">
<input
bind:value={text}
class="main_form_txt"
type="text"
placeholder="TODO">
<button
on:click={registerTodo}
class="main_form_submit"
type="button">
追加
</button>
</form>
</div>
Svelte.js特有の記述を上から説明していきます。
-
リアクティブな変数の定義
→ Svelte.jsでは変数を定義するだけでリアクティブになります。let text = '' let errorMessage = ''
-
storeの使用
→ Svelte.jsではstoreを使用するにはstoreが記述されているファイルをインポートするだけで使用ができます。
また、値を更新するためにはupdate関数を使用します。(詳細は公式ドキュメントが大変わかりやすいので、そちらを参考ください。)
import { todos } from '../stores/todo' . . . todos.update((todo) => { return [...todo, {id, text, 'isDone': false}] })
また、HTMLコードにもifブロックや、eachブロックを使用することで、HTMLコードの中で条件分岐や繰り返し処理を書くことができます。
とても直感的にかけるので、学習コストもすごく低いです。
(他にも、awaitブロックなどもあるので、気になったら調べてみてください。)
// errorMessageがあれば表示
{#if errorMessage}
<p class="main_form_error">{errorMessage}</p>
{/if}
コンテンツ部分の作成 - TODO表示コンポーネント -
次にTODOを表示する部分のコンポーネントを作成します。
注: 先ほどの説明と重複する部分の説明は省きます。
早速完成したTODO表示コンポーネントのコードです。
<script>
import { todos } from '../stores/todo'
import SelectTab from './SelectTab.svelte'
let current = 'all'
const validDoneFlag = (id) => {
todos.update((todos) => {
const result = todos.map((todo) => {
if(todo.id === id) {
todo.isDone = true
}
return todo
})
return result
})
}
$:resultTodoArr = $todos.filter(value => {
if(current === 'done') return value.isDone === true
if(current === 'notDone') return value.isDone === false
return value
})
const changeCurrentStatus = (event) => {
current = event.detail
}
</script>
<div class="main_contents">
<SelectTab
on:changeCurrentStatus={changeCurrentStatus}
current={current}/>
<ul class="main_contents_list">
{#if resultTodoArr.length === 0}
<p class="main_contents_zerocase">0件です。</p>
{:else}
{#each resultTodoArr as todo}
<li class="main_contents_list_item">
<p
style={ todo.isDone ? 'text-decoration: line-through;' : '' }
class="main_contents_list_item-ttl">
{todo.text}
</p>
<button
on:click={validDoneFlag(todo.id)}
style={ todo.isDone ? 'background-color: #C7C7C7;' : '' }
disabled={todo.isDone}
class="main_contents_list_item-button">
完了
</button>
</li>
{/each}
{/if}
</ul>
</div>
先ほど同様、Svelte.js特有の記述を上から説明していきます。
-
コンポーネントのインポート・使用方法
→ Svelte.jsではvueのような単一ファイルコンポーネントを採用しているため、
コンポーネントをインポートして使用することができます。import SelectTab from './SelectTab.svelte'
インポートしたコンポーネントを使用するにはHTMLコード内でモジュールから参照した名前をタグ名として挿入することで使用できます。
また、Propsやディレクティブ等も渡すことができます。<SelectTab on:changeCurrentStatus={changeCurrentStatus} current={current}/>
-
Computedの定義
→Svelte.jsでComputedを定義する場合は、$: {変数名や処理}といった書き方で定義ができます。
$:resultTodoArr = $todos.filter(value => { if(current === 'done') return value.isDone === true if(current === 'notDone') return value.isDone === false return value })
今回は、絞りこみ機能を実装するために、使用しました。
コンテンツ部分の作成 - セレクトタブコンポーネント -
最後に先程の「TODO表示コンポーネント」でインポートしていた「セレクトタブコンポーネント」を作成します。
ここからEmit相当の記述などが入ってくるため、Vueを書いたことのない方は少し戸惑うかと思います。
早速完成した、「セレクトタブコンポーネント」をみていきます。
<script>
import { createEventDispatcher } from 'svelte'
const tabsObject = [
{
'title': '全て',
'statusName': 'all',
},
{
'title': '完了',
'statusName': 'done',
},
{
'title': '未完了',
'statusName': 'notDone',
}
]
const dispatch = createEventDispatcher()
const changeCurrentStatus = (currentStatus) => {
dispatch('changeCurrentStatus', currentStatus)
}
</script>
<ul class="main_contents_tabs">
{#each tabsObject as tab}
<li
on:click={() => changeCurrentStatus(tab.statusName)}
style={current === tab.statusName ? 'background-color: #fff;' : ''}
class="main_contents_tabs_tab">
{tab.title}
</li>
{/each}
</ul>
- emit相当の機能
vueでいうemit相当の機能が、Svelte.jsにも備わっています。
emitというのは、子コンポーネントでイベントが発火したタイミングで、親コンポーネントに値などを渡すようなイメージです。
またそのタイミングで、親コンポーネントでも関数を実行させる事ができます。
今回は、「タブを押す→押したタブに応じてTODOを絞り込む」という実装にしました。
その際、親コンポーネントで表示するTODOを管理していたため、TODOを追加するフォームコンポーネント(子コンポーネント)から、
親コンポーネントに値(TODO)を渡す必要があったため使用しました。
Svelte.jsでディスパッチを使用するためにはcreateEventDispatcher
メソッドを、
コンポーネントがインスタンス化された時に呼び出す必要があります。
import { createEventDispatcher } from 'svelte' // importして
.
.
.
const dispatch = createEventDispatcher() // 呼び出す
createEventDispatcher メソッドを呼び出した後は実際にイベント発火時に使用するメソッドの実行文を書いていきます。
<li
on:click={() => changeCurrentStatus(tab.statusName)}
.
.
</li>
子コンポーネントではclickイベントが発火した時に、changeCurrentStatusメソッドが発火するように定義しています。(引数には、完了しているかのステータス(文字列)が入っています。)
changeCurrentStatusメソッドの記述は以下のようになっています。
const changeCurrentStatus = (currentStatus) => {
dispatch('changeCurrentStatus', currentStatus)
}
ここで、インスタンス化されたタイミングで変数dispatchに呼び出した、createEventDispatcherメソッドを実行しています。
第一引数に親コンポーネントで実行時に使用する「イベント名」を渡し、
第二引数に親コンポーネントに渡したい「値」を渡します。
これで子コンポーネントでの作業は終了です。
次に受け取る側の親コンポーネントの記述を説明します。
<SelectTab
on:changeCurrentStatus={changeCurrentStatus}>
インポートしたコンポーネントにディレクティブを追加します。
on:{子コンポーネントから受け取ったイベント名}={親コンポーネントで実行したいメソッド名}
と記述することで、子コンポーネントのイベントを受け取ることができます。(言い方合ってるかな、、、違ったら教えてください。)
親コンポーネントのchangeCurrentStatusメソッドの記述は下のようになっています。
const changeCurrentStatus = (event) => {
current = event.detail
}
引数event
に子コンポーネントのdispatch関数で、第二引数に渡された値を引数に渡しています。
渡された値を参照するには、{引数名(今回はevent
)}.detailで参照することができます。
これで親コンポーネントでの作業も終了しました。
TODOアプリの完成です。最終形はこちらから見れますので、ぜひ見てみてください。
所感
自分が初めて触ったフレームワークがvueだったため、構文も似ていてとても描きやすかったです。
フロントエンドの言語が初心者の方がプログラミングとは何か、を学ぶ目的で使用するのも向いているんじゃないかと感じるくらいに簡単でした。
また、ビルドなども高速で開発のストレス軽減されたり、記述が少なくて済むような仕組みが充実しているため、
開発者視点からはスピーディーに、かつ効率的に開発できると感じました。
他にも、TypeScriptがサポートされていたりアニメーションの実装が楽にできたりなど、メリットがたくさんありました。
ですが、まだドキュメントが少なかったり不具合があったりなどするので、大規模であったり堅牢性が求められるプロジェクトには向かないかもしれないので、個人学習や小規模のアプリをスピーディーに構築したいという方は、十分に使ってみる価値はあると思います。
#最後に
大変長くなりましたが、最後まで読んでいただきありがとうございます。
また、この記事内で紹介したのは、Svelte.jsの機能のあくまでも一部のみになります。
本格的に使えるようになりたい!という方は公式ドキュメントをみて見てください!!
また、間違い等ありましたら、コメントいただけると嬉しいです。