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


Store

Store 是 Svelte 的資料交換機制,被定義成 store 的變數,可以被多個元件使用,一個 store 變數的值的變化,也可以透過 subscribe 的機制,即時反映給元件們。

可讀寫的 Store

在下面的例子裡,我們利用 Svelte 的 writable() 函式在 store.js 內定義了一個可讀寫的 store 變數 count,令其初始值為 0,並利用 JS 的標準關鍵字 exportcount 可被外部調用,如此在其他的 Svelte 元件內即可引入 count 並讀寫它:

在 Incrementer 和 Decrementer 裡,我們調用了 count.update() 來更新 count 的值,因為這裡的 count 的值(literal)並不是 primitive 的 number,而是透過 writable() 建構出的物件,得用它自有的方法來更新,在 Resetter 內也是一樣,我們調用的是 count.set() 來賦值。

在 App 這邊,我們用 count.subscribe()count 的值的變化即時反映到 App 的變數 count_value 身上。

自動訂閱

Svelte store 的觀察者模式還有更簡單的用法,沿用上面的例子,把 count.subscribe() 那塊程式碼拿掉,直接在 HTML 內調用 $count 就做完了:

$count 自動幫我們訂閱 count,也就是當 count 狀態改變時,$count 也會改變,$count 也幫我們自動關訂閱,當元件銷毀時,$count 自動關訂閱。

另外 $count 也是個 JS 變數,雖然它是由 Svelte 幫我們產生的,並且 Svelte 有對它賦予了上面的特性,但它依然是個能如同一般變數般被操作的變數,所以我們能用在 JS 區域內用 console.log($count) 來印出它的值,也可以用 $count += 1 來改它的值。

可讀不可寫的 Store

某些情況下我們想讓 store 僅可被讀取,但不可被寫入,我們可以改用 readable store。

下面的例子裡,time 是一個僅供讀取的 store:

在 stores.js 裡,我們改用 readable() 來建構 store。

readable() 接受的第一個參數,是 store 的初值,第二個參數是個 callback 函式,會在 store 被初次訂閱時執行,這裡我們命名為 start()start() 裡面的 set 又是一個 callback 函式,用於設定 store 的值,這裡我們讓它每秒跑一次把 time 更新到當前的時間點,而最後的 return 函式又是個 callback 函式,會在所有的訂閱都關閉後執行。

衍生的 Store

以某個 store 為基礎,可以做出衍生的 store。

沿用上面的範例,下面加一行頁面開啟時間的計時器:

derived() 用法與上面類似,第一個參數是初值,第二個參數則是一個函式,藉由 reactivity 的特性隨 $time 更新 elapsed 這個衍生 store。

自定義 Store 行為

除了內建的 subscribe()set()update() 等行為,也可以在 store 內定義自己特有的方法。

我們沿用前面的例子,但這次把按鈕的行為定義在 store 內,不再做成獨立的元件:

我們在 store.js 裡面做了幾件事:

  • count 改由自訂函式 createCount() 建構
  • createCount() 內引入了 writable() 的三個方法 subscribeupdateset
  • createCount() 還自定義了 incrementdecrementreset 三個方法
  • createCount() 最後暴露在外的只有 subscribe 以及自訂的 incrementdecrementreset,來自 writeable()updateset 並不對外暴露

在這樣的改造之後,外面的元件在調用 count 時,就只能呼叫允許的 subscribeincrementdecrementreset 四個方法,而 updateset 將無法使用。其中 subscribe 是 store 必須存在的要素,一定要開放外界呼叫。

Store 綁定

Store 也可以做數據綁定,在第四集我們見過一個典型的數據綁定會是像這樣:

<input bind:value={name}>

要對 Store 綁定,也是類似的做法,回顧上面的自動訂閱一節,在 Svelte 的 HTML 區域內可以用 {$name} 的方式調用 store,而 $name 也只不是個被賦予 store 特性的一個變數,因此要對 store 做綁定,也是用同樣的邏輯:

<input bind:value={$name}>

完整的範例如下:

在範例中的按鈕,它的點擊觸發的匿名函式會把 $name 後面加個驚嘆號,注意到我們並沒有用 set() 而是用更直覺的 $name += '!',這與 set() 是等價的,Svelte 會幫我們處理好。

小結

Store 與 props 同樣作為元件間資料的交換機制,Store 有著這些與 props 不同的特性:

  • Store 是獨立於元件的,而 props 是元件內的
  • Store 在使用上與元件的父子關係無關,而 props 的使用都是父元件傳給子元件的

本集重點整理:

  • Store 分為可讀寫的、可讀不可寫的、衍生的
  • Store 被設計成具有觀察者模式的特性,元件可以訂閱一個 store,當 store 變化時,會反映到那些訂閱者身上
  • Store 有簡單的訂閱、關訂閱語法,以一個名為 name 的 store 為例,元件可以用 $name 調用它,而 Svelte 會自動處理 $namename 間的訂閱、關訂閱工作
  • Store 也可以綁定,延續上例,可以用 bind:value={$name} 的形式做綁定