他側過身體看向我。我可以理解為什麼貝克兒小姐會認為他說謊隱瞞了整個事件傳遞。當他說
on:click
的時候,要嘛含糊其詞,要嘛快速帶過,不然就是如鯁在喉。彷彿這個詞兒一直困擾著他。~節錄自《The Great Svelte:第四章》
在前一天的文章當中,我們在 Counter.svelte
當中替 <button>
加了一個事件處理器,當 <button>
被點擊 (click
),就會觸發相對應的事件,也就是更改變數 count
的值。
然而這樣設計可能有點問題。事件處理器的作法本身沒有錯,但是更新 count
的邏輯設計不太好。仔細想一想,count
的值其實是從 App.svelte
這個父元件 (parent component) 傳到 Count.svelte
這個子元件 (child component) 當中。如果只有在 Count.svelte
裡面修改 count
的值,而 App.svelte
卻渾然不知道這些改動,好像哪裡怪怪的。因此,通常的設計邏輯會將專案主要的狀態由最上層的父元件 (parent component) 來儲存跟維護,狀態的更動都是由父元件 (parent component) 發起,然後再以元件的 Property 這個管道由上而下將變數的內容傳給下層的子元件 (child component)。那麼現在問題就來了:如果子元件 (child component) 當中發生了需要更改狀態的事件,這個事件有辦法被父元件 (parent component) 接收並成功進行狀態更新嗎?
這就是今天要講的主題,也就是事件傳遞。要怎麼讓子元件 (child component) 當中發生的事件,往上傳出去給父元件 (parent component) 接收呢?Svelte 提供了一個簡單的方法,只要不定義事件處理器就行:
<!-- 定義了事件處理器為 handleClick 的 button,發生的事件會直接被事件處理器接收 -->
<button on:click={handleClick}>Click</button>
<!-- 沒有定義事件處理的 button,發生的事件會向上往父元件傳遞 -->
<button on:click>Click</button>
第二行:<button on:click={handleClick}>Click</button>
利用 on:click
表示我們想要聆聽 click
事件,並寫出 handleClick
這個函式做為事件處理器。如此一來,click
事件會直接被 handleClick
接手,而不會在往上傳遞。
第五行:<button on:click>Click</button>
利用 on:click
表示我們想要聆聽 click
事件。然而跳過定義事件處理器,這時候 click
事件就會直接往上傳向父元件 (parent component) 了。
學會了嗎?那讓我們實際的來試一試吧。首先我們在 App.svelte
當中做好接收事件的準備:
/src/App.svelte
<!-- 在 Javascript 當中 import Counter -->
<script>
import Counter from './lib/Counter.svelte';
let someState = 'TheGreatSvelte';
const sparkle = (text) => {
const sparkles = ['★', '☆', '✧', '✪'];
const randomSparkles = () => sparkles[Math.floor(Math.random() * sparkles.length)];
const sparkledText = text.split('').reduce((a, c) => a + randomSparkles() + c, '');
return sparkledText;
}
const href = 'https://ithelp.ithome.com.tw/users/20120178/ironman/7031';
</script>
<main>
<!-- 在 HTML 當中直接嵌入 Counter -->
<Counter on:click={() => console.log('Click from Counter')}/>
<p class='comment'>Check out <a {href}>Svelte Tutorial</a>, the awesome article powered by {sparkle(someState)}!</p>
</main>
第三行:import Counter from './lib/Counter.svelte';
引入 Counter
。
第十八行:<Counter on:click={() => console.log('Click from Counter')}/>
用 on:click
接收來自 Counter
的 click
事件,並加入一個行內事件處理器 () => console.log('Click from Counter')
,只要收到來自 Counter
的 click
事件,就會用 console.log
記錄下來。
接著在 Counter.svelte
將點擊 (click
) 事件傳遞出去:
/src/lib/Counter.svelte
<script>
export let count = 100;
</script>
<section>
<h1>Create Color Palette for Me!</h1>
<div class='counter'>
<button on:click>
<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 on:click>
<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>
第九行:<button on:click>
用 on:click
接收來自 <button>
的點擊 (click
) 事件,但不寫出事件處理器。這麼一來,click
事件就會往上傳遞到父元件 (parent component),也就是 App.svelte
。
第十七行:<button on:click>
用 on:click
接收來自 <button>
的點擊 (click
) 事件,但不寫出事件處理器。這麼一來,click
事件就會往上傳遞到父元件 (parent component),也就是 App.svelte
。
圖一、Click from Counter!
太棒了,我們成功讓 App.svelte
接收到來自 Counter.svelte
的點擊 (click
) 事件了。
現在 App.svelte
可以對來自 Counter.svelte
當中的 <button>
的 click
事件做出回應了。可是 <button>
有兩種,一種要讓 count
減少,另一種要讓 count
增加。該怎麼區別呢?其實也不難,因為文件物件模型 (DOM) 的事件會自帶一個事件物件 (event object),將這個事件物件 (event object) 當作事件處理器的參數,我們可以找到事件發生的來源。
/src/App.svelte
<!-- 在 Javascript 當中 import Counter -->
<script>
import Counter from './lib/Counter.svelte';
let someState = 'TheGreatSvelte';
const sparkle = (text) => {
const sparkles = ['★', '☆', '✧', '✪'];
const randomSparkles = () => sparkles[Math.floor(Math.random() * sparkles.length)];
const sparkledText = text.split('').reduce((a, c) => a + randomSparkles() + c, '');
return sparkledText;
}
const href = 'https://ithelp.ithome.com.tw/users/20120178/ironman/7031';
</script>
<main>
<!-- 在 HTML 當中直接嵌入 Counter -->
<Counter on:click={(e) => console.log([...e.target.classList])}/>
<p class='comment'>Check out <a {href}>Svelte Tutorial</a>, the awesome article powered by {sparkle(someState)}!</p>
</main>
<Counter on:click={(e) => console.log([...e.target.classList])}/>
(e) => console.log([...e.target.classList])
,當接收到 click
事件時,利用該事件物件 (event object, 這邊就是 e
) 作為事件處理器的參數丟進函式當中,並將事件來源的 HTML 元素的 classList
記錄下來。 接著我們在 Counter.svelte
當中對兩個 <button>
做手腳,藉由加上不同的 class
區分這兩個 <button>
:
/src/lib/Counter.svelte
<script>
export let count = 100;
</script>
<section>
<h1>Create Color Palette for Me!</h1>
<div class='counter'>
<button class='-' on:click>
<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 class='+' on:click>
<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>
第九行:<button class='-' on:click>
對於要減少 count
的 <button>
加上 -
這個 class
。
第十七行:<button class='+' on:click>
對於要增加 count
的 <button>
加上 +
這個 class
。
圖二、利用 e.target.classList
成功區分兩個 <button>
太好了,看來利用 e.target.classList
可以成功區分兩個具有不同 class
的 <button>
。
不過好像有一件事跟我們想的不一樣。這兩個 button
除了 -
和 +
之外,還多了另外一種 class
,那就是看起來像一串亂碼的 s-lXDMeZ-gXAhl
。這是怎麼回事呢?
讓我們重新回頭來看一下 Svelte 元件的 CSS 吧。
我們在第 06 天的時候介紹過 Svelte 元件的 CSS。那時我們說 Svelte 元件的 CSS 是各自獨立不互相干擾的。至於 Svelte 是怎麼做出各自獨立的 CSS,則是靠現在我們看到的那個多出來的奇妙 class
。構想很簡單:當我們分不出來事件是源自於哪一個 button
的時候,我們幫不同的 <button>
加上不同的 class
。同理,當 Svelte 要將所有的 Svelte 元件整合在一起,又不想把 CSS 選擇器弄混的時候,就在編譯的過程,針對不同的 Svelte 元件加上不同的 class
,同時也稍微修改 CSS 選擇器,加上這個隨著不同元件而產生的 class
。這就是我們看到 s-lXDMeZ-gXAhl
這個奇妙的 class
的原因。
圖三、CSS 選擇器是不是也多了一個 s-lXDMeZ-gXAhl
呢
那麼讓我們利用 classList
重新寫一個能夠加加減減的 <button>
:
/src/App.svelte
<!-- 在 Javascript 當中 import Counter -->
<script>
import Counter from './lib/Counter.svelte';
let count = 100;
let someState = 'TheGreatSvelte';
const sparkle = (text) => {
const sparkles = ['★', '☆', '✧', '✪'];
const randomSparkles = () => sparkles[Math.floor(Math.random() * sparkles.length)];
const sparkledText = text.split('').reduce((a, c) => a + randomSparkles() + c, '');
return sparkledText;
}
const href = 'https://ithelp.ithome.com.tw/users/20120178/ironman/7031';
const handleClick = (e) => {
console.log([...e.target.classList]);
([...e.target.classList].includes('-')) && (count -= 1);
([...e.target.classList].includes('+')) && (count += 1);
}
</script>
<main>
<!-- 在 HTML 當中直接嵌入 Counter -->
<Counter {count} on:click={(e) => handleClick(e)}/>
<p class='comment'>Check out <a {href}>Svelte Tutorial</a>, the awesome article powered by {sparkle(someState)}!</p>
</main>
第二行:import Counter from './lib/Counter.svelte';
引入 Counter.svelte
。
第五行:let count = 100;
因為我們想要用 App.svelte
來控制 count
,所以這邊先將變數宣告出來。
第十七行:const handleClick = (e) => {
定義一個函式 handleClick
,並將事件物件 (event object) 作為函式的參數使用。
第十八行:console.log([...e.target.classList]);
將觸發事件的 HTML 元素的 classList
記錄下來。
第十九行:([...e.target.classList].includes('-')) && (count -= 1);
如果觸發事件的 HTML 元素有 -
這個 class
,就把 count
的值減一。
第二十行:([...e.target.classList].includes('+')) && (count += 1);
如果觸發事件的 HTML 元素有 +
這個 class
,就把 count
的值加一。
第二十六行:<Counter {count} on:click={(e) => handleClick(e)}/>
將 count
作為 Property 傳入 <Counter />
,並且加上事件處理器 handleClick
,接收來自 <Counter />
的 click
這個事件。
我們已經在 Counter.svelte
當中將需要區分開的 <button>
加上不同的 class
了,所以不需要另外多做修改:
/src/lib/Counter.svelte
<script>
export let count = 100;
</script>
<section>
<h1>Create Color Palette for Me!</h1>
<div class='counter'>
<button class='-' on:click>
<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 class='+' on:click>
<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>
圖四、又可以加加減減了
如何,是不是不困難呢?這就是 Svelte 當中事件的傳遞跟接收的作法。我們今天的內容就到這邊了,謝謝各位讀者支持。