這是〈Svelte 從小白到入門〉系列的第十三集,各集連結如下:


Context API 是 Svelte 中一種元件間交換資料的機制,當一個父元件以 setContext(key, context) 設定一組 context,那麼它的子元件,以及子元件的子元件,都可以透過 getContext(key) 來取得那組 context。

在下面的地圖範例中,我們有 Map 元件,以及子元件 MapMarker。

Map 元件的主要成員是 map,它是 mapbox.Map() 構成的地圖物件,在範例中,我們用 setContext()getMap 成為一組 context,使子元件們(MapMarker)也得以取得 map 這個地圖物件:

在 Map 元件中,注意到 map = new mapbox.Map() 這句生成地圖物件的敘述是放在 onMount() 內,onMount() 在第五集生命週期有出現過,onMount() 指的是當 Svelte 元件在被瀏覽器生成之時,而地圖物件的生成也必須是它的容器元素已經被瀏覽器建立之後,也就是 Map 元件內的那個 <div> 必須先存在,Mapbox 才有可能以它為容器,建構出地圖物件本身,所以這些構成地圖物件的程式碼才得放在 onMount() 裡面。

並且需要注意的是,setContext() 得在元件初始化時調用,但此時 map 僅是 undefined,尚未生成地圖物件,因此我們並不直接在 context 中置入 map,而是用 getMap() 函式來處理:

setContext(key, {
	getMap: () => map
});

因此在 MapMarker 中,我們透過 getContext() 得到的並非是 map 真身,而是可以取得 map 的函式 getMap(),如此迂迂迴迴 只為了求你點個讚 讓我們得以把元件細節都封裝在元件內,使最外層的 App 元件看起來是如此的清晰而且語意明確:

<Map lat={35} lon={-100} zoom={2}>
	<MapMarker lat={37.8225} lon={-122.0024} label="Svelte Body Shaping"/>
	<MapMarker lat={29.7230} lon={-95.4189} label="Svelte Waxing Studio"/>
</Map>

Context 的 Key

在上面的範例中,我們再做 context 時用的 key 只是一個空物件 {},在 Svelte 的約定裡,可以用任意物件當 key,只要自己確保沒有與其他物件搞混就好,所以 Svelte 推薦我們用物件做為 key,因為在 JavaScript 的設計上,兩個一樣的空物件是參照到不同的記憶體位址,即 {} !== {}

當然,如果有兩個 context,key 也必須是各自獨立的。

Context 與 Store

同樣做為跨元件的資料交換機制,第六集的 store 與本集的 context 有著以下差異:

  • Store 是全域的,任何元件都可以使用,而 context 是一個元件及它的子代元件才可使用
  • 承上,當一個元件有 A、B 兩個實體時(被調用兩次),他們各自的 context 是獨立的,不互相影響,他們的子元件調用到的 context 當然也是獨立的,A 的子元件吃到的 context 就是來自 A,不會進錯家門上錯床吃到 B 的 context
  • Store 有觀察者模式的特性,元件可以 subscribe 一個 store 的即時變化,而 context 不具有這樣的特性
  • 承上,透過 Store 的 subscribe 機制,我們可以說 store 是 reactivity 的,而 context 是沒有 reactivity 的特性的

基於上述的最後一點,如果某個 context 的內容會改變,而且我們希望它的改變要反映在其他地方,那可以把 store 與 context 合併運用:

const { these, are, stores } = getContext(...);

如此一來,這些 store 的值來自 getContext(...),並且也有 reactivity 惹!

小結

本集重點整理:

  • Context 是跨元件的資料交換機制,但僅限父元件與它的子代元件間做交換
  • setContext(key, content) 設定 context、用 getContext(key) 取得 context
  • setContext() 得在元件的初始化階段調用
  • 關於 context 與 store 的差異請看上一節