Blog

Svelteチュートリアル(Part1)やってみた

この記事は、リレーブログ企画「24卒リレーブログ」の記事です。

はじめに

初めまして、新卒1年目の緑川です。
現在、OJTジョブローテの1期目として、会員システムグループの第二開発チームに所属しています。主な業務はオプションサービスの開発と運用であり、フロントエンド・バックエンド問わず様々な技術を日々学んでいます。
開発にあたってSvelteというフレームワークを使用する場面があり、そのためにSvelteチュートリアルをやってみたので、この記事ではそこで重要だと思った点や難しかった点、知らなかった用語などを自分なりの理解でまとめていきたいと思います。あくまでチュートリアルの説明の補足のようなものなので、そちらを読んでいる前提で執筆します。また、各項目の解説の密度は、自分の理解度に依拠しているために偏りがあり、一部の項は一纏めにしています。
なお、チュートリアルはSvelteとSvelteKit(Svelteの拡張版のようなもの。今回は割愛)それぞれの基本と応用とで4つのPartに分かれていますが、今回はPart1:Basic Svelteをやりました。

Svelteとは

まずSvelteについて説明します。Svelteとは、前述のチュートリアルによれば、

web アプリケーションを構築するためのツールです。他のユーザーインターフェースフレームワークと同様、マークアップ(markup)、スタイル(styles)、振る舞い(behaviours) を組み合わせたコンポーネントでアプリを 宣言的(declaratively) に構築することができます。

Svelte を理解するには、HTML、CSS、JavaScript の基本的な知識が必要です。

https://learn.svelte.jp/tutorial/welcome-to-svelte

だそうです。少し使ってみた感じではHTMLとJS(JavaScript)をシームレスに融合させ、その上簡単に書けるようにしたものといった印象を受けました。自分はJSについても詳しくはないので、本記事で解説する単語の中にはJSのものも含まれています。

Introduction

Svelteにようこそ

ここから解説に入っていきます。
Svelteチュートリアルは以下のような画面で進行します。

左上がファイル構造を表しています。右上がファイルの中身で、ここを変更するとリアルタイムで下の表示に反映されます。

  • フットプリント…プログラムが動作する際のメモリ使用量の多さ
  • コンポーネント…次項で解説される
  • オーバーヘッド…プログラムが行う作業のために間接的に生じる余計な負荷

Your first component, Dynamic attributes

ここでは、変数やコードを中括弧で埋め込むというSvelteの基本的な動作が解説されています。変数の中身にはテキストだけでなく画像も使用することができます。

Styling

これは要するにCSSです。

Nested components

この項目はSvelteを学ぶにあたって重要です。この後もほぼ全ての項でコンポーネントのインポートが行われます。

HTML tags

@htmlによってHTMLタグを使うことができます。これは後述のBindings/Textarea inputsでも出てきます。

  • DOM…Document Object Modelの略。Webページの要素やコンテンツなどをツリー構造で表現したもの
  • brob…Binary Large Objectの略。単にバイナリデータの塊を表現したもの
  • サニタイズ…脆弱性を突こうとする入力を別の表記に置き換え無害化することを指す。 英語では「消毒する」などの意味がある

Reactivity

Assignments

カウントを増やす関数と、カウントを表示するテキストを作り、「クリックした時にこの関数を実行する」とボタンに設定することでそれらを結びつけています。

Declarations, Statements, Updating arrays and objects

他の変数に依存する変数は、$:をつければ自動的に連動計算してくれます。また、$:は変数だけでなく文やブロックなどにも付与できます。ただし、連動計算は代入によって起こるものであり、リストに追加する場合はもう一工夫必要だそうです。
このリアクティブ宣言という概念は開発でよく使う印象があります。

  • statements…プログラミングにおける文のこと

Props

Declaring props, Default values, Spread props

Introduction/Stylingの項などでコンポーネント同士の独立性についての話がありましたが、もちろんデータの受け渡しも可能です。受け渡しにあたってはimportexportをセットで使用します。これも開発には欠かせないものです。
プロパティはデフォルト値も設定することができ、しない場合はundefinedになります。また、渡す変数が複数あり、かつその数と名前が一致していれば、一気に代入できます。

Logic

If blocks, Else blocks, Else-if blocks

他のあらゆる言語、フレームワークと同様、if、else、else-ifも実装されています。これらのブロックは#にはじまり/に終わります。elseのような間に挟むものには:がつきます。
HTMLでこのような処理を実装しようとするとJSを組み込む必要がありますが、Svelteであれば地続きの感覚で記述できます。

Each blocks

#にはじまり/に終わるのはifと同じです。配列やそれに類するものをあらかじめ宣言しておけば、eachブロックを利用して一気に代入できます。

Keyed each blocks

ここは少し理解に時間がかかった部分です。
まず初期化時に、Thingコンポーネントにthingsの中身が渡され、emojithingsnameに対応したものがセットされます。thingsが決めているのはnameとコンポーネントの数なので、things = things.slice(1);によって配列の先頭を消すと、コンポーネントはbananaeggの4つになります。しかし、emojiを決めているのはThingであり初期化時点から動かないこと、そしてコンポーネントは末尾から消える法則があることから、ズレが生じるようです。

shiftメソッドを使わずsliceメソッドを用いているのは、shiftがsliceと違って配列を直接変更するものだからです。Reactivity/Updating arrays and objectsで述べられたように、リアクティビティは代入によってトリガーされるため、shiftメソッドを使う場合は以下のように記述する必要があり、冗長になります。

Await blocks

utils.jsのgetRandomNumber()は、まず/random-number というAPIエンドポイントにリクエストを送信して結果が帰るまで待ち、レスポンスが成功した場合レスポンスの本文をテキストとして取得するのを待ち、それをApp.svelteに渡します。res.ok は通常、HTTPステータスコードが200-299の範囲内にある場合にtrueだそうです。また、waitingメッセージを表示するため、APIにはあえて遅延が組み込まれているようです。エンドポイントの実装はサーバサイド(バックエンド)で行われており、ここからは見えません。

App.svelteはlet promise = getRandomNumber();#await promiseで、一連の処理が終わるのを待ちます。
{number}があるのにlet numberなどがないのは不思議に思えますが、{:then number}getRandomNumber()の返した値をnumberとして割り振っているのだと思われます。

Events

DOM events, Inline handlers, Event modifiers

Reactivity/Assignmentsではbuttonにイベントハンドラを付与していましたが、divに付与することもできるそうです。インラインで宣言することも、修飾子をつけることで条件・属性を追加することもできます。

Component events

Inner.svelteの1行目にimport { createEventDispatcher } from 'svelte';とありますが、これは今までのように他のファイルからではなくsvelteパッケージから関数をインストールしているということです。
ここでは、ボタンを押すとsayHello関数が発火し(実行され)、sayHello関数はmessageイベントを発生させています。App.svelteの方では、messageイベントが発生したのを受けてhandleMessage関数が発火しています。

  • イベントディスパッチャ…イベントを送信するもの。イベントを受信するものはイベントリスナー

Event forwarding, DOM event forwarding

コンポーネントのイベント(前項Component eventsで作ったもの)はバブルしないため、Component eventsでは2層だった構造が3、4層と多層化する場合、中間層はフォワードする(イベントを受け渡す)必要があります。
また、今までbuttonにイベントハンドラを設定する機会が何度かありましたが、もう一つファイルを用意する必要があるような凝ったbuttonの場合は、ファイルをまたぐためにイベントフォワーディングを使用するようです。

  • バブルする(バブリング)…イベントが発生した要素から、親要素を通って最上位の要素まで順番に伝わっていくこと

Bindings

Text inputs

Svelteでは、基本的に変数の更新に応じてその変数を使用するコンポーネントも更新されるという処理がされます。Reactivity/Assignmentsの時も、buttonコンポーネントに足し算関数を発火させるインベントハンドラを定義することで遠回りに表示を更新していました。これも似たようなもので、bindを使ってinputからnameを変更し、nameの変更がh1に反映されるという処理が行われています。

Numeric inputs, Checkbox inputs

numberが数値ボックスでrangeが数値バーです。「input.valueを使わなければならない」という話は、前項の説明の「on:inputイベントハンドラを追加し〜」という箇所と同じ話です。
チェックボックスもほとんど同じ感覚でbindを使用できます。

  • numeric…数値という意味
  • <label>…htmlの要素の一つ。主にフォーム部品を関連づけるために使う

Select bindings

on:change={() => (answer = '')は、セレクトボックスで新しい選択肢を選んだ(=質問を切り替えた)時に、答えの部分を空にするという意味です。{selected ? selected.id: '[waiting...]'}は、selectedが真値である(なんらかの質問を選択している)場合selected.idが表示され、そうでない場合[waiting...]が表示されるということを示しています。ここでは、bindをつけないとselectedが真値になりません。

Group inputs, Select multiple

Group inputsで使用したチェックボックスは、<select multiple>に置き換えることもできるそうです。こうすると記述が短く分かりやすくなりますが、その代わり、NOTEに書かれているように複数選択する際に少し工夫が必要になります。

  • Intl.ListFormat…リストを整形するオブジェクト。オプションを指定することで最終的に表示するものをA, B and Cのような形式にしている

Textarea inputs

import { marked } from 'marked'でマークダウンをHTMLに変換するためのライブラリmarkedをインポートし、@html marked(value)でHTMLに変換した文字列valueをHTMLとして解釈しています。

Lifecycle

onMount

document.querySelector('canvas')では、ドキュメント内の最初の<canvas>要素を選択しています。この方法で HTML 内の<canvas>要素を操作できるようになります。canvas.getContext('2d')では、選択した<canvas>要素の描画コンテキストを取得しています('2d' は 2D 描画コンテキスト)。このコンテキストを通じて、<canvas>上に図形や線を描いたり、色を塗ったりすることができます。「コンポーネントが破棄されてもloopが動く」とは、<canvas>を消すと画像が見た目の上では消えるものの、cancelAnimationFrameがないとバックで処理が継続してしまうということです。

  • svg…画像フォーマットの一種。大きさを自由に変えられる

beforeUpdate and afterUpdate

scrollableDistance = div.scrollHeight - div.offsetHeightでスクロール可能な総距離を計算し、div.scrollTop > scrollableDistance - 20で現在のスクロール位置が下端から20ピクセル以内にあるかチェックしています。div.scrollTo(0, div.scrollHeight)は要素を最下部にスクロールするという意味です。

  • 状態駆動…アプリケーションの状態(データ)に基づいてUIを自動的に更新する方法。Svelteは基本的に状態駆動の考え方に基づいているが、スクロール位置の取得やスクロールの実行はDOMの直接操作を必要とするため実現が難しい

tick

テキストが新しい値に変更されると、古いテキストを選択している情報は失われます。<script>の末尾のthis.selectionStart = selectionStart;this.selectionEnd = selectionEnd;の2行は、現在の選択範囲を過去の選択範囲と同じにするというもので、テキスト変更後も選択範囲を維持しようとしています。しかし、そのままだとテキストの変更がこの2行より後になってしまうので意味がなく、したがってtickが必要となります。

Stores

Writable stores, Auto-subscriptions, Readable stores

Storeにおいて明示的にsubscribeするならばunsubscribeも必要です。$記法(自動サブスクリプション)を使えば自動的にリアクティブな更新をしてくれて、そしてコンポーネントが破棄されるときに自動的にunsubscribeも行ってくれます。Reactivity/Declarationsなどで使用したリアクティブ宣言と自動サブスクリプションは似て非なるものです。
また、Storeは用途によって書き込み可能にしたり読み取りのみ可能にしたりできます。

  • subscribe…データの変更を監視し始めること。unsubscribeはその逆で、不要になった監視を停止すること
  • リアクティブ宣言…$:を使用。依存する値が変更されるたびに自動的に再計算される。コンポーネント内のローカルな状態・計算に使う
  • 自動サブスクリプション…$を使用。Storeの値が変更されるたびに自動的に更新される。グローバルな状態(Store)にアクセスするために使う

Derived stores

他のStoreを参照するStoreです。export const 【exportする変数名】 = derived(【参照するStore】,($【参照するStore】) =>【計算ロジック】);という形式で記述します。参照するStoreを2回書いていますが、1回目はどのStoreから派生するかを指定していて、2回目では実際のStoreの値にアクセスしています。

Custom stores

countという値自体に増減などのロジックを組み込んでいます。increment: () => update((n) => n + 1)JSのオブジェクトリテラル記法におけるメソッド定義の短縮構文で、increment: function() { return update((n) => n + 1); }と同じ意味です。

Store bindings

書き込み可能なStoreはbindしたりコンポーネント内で直接代入することも可能という話です。${$name}は、$が二つ続いていてわかりづらいですが、中の$name がこのStoresの項で学んだことで、外の${}はテンプレートリテラル(JSの文字列を作成するための機能)です。"Hello " + $name + "!”と同じです。

まとめ

今回はSvelteチュートリアルのPart1を体験して学んだことをまとめてみました。評判通り、学習コストが低めで書きやすいという印象でした。まだまだ学習中の身であるため、誤った理解をしている部分もあるかもしれませんが、Svelteの基本を習得しスキルアップした実感を持っています。
Svelteは人気上昇中のフレームワークなので、フロントエンド系の開発に携わっている方は今の内に学んでおくと将来様々な場面で役に立つと思います。自分も学習を続けたいと考えており、次はSvelteKitの基礎であるPart3に挑戦する予定です。

リレーブログ企画「24卒リレーブログ」は、この記事で終了となります。執筆に協力していただいた皆さん、見てくださった皆さん、ありがとうございました。

ニフティでは、
さまざまなプロダクトへ挑戦する
エンジニアを絶賛募集中です!
ご興味のある方は以下の採用サイトより
お気軽にご連絡ください!

ニフティに興味をお持ちの方は
キャリア登録をぜひお願いいたします!

connpassでニフティグループに
参加いただくと
イベントの
お知らせが届きます!