アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

SvelteでToDoリストを作る

はじめまして、エンジニアの堤です。
かれこれアシアルでは2年弱働いているのですが、初ブログです。 お手柔らかにお願いします。

今回は個人的に気になっているJavaScriptフレームワーク、Svelteを使ってToDoリストを作ってみます。 「また新しいJavaScriptフレームワークか…」とうんざりする人もいるかもしれませんが、 SvelteはこれまでのJavaScriptフレームワークとは一風変わったアプローチですので、 ちょっと触ってみたいと思います。

Svelteとは

Svelte自体はReactやVueと同じく、いわゆるUIフレームワークですが、 上にも書いた通りアプローチが異なっています。 Svelteのホームページにアクセスすると、

The magical disappearing UI framework

というキャッチコピーが目に入ると思います。 直訳すると「魔法のように消えるUIフレームワーク」といった感じでしょうか。 これは一体どういうことかというと、Svelteではコンパイルされて最終的に出力されるコードは、 すべて”純粋な”JavaScriptだけのコードになります。 そのため、非常に軽量かつ高速なフレームワークになっています。 この辺りの設計思想に関して詳しく知りたい方は、公式のブログ記事をお読みいただければと思います。 それでは、以降で実際にSvelteを使ってみます。

Svelteのインストール

Svelteでは公式にREPLが提供されているので、 それを使って試すこともできるのですが、今回はローカルで開発する場合の手順でやってみます。

まず、公式のガイドでおすすめされているdegitをインストールします。
※ Node.js v8以上がインストールされている必要があります。

npm install -g degit

以降もドキュメント通りに実行していきます。 ブラウザでlocalhost:5000を開いて「Hello, World!」と表示されればOKです。

degit sveltejs/template todo-list
cd todo-list
npm install
npm run dev

これでSvelteを使う準備自体はできたのですが、追加でsvelte-extrasをインストールします。 このライブラリはSvelteで配列操作を行ったりする場合に必要になるものです。

npm install --save svelte-extras

ToDoリストを作る

以上で準備ができたので、ToDoリストを作ります。
SvelteではVueのように単一ファイルにhtml、JavaScriptCSSをまとめて書くSingle File Component形式での記述が可能なので、 ここでもそのように記述していきます(とはいえ、今回CSSはほぼありませんが…)。 その際、ファイルの拡張子は.htmlになります。

まずは、ToDoリストの各アイテム用コンポーネントです。

  • src/TodoLIstItem.html
<li 
  class="{ todo.done ? 'todo-li--done': '' }"
  on:click="fire('toggle-todo', { todo, index })"
>
  { todo.title }
</li>

<style>
.todo-li--done {
  text-decoration: line-through;
}
</style>

正直、今回作成するアプリレベルではTodoListItemと分ける必要もないんですが、 子から親へのイベントも扱ってみたかったので分けてます。 具体的には3行目が該当します。 Svelteでは、html内にon:イベント名と書くことでイベントを受け取ることができます。 ここではクリックされた際にtoggle-todoイベントを発火するようにしています。 あとは、2行目でToDoが完了の場合のみCSSのクラスを当てるようにしています。

続いてToDoリストを表示するコンポーネントです。

  • src/TodoList.html
<input bind:value="title" type="text">
<button on:click="addTodo(title)">追加</button>
<ul>
  { #each filteredTodos as todo, index }
    <TodoListItem {todo} {index} on:toggle-todo="toggleTodo(event)"/>
  { /each }
</ul>
<button on:click="set({ condition: null })">すべて</button>
<button on:click="set({ condition: true })">完了のみ</button>
<button on:click="set({ condition: false })">未完了のみ</button>

<script>
import { push, splice } from 'svelte-extras';

export default {
  components: {
    TodoListItem: './TodoListItem.html',
  },
  data() {
    return {
      title: '',
      todos: [],
      condition: null,
    };
  },
  computed: {
    filteredTodos({ todos, condition }) {
      return condition !== null ? todos.filter(todo => todo.done === condition) : todos;
    },
  },
  methods: {
    push,
    splice,
    addTodo(title) {
      this.push('todos', { title, done: false });
      this.set({ title: '' }); 
    },
    toggleTodo({ todo, index }) {
      this.splice('todos', index, 1, { ...todo, done: !todo.done });
    },
  },
};
</script>

ここでようやくJavaScriptが出てきました。 Svelteでは、scriptタグ内にコンポーネントに関連するJavaScriptの処理を記述します。
componentsプロパティには、使用する子コンポーネントを定義します。 ここでは先ほどのTodoListItemですね。
data関数では、コンポーネントで使用するデータを初期化します。 ToDoを追加するためのtitle、ToDoの一覧を保持するためのtodos、完了/未完了で表示を切り替えるためのconditionを定義しています。
computedプロパティでは、他の値に依存して変化する値を定義します。 今回は完了/未完了で表示を切り替えたいので、conditionの値に連動して表示するToDoをフィルタリングするよう、ここで記述しています。
methodsプロパティでは、主にhtmlテンプレート内で呼び出す関数を定義します。 addTodoは文字通り、ToDoを追加するための関数です。 単純に、todosに新しいToDoを追加した後、titleを初期化しています。 Svelteでは、配列に要素を追加するのはsvelte-extraspush関数で行います(35行目)。 また、単純な値の更新はset関数を使います(36行目)。 この関数は「追加」ボタンが押されると呼び出されます。
toggleTodoは、ToDoの完了/未完了状態を切り替えるための関数です。 5行目で、子コンポーネント(TodoListItem)からのイベントを受け取ると、この関数を呼ぶように記述しています。

最後にmain.jsを以下のように書き換えましょう。

import TodoList from './TodoList.html';

const todoList = new TodoList({
  target: document.body,
});

export default todoList;

以下の画像のように表示されるはずです。 せっかくですので、色々いじって試してもらえればと思います。

f:id:tsutsumi-asial:20180826215745p:plain

終わりに

と、ここまでSvelteを触ってきましたが、Vueのシンタックスにかなり似ています。 なので、Vueを触ったことのある人であればかなり馴染みやすいのではないでしょうか。

ただ、Vueと同じ感覚で書いていると、割と色んなところでハマるな、といったのが自分の印象です。^^;
とはいえ面白いフレームワークだと思いますし、一度Svelteでちゃんとしたアプリも作ってみたいなー、と思いました。 今回のToDoリストでは売りのパフォーマンスの部分も全くわからないので。。。

また機会があればSvelteについて書いてみようと思います。 お読みいただきありがとうございました。