Svelteを触りながら実務で取り入れられないか模索するスクラップ
公式ドキュメント
一旦ゴールを設定(随時更新)
- ローカル開発環境(※PhpStormでしかやらないよ)
- TypeScript
- できた。めっちゃ楽
- ESLint, Formatter
- 現状どちらかを捨てなければいけない。自分はPrettierを取った。
- ホットリロード
- できる。
- デバッグ
- スキャフォールディングツールの調査
- 初回のテンプレート以外見つからなかった。
- angular-devkit-schematicsの力を再び借りるか…。
- 初回のテンプレート以外見つからなかった。
- TypeScript
- 実際の構文
- コンポーネントの親子関係の作り方
- できた。
- レイヤリング等
- 模索中
- コンポーネントの親子関係の作り方
- 状態管理
- 一旦Serviceでできないか
- デファクトスタンダートなやつ探す
- そもそもSvelte自体に備わっていた
- envの管理
ローカル環境構築(PhpStorm版)
公式が環境構築について書いてくれてる。
「degit」というものがスキャフォールディングツールらしい。
これでプロジェクトのテンプレートを作成できる。
The easiest way to get started with Svelte#Use degit
テンプレートのリポジトリはこちら↓
sveltejs/template
※中身を見るとなんと setupTypeScript.js
というファイルが。自動でTypeScript対応してくれるようだ。
ESLintとPrettierも入れてみる。
参考はこちら→ ESLint and Prettier with Svelte
大体これでいける。TypeScriptに関してはこちら→ TypeScriptのプロジェクトにESLintとPrettierを導入する方法(+VSCodeでの設定)
※Svelteのversion 3(TypeScriptが公式に対応したバージョン)が大体2020年6~7月あたりなので、参考にするなら同年7, 8月以降の記事を参考にした方が良さそう。
ただこれだと、TypeScript化した .svelte
内で型を宣言するとLinterエラーが起こる。
Svelte3 で Typescript,Pug,Sassを使える様にセットアップする#ESLintの導入
この人も同じエラーが出ている。(投稿日見るとほぼ同時期)
いったん .svelte
ファイルはJSのまま進めて、JSDocで型付けをするようにしてみようかな。
→ lang=ts
でやるとJSDocの import()
ができない。PrettierのフォーマットとESLintどちらをとるかと言ったら…。
Prettierかなぁ…。lang=ts
使いたいし…。
SvelteでSCSSを使いたい。
これでやってみる→ Sapper で SCSS を使う方法( rollup 利用時)
→できた。 ただし今回Sapperではないため以下の記述は不要(というよりエラーが出る) そもそも npm install postcss
してなかったワネ。。。
postcss: {
plugins: [require('autoprefixer')],
},
実際の構文
配列、オブジェクトのReactivityは 新しい配列やオブジェクトを割り当てない限り更新されない 。
これが公式のチュートリアルで明言されているのは非常に良い。
子コンポーネントへの値の渡し方
- 子コンポーネント側のプロパティに
export
をつける。(Angularの@Input()
)
<script>
export let answer;
</script>
<p>The answer is {answer}</p>
- 親側は
export
されているプロパティに値を渡すだけ
<script>
import Child from './Childe.svelte'
</script>
<Child answer={42}/>
keyed-each-blocksの修正前の例がなぜそうなるのか理解できない。
→同じことを思っている人がいた。 Further explanation of Svelte's keyed each block
(Deepl翻訳)
リストから最初の項目を削除しても、Thing1 は変わらず、それに関連付けられたキー(この場合はインデックス)は変わらないので、Thing1 のままです。以前は Thing2 に送られていたプロップが Thing1 に送られるようになりました。これはチェーン全体で起こります。しかし、要素が1つ減ったので、Thing5はDOMから削除されます。 キー "0" (Thing1) に関連付けられたコンポーネント Thing のインスタンスは、最初のアイテムが削除されても破棄されません。これは、キーが同じままであるために起こります(新しい配列はインデックス 0 にもアイテムを持っています)。Thing1に送られているプロップだけが変更され、id: 1の元のアイテムの色を持つ初期変数を残しています。
each
に対応する子コンポーネントのインスタンスが残ってしまうために、このようなことが起こるようだ。
-
each
でそれぞれの要素に対応する子コンポーネントインスタンスが生成、描画される。この各要素は配列のインデックスに紐づけられる。 - はじめの要素を消すと配列内では0番目の要素が削除されるが、DOM上は1番目の要素は残ったままで5番目の要素が削除される。
- 本来(DOM上の)1番目の要素は、2.で「はじめの要素を削除した」ため2番目のPropsが反映されて描画されるはずだが、その1番目の要素には破棄されていない「配列で0番目の要素をPropsとする子コンポーネントインスタンス」が残ってしまっている。
- 故に
initial
だけ残ってしまったということ。
それの対策として今回の「keyed-each-blocks
」が存在する。
{#each cats as cat (cat.id)}
<li>...</li>
{/each}
とインデックスとなる値を each
で指定することで、そのその指定された値とDOM上の要素をそれぞれ紐づける。
ちなみにそもそもそのIteratableな値のインデックスを紐付け対象とすることもできる。
{#each cats as cat (cat)}
<li>...</li>
{/each}
ただ公式としては、こう言ったIteratableな値というのは大抵APIを介して取得したデータのためなるべくStringやNumberの値を利用してとのこと。
Using a string or number is generally safer, however, since it means identity persists without referential equality, for example when updating with fresh data from an API server.
子コンポーネント→親コンポーネントのイベント通知
- 子コンポーネント側で
createEventDispatcher
を使用する。
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function sayHello() {
dispatch('message', {
text: 'Hello!'
});
}
</script>
...
- 親側での通知の受け取りは、
dispatch()
の第一引数に指定された文字列をイベント名として受け取る。
<script>
import Child from './Child.svelte';
function handleMessage(event) {
alert(event.detail.text);
}
</script>
<Child on:message={handleMessage}/>
またコンポーネント階層が複数の場合、一番下の子コンポーネントのイベントを一番上の親に通知するには中間のコンポーネントが createEventDispatcher()
を転送する必要がある。その際中間コンポーネントも createEventDispatcher()
を生成して dispatch()
しなければいけないと思うが、実はそんなめんどくさいことはしなくて良い。値の無い on:~
を実装するだけで上に転送するものと判断してくれる。ちなみにブラウザ標準の click
と言ったイベントも同じように on:click
とするだけで自動で転送してくれる。
<script>
import Child from './Child.svelte';
</script>
<Child on:message/>
子コンポーネントのインスタンスを取得するには bind:this
を使用する。
ただし readonly である。
<script>
import Child from './Child.svelte';
let childComponentInstance;
onMount(() => {
console.log(childComponentInstance);
})
</script>
<Child bind:this={childComponentInstance}/>
Components Bindingということもできる。
子コンポーネントで export
された値は、親コンポーネント側でBindingすることができる。
※いろいろ端折ってある
<script>
let num;
</script>
<Child bind:numberHoge={num}/>
<script>
export let numberHoge;
</script>
ただし、親コンポーネント子コンポーネントのどちらからも変更ができてしまうため、自分だったら基本使わないしメンバーには禁止させる。
Content Projectionをする機能が備わっている。
Svelteの場合は <slot></slot>
を定義することで Content Projection を実現する。
もちろん名前を付けて複数の <slot></slot>
を定義できる。
<div>
<slot name="hoge"></slot>
</div>
<Child>
<p slot="hoge">
Hello world!
</p>
</Child>
$$slots を使うことで、対応するSlotsに要素が渡されてない時はレンダリングしないといったこともできる。
Slots Props という子から Content Projection する親に値を渡せる仕組みがあるが…。
ちょっと慣れるまでわかりづらい…。
<Hoverable let:hovering={active}>
この構文で let:hovering
が子から渡される値で、親側はそれを active
という名前で使用する感じ。
確かに書く量は減るが、個人的には Container/Presentational Component の原則的にイベントで値を子から親に通知したい…。
Svelte-Component という要素を使えば if
でコンポーネントの表示を分岐させなくても、動的にコンポーネントを描画できる。
※ただし、使用するコンポーネントを import
しておく必要がある。IDEによってはもしかすると「使用していない変数」ということで Warning
が表示されるかもしれない(特にPhpStorm)
Svelte-window-bindings を利用すればスクロール系の操作が楽になる…?
状態管理
Svelteには状態の更新とそれを特定のコンポーネントに通知するための仕組みを備えている。
それが Store と呼ばれるもの。
RxJSに似ているかも…?
RxJSの .next()
にあたるものが set()
, update()
.
コールバック関数を使用して更新するのが update()
で、値で更新するのが set()
かな?
→実際に update()
にプリミティブな値を渡したら not a function
と怒られたのでそうだろう。
またコンポーネントが複数の Store
を使用すると subscribe()
の実装が増えてしまう。
→そうならないために Store
となっている変数名に $
のprefixをつけることで自動で subscribe する。ちなみにこのauto subscribeはコンポーネント破棄時に自動で unsubscribe してくれる。
<script>
import { count } from './stores.js';
</script>
<h1>The count is {$count}</h1>
Store は Writable
と Readable
を作成できる。
derived
を使用することで他の Store に基づいた別の Store を作成できる。
export const time = readable(new Date(), function start(set) {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return function stop() {
clearInterval(interval);
};
});
export const elapsed = derived(
time,
$time => {~~~}
);
また Writable
な Store であればそれ自体を Binding することも可能。
<script>
import { name, greeting } from './stores.js';
</script>
<h1>{$greeting}</h1>
<input bind:value={$name}>
ただどうだろう…。個人的には後述の Custom Stores
のメソッド経由で更新したい派。
と思ったが、結局 関連する Store のデータは一つのファイルにまとめる ので別に問題無いかも?
derived
使えば簡潔にできそうだし。どちらにせよ、複雑な Store は作りたく無い。
Custom Stores
上記のようにすることで、Stores 固有の update
, set
等のメソッドを隠蔽して、宣言的な Stores を使用できる。