目的
本記事では、Svelteを紹介しながら、TypeScriptとSvelteを使ってリストアプリを作っていきます。
背景
現代のWeb開発ではUXを重視するようになりました。PHPでWebサイトを作ることももちろんありますが、React、Vue、Angularなどのフレームワークを導入したアプリが増えているのは書くまでもないでしょう。
しかし、Reactを学習した方ならわかると思いますが、なんだか、くどいところがあるよねぇ、という感想を持たれたことはあるでしょう。
また、現実的な問題としてどのフレームワークもBrowserでCompileするため、フレームワークごとBrowserまで送らないといけないのです。Angularだと、400kb以上の情報量になります。
そこで、Svelteが入ってくるのです。
Svelteは米New York Timesによって開発された、フレームワークではなく、Compilerなのです。ReactとVueと同じようなComponentの考え方を元に、Hooksのくどい部分をなくしています。
また、Deployする時に純粋なJavaScriptにCompileされるため、プロジェクトの軽量化ができ、Performanceも抜群。
上記のようなメリットがあるから、ますます注目されているのです。
特に、Reactのくどさ、導入のしづらさを理由にVueを選ぶ日本では、Svelteは評価されるのではないかと筆者は考えています。
Appを作ってみましょう!
この記事はSvelteの公式ドキュメントと下記のMozillaの記事を参考にして筆者が学習しながら書いていくようなものです。
まず、任意の場所でdegitでTypeScriptのテンプレートをダウンロードしましょう。
#svelte-appのところはApp名
npx degit sveltejs/template svelte-app
そこにTypeScriptも追加したいので、次のCommandを実行しましょう。
cd svelte-app
node scripts/setupTypeScript.js
npm install
そうすると、tsconfigのファイルが追加され、TypeScriptの準備はこれで終わり!サクサク簡単。
筆者はVS Codeでやっていますが、まだ使ってない方は... 使いなさい。No if and or butsです。
Svelteの拡張もあるので、インストールしておきましょう。
package.jsonを見ると、run devのCommandがよさそうなので実行してみましょう。
npm run dev
じゃあ、HOSTの変数を指定して実行してみましょう。
HOST=0.0.0.0 npm run dev
(お恥ずかしい、別の何かでPort5000ちゃんを使っていたみたい)
ちゃんとアプリが出てきました。
Svelteの構造
main.tsを見てみましょう。
import App from './App.svelte';
const app = new App({
target: document.body,
props: {
name: 'Austin'
}
});
export default app;
ここでSvelteがAppというComponentを読み込んで、targetとpropsの設定を入れて立ち上げています。
targetは、DOMのどこにアプリを落としていくかを設定します。
PropsはApp.svelteにどのような変数を渡すかを指定できます。試しにAustinに変えてみます。
App.svelteを見てみましょう
<script lang="ts">
export let name: string;
</script>
<main>
<div>{name}</div>
</main>
<style>
main {
text-align: center;
padding: 1em;
max-width: 240px;
margin: 0 auto;
}
h1 {
color: #ff3e00;
text-transform: uppercase;
font-size: 4em;
font-weight: 100;
}
</style>
上から解説します。
Script: ここでコードを落とします。ここで指定する変数はそのまま下記のMainで使えます。
ここで、あれ、なんでnameをexportしてんの?main.tsからここに落としたんだはず、と思いますよね。svelteのpropsの設定は独特で、export let [変数名]という形式でやっています。
Main: ここはHTMLのところです。上記の変数を落としたい時はReactと同様に{}を使います。ちなみに、<main>じゃなくても、<div>, <form>など、どのHTMLタグでも大丈夫。
Style: ここはCSSのところで、svelteはデフォルトで全てをScoped(同Component内のみ適用される)にしています。Scriptの変数をここで使うことができます。
Browserを見ると、無事に変数が表示されています。
Eventを試してみましょう。
まずCounterの変数と足す関数を作って、ボタンでそれを実行するようにしてみましょう。
<script lang="ts">
export let name: string;
let counter = 0;
const increaseByOne = () => {
counter++;
};
</script>
<main>
<div>{name}</div>
<div>Counter: {counter}</div>
<button on:click={increaseByOne}>Add 1</button>
</main>
結果オーライ
すでに面白いことが起きてますね。気がついたと思いますが、僕はHookを使わずにRe-Renderをさせているのです。はい、私は神となり、Hookを超えました。
ここが、svelteの嬉しいところなんです。JavaScriptがくどくない。直接変数を変えればいいのです。
Componentを読み込んでみましょう
まずsrc/にcomponentsのフォルダーを作って、そこにさらにUIのフォルダーとそこに入るCard.svelteを入れてから、NewItem.svelteをsrc/componentsに落としましょう。
こうやって<slot />を使ってReactでいう{props.children}もしくはVueのそれと同じことができます。
<div class="card">
<slot />
</div>
<style>
.card {
width: 90%;
max-width: 500px;
margin: 1rem auto 0 auto;
border: 1px solid white;
border-radius: 8px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.24);
padding: 1rem;
}
</style>
次はリストに追加するItemのformをNewItem.svelteに入れてみましょう。
ここで、svelteのbind機能と、event修飾について解説します。
inputではbind:valueでtitleの変数を紐付けします。HTMLオブジェクトそのものもBindできますよ。
on:submit|preventDefaultは自動でevent.preventDefaultを実行してくれます。ここはVueと似てますね。
<script lang="ts">
export let addNewItem: (
title: string,
contents: string
) => void;
let title: string;
let contents: string;
const handleSubmit = (event: Event) => {
addNewItem(title, contents);
};
</script>
<form class="form" on:submit|preventDefault={handleSubmit}>
<div>
<label for="title">Title</label>
<input type="text" name="title" bind:value={title} />
</div>
<div>
<label for="contents">Contents</label>
<input type="text" name="contents" bind:value={contents} />
</div>
<button type="submit">Add Item</button>
</form>
<style>
.form {
display: flex;
flex-direction: column;
text-align: left;
}
.form div {
margin-bottom: 1rem;
}
.form label {
margin-bottom: 0.3rem;
}
.form input {
width: 100%;
}
</style>
App.svelteでリストを作っていきましょう。
解説:$:は上記で指定されている変数の値を見て計算される変数。VueのComputedと一緒。
<script lang="ts">
import NewItem from "./components/NewItem.svelte";
import Card from "./components/UI/Card.svelte";
const list: { title: string; contents: string }[] = [];
const addNewItem = (title: string, contents: string) => {
list.push({
title,
contents,
});
};
$: isEmpty = !list.length;
$: console.log(list, isEmpty);
</script>
<main>
<Card>
<NewItem {addNewItem} />
</Card>
</main>
しかし!このコードに問題があるのです。
そう、svelteは変数を再指定しないと、Rerenderをしないのです!
だから、pushではなく、unpackして新しいArrayに入れるのが正解です。
let list: { title: string; contents: string }[] = [];
const addNewItem = (title: string, contents: string) => {
list = [...list, { title, contents}];
};
そうすると、こうやってうまくいくんだよね(やってみんとわからんことだらけ)。
ここからはただComponentを増やしてリストを表示させます。
<script lang="ts">
import NewItem from "./components/NewItem.svelte";
import Card from "./components/UI/Card.svelte";
import ItemList from "./components/ItemList.svelte";
import type ListItem from "./models/list-item"
let list: ListItem[] = [];
const addNewItem = (title: string, contents: string) => {
list = [...list, { id: Math.random().toString(12),title, contents}];
};
</script>
<main>
<Card>
<NewItem {addNewItem} />
</Card>
<Card>
<ItemList {list} />
</Card>
</main>
<style>
main {
text-align: center;
padding: 1em;
max-width: 240px;
margin: 0 auto;
}
</style>
if, :else if :else /ifという感じでロジックが簡単にできます。
<script lang="ts">
import type ListItem from "../models/list-item";
export let list: ListItem[];
$: isEmpty = !list.length;
</script>
<section>
{#if isEmpty}
<p>No Items in List</p>
{:else}
<ul>
{#each list as item (item.id)}
<li>
<h3>{item.title}</h3>
<p>{item.contents}</p>
</li>
{/each}
</ul>
{/if}
</section>
<style>
ul {
list-style-type: none;
padding: 0;
margin: 0;
}
li {
margin: 0 0 1rem 0;
padding: 1rem;
border: 1px solid grey;
border-radius: 4px;
text-align: left;
}
h3 {
margin: 0 0 0.5rem 0;
}
</style>
## まとめ
筆者はsvelteについて何も知らない状態で1時間半で本記事を書いたことでわかるように、svelteは簡単でやりやすい!
個人的な感想として、VueよりもVueらしい。Vueがやろうとしていることをくどくせずに綺麗にできている感じです。特にVueはCompositional APIでReactっぽいReactじゃない怪物になったので、svelteはいい代わりになるかもしれません。