謝罪とはじめに
担当日が酷い3日酔いのため掲載が遅れて本当にごめんなさい。これからは掲載の日は気合で記事書いていこうと思います。楽しい忘年会でした
こんにちはMYJLab Advent Calendar 20日目 今日は個人的にいいことがありました阿左見です。
前回はTDDについて書きましたが今回はSvelteについて書いていこうと思います。
2021年最も愛され、スポットを浴びたフレームワークなsvelteは最近svelte summitなるものを開催しました。
なんか熱そうなので興味を持った次第です。
では、書いていこうと思います。
Svelteとは何か
- Write less code
- No virtual DOM
- Truly reactive
公式サイトによるとこの3つが特徴として挙げられています
個人的に特筆すべきはNo virtual DOMの部分かなと思っております。
また同サイトではtraditional frameworks like React and Vue doとか書かれており、仮想domがtraditionalはちょっと辛い。ついこの間まで仮想DOMしか勝たんとか言ってた筈では
仮想DOMは遅い!?
またまた公式サイトですが、Virtual DOM is pure overhead、副題にLet's retire the 'virtual DOM is fast' myth once and for all という記事があります。仮想DOM速いよ神話を終わりにしよう。だそうです。reactあたりにしっかり喧嘩売ってる気がします。
果たして本当に仮想DOMは遅いのか
結論から書きます。仮想domは大抵、十分に速いです最近のReactですとFiverが導入されたりとdomの更新をより小さな単位に分割できるようになりました。ReactやVueで用いられている差分検出のアルゴリズムは高速です。しかし、仮想DOMは機能ではないことがこのsvelteの存在意義であるといえます。あくまでも手段である仮想DOMは宣言的で状態駆動型のUI開発を目的としています。仮想DOMをしようせずに同様のプログラミングモデルを実現できることが分かったのです。(=svelteの登場)
結局svelteってライブラリなの?フレームワークなの?
今までVueやReactとの比較をつらつらと書き連ねてきているので一体svelteってなんだよって思いますよね。
この疑問へはフレームワークでもライブラリでもないと答えるのが最善かと思います。Svelteとはコンパイラです。
単純に書く側の観点から見るとVueの単一ファイルコンポーネントに似ているのでフレームワークな気もしますが、厳密にはSvelteはコンパイラです。
ReactやVueはブラウザがコンパイル等の動作を行いますが、svelteは先にやっちゃうのでsvelteの方が早いよってことらしいです。
コードで比較
説明するのが段々と辛くなってきました。日本語の記事少なすぎ問題はあると思います。
エンジニアならコード見た方が早いもんね!!!
と言うことでReactとSvelteで簡素なtodoアプリを作成していければと思います。SvelteもTypeScript対応してるのでTypeScript使っていきます。
今回のコードはhttps://github.com/renasami/advent にあげています。よければスターしてください。
React編
普段通りnpx create-react-app --template typescript .
で作成していきます
create-react-appはアップデートされてるらしくcreate-react-app@5.0.0
で作成できました。
http://localhost:3000 にアクセスするといつもの画面が現れるかとお思います。
今回のディレクトリ構成はこんな感じです。
src
|-App.tsx
|-Task.tsx
|-type.
はい。これだけです。非常にシンプルなtodoにしました。
import React, { MutableRefObject, useRef, useState } from "react";
import Task from "./Task";
import { TaskType } from "./types";
function App() {
const [tasks, setTasks] = useState<TaskType[]>([]);
const ref = useRef(null);
const addTask = (ref: MutableRefObject<any>) => {
if (!ref.current.value) return;
const newTask: TaskType = {
id: tasks.length > 0 ? tasks[tasks.length -1].id + 1 : 0,
text: ref.current.value,
done: false,
};
setTasks([...tasks, newTask]);
ref.current.value = "";
};
return (
<>
<h1>react todo</h1>
<hr />
<input type="text" placeholder="タスク入力" ref={ref} />
<button onClick={() => addTask(ref)}>追加</button>
<ul>
{tasks.map((task,i) => {
if (task.done) return null
const props = {
...task,
setTasks: setTasks,
tasks: tasks,
};
return <Task {...props} key={i} />;
})}
</ul>
</>
);
}
export default App;
import React, { Dispatch, useRef } from 'react';
import { TaskType } from './types';
type Props = TaskType & {
setTasks:Dispatch<TaskType[]>,
tasks:TaskType[]
}
const Task:React.FC<Props> = (props: Props) => {
const ref = useRef(null);
const handleOnChange = () => {
const newTasks = props.tasks.map((task,i) => {
if (props.id !== i) return task
return {
id: task.id,
text: task.text,
done:!task.done
}
})
props.setTasks(newTasks);
}
return (
<li ref={ref} style={{display: "flex", margin:10}}>
<button onClick={handleOnChange}>削除</button>
<p style={{margin: 0}}>{props.text}</p>
</li>
)
}
App.tsにはタスクの追加の関数、Taskにはタスク自体を削除する関数を書いています。コードとして決して綺麗なコードではないですが、reactの状態管理としては基本的な構造じゃないでしょうか。
svelteの記事なので一旦reactはこんな感じでとどめます。
svelte編
ディレクトリ構造はこんな感じです。
src
|-App.svelte
|-Task.svelte
|-types.ts
|-stores.ts
|-main.ts
store...? vueかな...?
<script lang="ts">
import Task from "./Task.svelte"
import {taskList} from "./stores"
let text = ""
const addTask = () => {
if (!text) return
taskList.update((task) => [...task,{
id:task.length,
text:text,
done:false
}])
text = ""
}
</script>
<h1>svelte todo</h1>
<hr />
<div>
<input bind:value={text}/>
<button on:click={addTask}>追加</button>
<ul>
{#each $taskList as task }
{#if !task.done}
<Task task={task}/>
{/if}
{/each}
</ul>
</div>
<script lang="ts">
import {taskList} from "./stores"
import type {TaskType} from "./types"
export let task:TaskType
const handleOnClick = () => {
const newTask:TaskType[] = $taskList.map((t,i) => {
if (task.id !== i) return t
return {
id: t.id,
text: t.text,
done:!t.done
}
})
taskList.update(() => [...newTask])
}
</script>
<li>
<button on:click={handleOnClick}>削除</button>
<p>{task.text}</p>
</li>
<style>
li {
display:flex;
margin:10px;
}
p {
margin:0;
margin-left: 10px;
}
</style>
import { writable } from 'svelte/store'
import type { TaskType } from './types'
export const taskList = writable(<TaskType[]>[])
SvelteほぼVueじゃねえか
これが僕の正直な感想です。Vueの経験がある人は非常に書きやすいと思います。
AppとTaskで行っている処理は一緒です。
簡単に文法の説明だけしていこうと思います。
バインディング
もう名前からvueな感じしますが、バインディングです。コードで言うとこの部分です
let text = ""
<input bind:value={text}/>
直感的にわかりやすいですが、inputの内容がtextに適宜代入されていきます。
イベントハンドラ
この題で合ってるかわかりませんがクリックイベントなどを検知します。
<button on:click={handleOnClick}>削除</button>
例えばこの部分になります。これも直感的ですね。clickした時に関数が発火します。またchange等一般的なイベントにも対応しています。
ロジック
vueでいうv-forであったりreactではmap関数を使用したりして描画する部分になります。
<ul>
{#each $taskList as task }
{#if !task.done}
<Task task={task}/>
{/if}
{/each}
</ul>
この部分です。eachの部分でfor文の役割を果たしています。 ifはifですね。
store
vuexやreduxのようにsvelteでは状態管理をstoreで行うことができます。
import { writable } from 'svelte/store'
import type { TaskType } from './types'
export const taskList = writable(<TaskType[]>[])
writableとういうのがstoreの肝です。これもコードとしては直感的な部分になります。
これを呼び出し先の関数等からアップデートすることがきます。
非常に長くなりすぎるのでこれくらいにしておこうと思います。
vueと比べてみた方が良かったかなと感じています。
まとめ
- Svelteはvueライクな構文で書くことが切る
- Svelteはそもそもライブラリ等ではなくコンパイラである
- 仮想domを用いていない
こんな感じではないでしょうか。個人的にvueの書き方は好きなので非常に書きやすかったです。
仮想domと比較した時のパフォーマンスやもう少し細かい記事は来年の頭にでも投稿しようかなと思います。
まだまだ未熟なのでコード見て気づいた点やsvelteの参考記事など英語でも構わないので有識者の方、よければコメントください。