はじめに
https://svelte.jp/
皆さんはSvelte使っていますか?
私はReactやVueをよく利用していましたが、最近Svelteにハマっています。
Reactヘビーユーザーの私が感じた、Svelteの良いところ、改善してほしいところを挙げてみました。
Svelteの良いところ
Reactを使う上で特に不満があったわけでもないですが、Svelteに乗り換えてみると無駄が多かったことに気付かされました。
私の思うSvelteの良いところをいくつか挙げさせていただきます。
書きやすい、分かりやすい
SvelteはReactと同様に、宣言的にUIを記述することが出来ますが、Reactと比べてより少ないコードで書けるようになっています。
例えば、React.useEffectのような記述は、以下のように書くことが出来ます。
let a = 0;
let b = 0;
let x = 0;
$: {
x = a + b;
}
React.useMemo/useCallbackは以下のように記述します。
$: x = a + b;
useEffectの第2引数のようなものを渡す必要もなく、シンプルに使うことができます。
上の例だと、aやbが変更されると自動的にxの値も変更されるようになっています。
見慣れない書き方なので初めは戸惑いましたが、$:はリアクティブに動作すると覚えておけば楽です。
また、ReactのuseStateのようにsetterを別で定義する必要がないので扱いやすくなっています。
const [num, setNum] = React.useState(0);
// 更新する場合は
setNum(1);
let num = 0;
// 更新する場合は
num = 1;
パフォーマンスが良い/バンドルサイズが小さい
https://svelte.dev/blog/virtual-dom-is-pure-overhead
パフォーマンスの良さはSvelteを活用する最大の理由といってもいいと思います。
SvelteはReactと違いVirtualDOMを使いません。
Reactの場合は、ステートが更新される度にrender関数が呼ばれ、
仮想DOMを生成し、前回のスナップショットと比較した差分をもとにリアルDOMを更新します。
一方でSvelteで書いたソースは、ビルドに直接DOMを変更するコードに変換されるため、差分検出をしなくていい分、高速に動作します。
個人的にパフォーマンスの違いを大きく感じたのはフォームを作成したときでした。
Reactでフォームを作る際に、input要素のonChangeイベントでステートを更新しようとすると、
ユーザーが文字を入力するたびにrender関数が呼ばれてしまうので画面が非常に重くなります。
そのためフォームを作る際はちょっとした工夫が必要で、意外と手間だったりします。
Svelteの場合は、input要素に直接バインディングしても画面全体が重くなることはありません。
なので直感的にフォームを作成することが出来るので、シンプルな作りになります。
またReactの場合はライブラリのランタイムがありますが、Svelteはライブラリのランタイムがほとんどなく、バンドルサイズが最小限で済みます。
svelte/storeが便利
SvelteはStoreという機能があり、ReactでいうReduxやVueでいうところのVuexに当たります。
複数コンポーネントや、Svelteコンポーネント以外のJavaScriptコードでステートを参照/更新したい場合に使います。
ReduxやVuexと違い使い方はシンプルで、以下のように記述します。
const x = writable(0);
let a = 0;
let b = 0;
$: {
a = $x + 1;
}
$: {
x.set(b);
}
Storeの値を参照する際は$を先頭に付けます。
更新したい場合は、方法はいくつかありますが、x.setのようにsetメソッドを使います。
他にも、derivedを使うことで、React.useMemoのようなことがStoreでも利用することが出来ます。
const y = derived([x], ([$x]) => $x + 1);
$: {
console.log($y);
}
StoreはContext APIを使うことで、Propsとしてコンポーネントに渡さずとも、他のコンポーネントで活用することができます。
https://svelte.dev/tutorial/context-api
Storeについても高パフォーマンスで動作するので、アプリケーション全体で使うデータについても扱いやすいです。
データバインディングが便利
Svelteは便利なデータバインディング機能があります。
以下のように記述するだけで、value
という変数にinput要素に入力された内容が反映されます。
<script>
let value = '';
</script>
<input bind:value={value} />
このデータバインディングが自作コンポーネントについても実装できるので、シンプルなデータのやり取りが可能です。
<script>
let value = '';
</script>
<CustomInput bind:childValue={value} />
<script>
export let childValue = '';
</script>
<input bind:value={childValue} />
また個人的に便利と感じたのはbind:clientWidth/clientHeightです。
以下の例だとdiv要素のサイズ(幅)がwidthという変数にバインディングされています。
<script>
let width = 0;
</script>
<div bind:clientWidth={width}>
aaa
</div>
SVGやCanvasを使ってゴニョゴニョする際にこの機能は重宝しています。
内部的には、iframe要素を活用してサイズの変更を検出しているようなので、Reactでも同様の仕組みを作ることは可能だと思います。
公式Playgroundが分かりやすい
https://svelte.dev/tutorial/basics
細かな機能の使い方などは公式のPlaygroundで解説をしてくれています。
実際にコードをいじったりもできるので分かりやすくて便利です。
Svelteの改善してほしいところ
これまではSvelteを使ってよかったところを紹介してきましたが、正直まだまだな部分もありました。
今後改善されるといいなという気持ちでいくつか挙げていきます。
一ファイル一コンポーネントの制約
この辺はVueと似たような話ですが、1つのファイルにつき1つのコンポーネントしか定義できないのは不便だと感じてしまいました。
Reactの場合は一つのファイルに複数のコンポーネントをexportすることが出来たため、細かなパーツの再利用が簡単にできました。
Svelteの場合はいちいちファイルを分ける必要があるため少し手間でした。
周辺ライブラリが少ない
Svelteを使う上で最大のデメリットがこれだと思っています。
ReactやVueは既に利用者がたくさんいるので、周辺ライブラリなども充実しています。
Svelteはまだまだ利用者が増えている段階なので、正直使い物になるライブラリは少ない印象でした。
特にWebアプリケーションを作るうえで必須とも言っていいUIコンポーネント系のライブラリが少なかったのが残念でした。
私がSvelteでWebアプリケーションを作成した際には、UIコンポーネントごと自作しました。
前述したように全体的に少ない記述で作ることが出来るのでUIコンポーネントの自作もそこまで負担でなく作ることが出来ました。
Svelteコンポーネント間でTypeScriptのジェネリクスが使えない
Svelte自体はTypeScript対応されており、例えばコンポーネントのPropsの型チェックなどもしてくれます。
しかしジェネリクス機能はまだ未対応なようで、コンポーネント間でジェネリクスの引数を渡すことが出来ないのが不便なこともありました。
GithubのIssueには挙がっていたのでボチボチ対応されることを願います。
https://github.com/sveltejs/language-tools/issues/442
React Portalみたいなことができない
https://ja.reactjs.org/docs/portals.html
ReactにはPortalという親コンポーネントのDOM階層外にあるDOMノードに対して子コンポーネントをレンダーするための仕組みが提供されています。
だいぶ細かな機能ですが、UIコンポーネントのモーダル機能を自作した際に、このPortalのような機能がないのが不便に感じました。
ちょっと裏技チックな方法で実装することが出来ましたが、Reactのように公式で対応してくれると安心して使えるので期待して待とうと思います。
オブジェクトでstyleの記述ができない
こちらも地味な機能ですが、Reactだと可能だったオブジェクト形式でのstyle指定がSvelteだと出来ないのが残念でした。
<div style={{ width: 100, height: 100, display: 'inline-block' }} />
<div style="width:100px;height:100px;display:inline-block" />
Svelteの場合は、普通のHTMLと同様に文字列でのスタイル指定をする必要がありました。
ステートに応じて幅・高さを変えたいシーンもたくさんあったので、記述のたびに面倒だと感じました。
こちらについても多少無理やりですが、スタイルのオブジェクトを文字列に変換する関数を作成して対応しました。
ErrorBoundaryがない
ReactにはReact.Suspenseというコンポーネントのレンダリング時にエラーが発生した場合にフォールバック(代替表示)を提供する仕組みがありました。
React.Suspenseを使わないと、エラーが発生した際に画面全体が真っ白になりますが、
使うことでエラーが発生した場合に、代替表示されるようになります。
try/catchのコンポーネント版みたいなものです。
SvelteにはこのErrorBoundaryの機能が提供されていないため、エラーが発生すると画面が固まってしまいます。
これについてはいい対処法が探しても見つからなかったので、エラーが起こらないように気を付けることしかできませんでした。
公式が機能を提供してくれるのを待つしかないですね。
まとめ
Svelteを使うことで、とにかく少ないコードでハイパフォーマンスなWebアプリケーションを作ることが出来ました。
一方で、Svelteにはまだまだ改善点もたくさんあるので温かい目で見守ることにします。
今回挙げたような内容もGithubのIssueで議論されていたりするので、見てみるのも面白いと思います。
実はこんな対処法があるよ!などあればぜひ教えてください。