context API は、データや関数をプロパティとして渡したり、たくさんのイベントをディスパッチしたりすることなく、コンポーネント同士で'会話'するための仕組みを提供します。これは高度ですが、便利な機能です。
Mapbox GL のマップを使ったこのアプリの例を見てみましょう。<MapMarker>
を使用してマーカーを表示したいのですが、ベースとなるMapboxインスタンスへの参照を各コンポーネントのプロパティとして渡したくありません。
context API は setContext
と getContext
に分かれます。もしコンポーネントが setContext(key, context)
を呼ぶと、どの子コンポーネントでも const context = getContext(key)
で context を取得することができます。
まずは context を設定してみましょう。Map.svelte
では、svelte
から setContext
をインポートし、mapbox.js
から key
をインポートして、setContext
を呼び出します。
import { onDestroy, setContext } from 'svelte';
import { mapbox, key } from './mapbox.js';
setContext(key, {
getMap: () => map
});
context オブジェクトはなんでも構いません。ライフサイクル関数のように、setContext
と getContext
はコンポーネントの初期化時に呼び出されなければいけません。それより後 (例えば onMount
の中) で呼び出すとエラーをスローします。この例では、コンポーネントがマウントされるまで map
は作成されないので、この context オブジェクトには map
自体ではなく getMap
関数が含まれています。
一方、MapMarker.svelte
では、Mapbox インスタンスへの参照を取得できるようになりました。
import { getContext } from 'svelte';
import { mapbox, key } from './mapbox.js';
const { getMap } = getContext(key);
const map = getMap();
これでマーカーをマップに追加することができるようになりました。
<MapMarker>
の、より完成度の高いバージョンでは削除やプロパティの変更も扱えますが、この場では context のデモンストレーションに留めておきます。
Context keys
mapbox.js
にはこの一行が含まれています。
const key = Symbol();
技術的には、どんな値でもキーとして使うことができます(例えば setContext('mapbox', ...)
のように)。文字列を使用することの欠点は、異なるコンポーネントライブラリが誤って同じものを使ってしまう可能性があることです。一方、symbol を使用すると、symbol は本質的に一意な識別子であるため、どんな状況でもキーが衝突しないことが保証されます。たとえ複数の異なる context が多くのコンポーネントレイヤーをまたいで動作している場合であっても、です。
Contexts vs. stores
context とストアは似ているように見えます。ストアはアプリのどの部分でも使用できるのに対し、context はコンポーネントとその子孫のみが利用できるという点で異なります。これは、ある状態が他の状態に干渉することなく、コンポーネントの複数のインスタンスを使用したい場合に便利です。
実際には、この2つを一緒に使うこともあるかもしれません。context はリアクティブではないので、時間の経過とともに変化する値はストアとして表現する必要があります。
const { these, are, stores } = getContext(...);