この記事は2部構成です。
part1はこちら
前回のpart1でほぼ終わっているのですが、stateを管理するcreateLoading
関数とSkeletonコンポーネントを作成していないので作成していきましょう。
createLoading
createLoading.svelte.tsというファイル名で以下を作成します。
import { getContext, hasContext, setContext } from 'svelte';
type Context = {
state: boolean;
};
export const createLoading = (symbol: symbol) => {
if (hasContext(symbol)) {
return getContext<Context>(symbol);
}
let loading = $state(true);
const rune = {
get state() {
return loading;
},
set state(v) {
loading = v;
}
};
setContext<Context>(symbol, rune);
return rune;
};
export const getLoadingContext = (key: symbol) => getContext<Context>(key);
ひとつのローディンググループごとにユニークなキーを持たせてそれに関連するスケルトンの表示状態を一度に変更かつ他のローディングの状態に依存させたくないので、Symbolを使用します。
クソ便利ですね!
中身はざっくり説明するとContextにrunesを詰めて返しているだけです。
getLoadingContext関数を作成し、いちいちgetContextに型を渡さなくていいようにしています。
Skeleton
Skeleton.svelteを以下で作成します。
<script lang="ts">
import type { Snippet } from 'svelte';
import { createLoading } from './createLoading.svelte';
let {
key,
time = 3000,
children
}: {
key: symbol;
time: number;
children: Snippet;
} = $props();
let loading = createLoading(key);
setTimeout(() => {
loading.state = false;
}, time);
</script>
{@render children()}
このコンポーネントの中身はやりたい実装に合わせて調整してください。
今回は要件にある
- スケルトンの内部でさらに別のスケルトンを表示する場合、同期されないようにしたい(スケルトンごとにグルーピングしたい)
これが確認できればそれでいいので単純にtime秒待ってからloading.state
をfalse(loading終了)に書き換えています。
この例ではContextで下層のコンポーネントにstateを渡す関係でこういった微妙なコンポーネントが必要になってしまっていますが、実際はSymbolの管理や適切な命名によって気にならなくなると思います。