Svelte と Riot v3 の文法比較
はじめに
Svelte という JavaScript フレームワークについて, 今更ながら本気でキャッチアップして色々と良さに気づいたので久々にエントリーを書いてみました
今まで spat(自作の Riot 用フレームワーク. Vue でいう Nuxt 的なもの)でプロダクトを作ってきたんですが, 久々に Svelte をキャッチアップしていたらかなり機能が改善, 追加されて実用的になっていたので実際に一部のプロダクトで採用してみてたりしています.
そんな中で、学んだ Svelte と Riot とのシンタックス的な違いをまとめてみました.
Riot から Svelte に最短で移行したいって人はそこそこキャッチアップの参考になるかと思います!(弊社のメンバーぐらい?w)
※以下 Riot と書いているのはすべて v3 についてになります.
ベース
コンポーネントのベースとなる書き方になります.
Riot
<my-component>
<!-- view -->
<h1>{title}</h1>
<!-- style -->
<style>
:scope h1 {
font-size: 26px;
}
</style>
<!-- script -->
<script>
this.title = 'Hello, Riot!';
</script>
</my-component>
Svelte
Svelte の場合は, 基本 import して使うのでファイル内にコンポーネント名を指定せずにいきなり HTML を書いていく形になります.
script, style, view の順番についても順不同です.
Svelte ではそのコンポーネント内のローカル変数を View に埋め込むことができます.
style については, Riot で必要だった :scope
や :root
といった疑似要素は不要です.
<!-- script -->
<script>
let title = 'Hello, Svelte!';
</script>
<!-- style -->
<style>
h1 {
font-size: 26px;
}
</style>
<!-- view -->
<h1>{title}</h1>
テンプレート変数(式の埋め込み)
ほぼ書き方は同じで { /* 式を書く */ }
という形で HTML 内に変数を埋め込んだり処理の実行結果を表示したりといったことができます.
Riot
<div>{2 + 4}</div>
<div>{Math.round(5.5)}</div>
Svelte
<div>{5 + 10}</div>
<div>{Math.round(4.5)}</div>
イベントリスナー
Riot
onclick
属性にメンバ関数を渡すことでイベントリスナーを登録することができます.
<button onclick='{hello}'>Hello</button>
<script>
this.hello = () => {
alert('Hello, Riot!');
};
</script>
Svelte
on:click
属性に(ローカル)関数を渡すことでイベントリスナーを登録することができます.
<script>
let hello = () => {
alert('Hello, Riot!');
};
</script>
<button on:click={hello}>Hello</button>
クラス属性
Riot
オブジェクトを渡すと value の結果が true の key のみをクラスとして出力してくれます
<div class="{bold: true, red: red}">Hello</div>
<script>
this.red = false;
</script>
Svelte
class:クラス名={条件}
という形でクラスの出し分けができます.
またクラス名とフラグの名前が一致している場合は value を省略できます.
<script>
let red = false;
</script>
<div class:bold={true} class:red>Hello</div>
条件分岐(if 文)
Riot
if='{条件式}'
で出し分けすることができます.
<div if='{flag}'>
...
</div>
Svelte
{#if 条件式} ~ {/if}
で囲うことで出し分けが可能です.
また間に {:else}
や {:else if 条件式}
を挟むこともできます.
{#if flag}
<div>
flag が true のとき
</div>
{:else}
<div>
flag が false のとき
</div>
{/if}
svelte-preprocess 経由で pug で書いている場合は以下のように別の記法が用意されています.
+if('flag')
div flag が true のとき
+else
div flag が false のとき
ちょっとネストしていくのは違和感ありますが慣れましょう!
ループ処理(for, each)
Riot
each in でループ処理を実現できます.
<div each='{item,index in items}'>
<h3>{index}. {item.title}</h3>
<p>{item.description}</p>
</div>
Svelte
{#each 配列 as 要素,インデックス} ~ {/each}
という形でループ処理を実現することができます.
{#each items as item,index}
<div>
<h3>{index}. {item.title}</h3>
<p>{item.description}</p>
</div>
{/each}
if 分同様 pug の場合は別の記法が用意されています.
+each('items as item,index')
h3 {item.title}
p {item.description}
データバインディング
Riot
ref
属性に名前をつけると this.refs[ref属性に付けた名前]
でアクセスできます.
<input ref='name' />
<div>あなたの名前は: {refs.name.value} です!</div>
Svelte
bind:value
に紐付けたい変数を指定することで要素に JavaScript からアクセスできるようになります.
<script>
let name = '';
</script>
<input bind:value={name} />
<div>あなたの名前は: {name} です!</div>
子コンポーネントへの変数受け渡しと参照方法
Riot
<app>
<item-article item='{item}'></item-article>
<script>
this.item = {
title: 'Hello, Riot!',
};
</script>
</app>
<item-article>
<h3>{opts.item.title}</h3>
</item-article>
Svelte
<script>
let item = {
title: 'Hello, Svelte!',
};
</script>
<Article item={item}></Article>
<script>
export let item;
</script>
<h3>{item.title}</h3>
yield, slot
Riot
配置側で設置した content は, 子コンポーネント側で yield タグを使うことで参照できます!
(v4 以降では slot)
<app>
<article title='Hello, world!'>これは解説エントリーです!</article>
</app>
<article>
<h3>{ opts.title }</h3>
<yield />
</article>
Svelte
Svelte では slot タグで親側で配置した content を参照, 展開できます!
<script>
import Box from './Article.svelte';
</script>
<Article>
<h2>Hello!</h2>
<p>This is a box. It can contain anything.</p>
</Article>
<div class="article">
<slot></slot>
</div>
mount イベントリスナーの登録
Riot
<script>
this.on('mount', () => {
...
});
</script>
Svelte
<script>
import {onMount} from 'svelte';
onMount(() => {
...
});
</script>
イベントの発火
Riot
<inner>
<button onclick='{hello}'></button>
<script>
this.hello = () => {
this.trigger('action', {
type: 'press',
});
};
</script>
</inner>
<app>
<inner ref='inner'><inner>
<script>
this.refs.inner.on('action', (e) => {
alert(e.type);
});
</script>
</app>
Svelte
createEventDispatcher を生成してそれを呼ぶことでイベント発火することができます.
<script>
import {createEventDispatcher} from 'svelte';
const dispatch = createEventDispatcher();
function hello() {
dispatch('action', {
type: 'press',
});
}
</script>
<script>
import Inner from './Inner.svelte';
function handleMessage(event) {
alert(event.detail.type);
}
</script>
<Inner on:action={handleMessage}/>
HTML をそのまま表示する
そういった場面も出てくると思います.
ただサニタイズされなくなるのでユーザーの入力した値等では使わないよう気をつけてください.(XSS攻撃)
Riot
riot では html をそのまま展開するシンタックスは用意されていないので無理やり innerHTML に代入するしかありません.
<raw>
<span></span>
this.root.innerHTML = opts.content
</raw>
Svelte
Svelte では {@html ...}
という記法で簡単に HTML をそのまま展開することができます!
<script>
let message = "Hello, <b>Svelte</b>!";
</script>
<div>{message}</div>
<div>{@html message}</div>
mount するコンポーネントを変数化
タブバーに対応したコンテンツを表示する際等に応用できます.
Riot
Riot では data-is 属性に文字列を渡すとその名前のコンポーネントを展開してくれます.
変数も使えるのでそこに
<!-- item-book タグが展開される -->
<div data-is='item-{component}'></div>
<script>
this.component = 'book';
</script>
Svelte
Svelte では <svelte:component this={コンポーネント}>
で動的にコンポーネントを展開することができます.
<script>
import Book from './Book.svelte';
let component = Book;
</script>
<!-- Book コンポーネントが展開される -->
<svelte:component this={component}/>
動的 mount
独自コンポーネントライブラリを作ったりする際には JavaScript 完結でコンポーネントを生成したり
コントロールしたりといったことが必須になってきます.
以下はその方法になります.
Riot
riot.mount()
関数にマウント対象となる DOM 要素, タグ名, オプションを渡すことでマウントすることができます.
let app = riot.mount(elm, 'app', {
item: item,
})[0];
Svelte
Svelte では import した svelte コンポーネントそのものが mount するために関数になっています.
その関数に対象となる DOM要素(target), オプション(data) を渡すことで JavaScript 完結でマウントすることができます.
import App from './App.svelte';
let app = new App({
target: elm,
data: {
item: item,
}
});
Svelte の便利機能
Riot にはなくて Svelte にだけある便利機能をまとめました
グローバルな関数登録
以下のようにコンポーネント内でのみ有効にしたい window や document に登録する関数を指定することができます.
<svelte:window on:scroll={hoge} />
<svelte:window on:keydown={handleKeydown}/>
こう書くことでこのコンポーネントが破棄(destroy)されたタイミングで removeEvnetListener が自動で呼ばれるようになり管理コストが減ります.
TODO: その他諸々追記してく予定...
おわりに
個人的にはずっと Riot, 特に v3 の思想やシンタックスが大好きで色々と足りないところは fork して拡張しながら実際のプロダクトで作ってきていたんですが, 色々と模索するなかで課題もあり, Svelte といった後発のイケてるライブラリもキャッチアップしていかないとなと改めて感じました.
Svelte/SvelteKit の良いところを学んで, また Riot に戻ってきてそれまでに学んだ知見を活かしていくなんて道もまだ諦めてなかったりします.
どういう結果になるにしろ, いろんな技術やライブラリを学んで開発者の思想に浸りながら開発していくのは楽しいものですね.
完全に社内メンバー向けエントリーで, かつマニアックなライブラリの対比にはなってしまったんですが参考になれば幸いです.
Discussion