在許多夏日的夜晚,我鄰居的家中總是傳來陣陣宣告變數的聲音。在他藍藍的花園裡頭,男人女人像飛蛾一般,隨著耳語、美酒、星空、和
$
翩翩起舞。~節錄自《The Great Svelte:第三章》
我們已經明白 Svelte 能夠透過賦值 (assign, =
) 這個動作,同步更新 Javascript 的變數以及 HTML 當中與這個變數相關的元素了。那麼再繼續問下去:Svelte 有辦法同樣根據賦值這個動作,同步去更新 Javascript 當中與這個變數相關的其他變數嗎?答案是肯定的。就讓我們來看看,Svelte 是用了什麼神奇的魔法來完成這個工作的吧!
在一個前端專案當中,我們可能會用到不少 Javascript 變數來記錄專案的狀態。同時為了方便起見,可能也會有不少衍生 (或說參照,reference) 自變數的變數。舉例來說,我們可能會想要用一個變數代表陣列,接著另外再創造一個變數,用來代表陣列的長度。
let someState = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let otherState = someState.length;
在互動式的使用者介面當中,當 someState
因為某些原因改變了,我們自然會希望衍生自 someState
的變數 otherState
也跟著更新,如此才能維持整個狀態的統一性。那麼 Svelte 如何幫助我們做到這件事呢?Svelte 提供了一個非常有趣的方式,也就是互動宣告 (reactive declaration),讓 Svelte 的編譯器知道何時該跟著更新衍生自變數的變數。
互動宣告 (reactive declaration) 的作法很簡單,當我們宣告一個新的變數,這個新的變數是參照自其他變數時,只要在開頭加上 $:
就完成了:
let someState = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
$: otherState = someState.length;
otherState
是衍生自 someState
的變數,需要跟著 someState
的改變而更新。因此在宣告 otherState
的時候,我們在開頭加上 $:
,Svelte 編譯器會創造出程式碼,幫忙追蹤 otherState
究竟是衍生自哪些變數,當那些變數改變時,便會跟著更新 otherState
。
明白了做法,就讓我們實際嘗試一次看看吧。同樣來到 Counter.svelte
這個檔案,讓我們重新寫過 Javascript 的內容:
/src/lib/Counter.svelte
<script>
let countArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
$: count = countArray.length;
setInterval(() => {
countArray = countArray.slice(1);
}, 1000);
</script>
<section>
<h1>Create Color Palette for Me!</h1>
<div class='counter'>
<button>
<svg aria-hidden="true" viewBox="0 0 1 1">
<path d="M0,0.5 L1,0.5" />
</svg>
</button>
<div class="counter-viewer">
<p>{count}</p>
</div>
<button>
<svg aria-hidden="true" viewBox="0 0 1 1">
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
</svg>
</button>
</div>
</section>
第二行:let countArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
首先宣告一個變數 countArray
。
第三行:$: count = countArray.length;
接著利用 $:
宣告一個衍生自 countArray
的變數,也就是 count
。
第六行:countArray = countArray.slice(1);
每一秒鐘丟掉一個 countArray
陣列當中的元素。記得,在 Svelte 當中,為了讓編譯器知道變數更新這件事,必須要使用賦值 (assign, =
)。所以這邊我們不是用原地 (in-place) 改變陣列的 countArray.shift()
,而是 countArray = countArray.slice(1);
第二十行:<p>{count}</p>
將衍生自 countArray
的變數 count
顯示在 HTML 當中。
這樣一來,就算完成互動宣告 (reactive declaration) 了,讓我們看一看成果吧。
圖一、10、9、8、7、6、5、4、3、2、1、0!
要提醒的一點是,在做互動宣告時,並不需要把 let
擺在變數名稱前,即使是第一次宣告的變數也一樣。如果是第一次宣告的變數,Svelte 編譯器會在編譯階段自動幫我們補上需要的 let
。
為什麼會這樣呢?這其實是源自於 Javascript 本身的設計。
不知道互動宣告 (reactive declaration) 的程式碼,對於第一次接觸 Svelte 的人來說,寫起來會不會很彆扭或是不習慣呢?不過 $:
可是完完全全符合 Javascript 規範的一種語法,叫做標籤陳述句 (labelled statement)。標籤陳述句的寫法是,先用一個標籤 (identifier) 加冒號 (:
) 開頭,接著將需要的陳述句放在後面。只要呼叫這個標籤,就可以執行指定的陳述句。有興趣的讀者可以到 MDN Web Docs 來詳細查看標籤陳述句的說明 (連結)。
而 Svelte 只是讓 $
作為標籤陳述句的標籤,並藉由編譯器賦予這個特殊的標籤陳述句更多的功能,讓這個標籤陳述句可以追蹤陳述句內用到的所有變數,只要任何一個變數發生重新賦值得事件,就回過頭來跟著重新執行一次該陳述句的內容。這麼一來就能夠讓衍生的變數跟著所有參照的變數一起更新了。
那麼現在是不是也不難理解為什麼在 $:
之後做的互動宣告 (reactive declaration) 不用 (實際上是根本不能) 加上 let
來宣告新的變數。因為加上 let
會使得重新執行這段程式碼時發生重複宣告變數的錯誤。
那麼今天關於互動宣告 (reactive declaration) 的介紹就到這邊了,謝謝大家!