這是〈Svelte 從小白到入門〉系列的第四集,各集連結如下:
- 〈Svelte 從小白到入門(一)元件、事件、Reactivity〉
- 〈Svelte 從小白到入門(二)Props、邏輯、迴圈、Await〉
- 〈Svelte 從小白到入門(三)事件〉
- 〈Svelte 從小白到入門(四)數據綁定〉
- 〈Svelte 從小白到入門(五)生命週期〉
- 〈Svelte 從小白到入門(六)Store〉
- 〈Svelte 從小白到入門(七)動態效果〉
- 〈Svelte 從小白到入門(八)進出場效果〉
- 〈Svelte 從小白到入門(九)動畫效果〉
- 〈Svelte 從小白到入門(十)Action〉
- 〈Svelte 從小白到入門(十一)CSS Class〉
- 〈Svelte 從小白到入門(十二)Slot〉
- 〈Svelte 從小白到入門(十三)Context〉
- 〈Svelte 從小白到入門(十四)Svelte 的特殊元素〉
- 〈Svelte 從小白到入門(十五)Module Context〉
- 〈Svelte 從小白到入門(十六)Debugging〉
- 〈Svelte 從小白到入門(十七)結束與展望〉
在前面的集數裡,我們遇到的 JS 與 HTMl 連動的關係都是單向的,也就是某個事件引發 JS 函式,而函式改變了某個變數的值,變數的值又反映在 HTML 中,但在那些 HTML 表單應用中,往往是要雙向連動的,而 Svelte 也提供了這樣的機制。
數據綁定
在下面的示例中,文字框和文字是要連動的,套用過去的經驗,會把 <input>
綁上 on:input
的事件,再製做相關的函式更新文字部份,但 Svelte 提供了另外的 bind:
語法讓這件事變得更簡單而直覺:
我們利用 bind:value={name}
這句讓 <input>
的 value
屬性和 JS 的 name
的值連動,並且是雙向的連動,不僅用戶在文字框輸入會改變同時改變兩者的值,並且如果是 JS 的 name
的值有被改動到,也會連動到 <input>
的 value
。
群組表單元素
HTML 的表單元素除了輸入框外,還有單選的 radio input 和多選的 checkbox input,Svelte 提供了 bind:group
語法來對付他們,以 radio input 為例:
<input type=radio bind:group={scoops} name="scoops" value={1}>One scoop
<input type=radio bind:group={scoops} name="scoops" value={2}>Two scoops
他們都和 JS 的 scoops
連動,而 scoops
的值則是來自 value
,選 One scoop,則 JS 的 scoops
將會是 1
,以此類推。
Checkbox input 也是同樣的作法,差別在多選的值在 JS 內會是以陣列的結構儲存。
完整的例子如下:
Textarea
正規的 HTML <textarea>
是把內容放在開關標籤中間:
<textarea>Some words are *italic*, some are **bold**</textarea>
但 Svelte 要做數據綁定的話,則是和 <input>
一樣用 bind:value
當作屬性放在 <textarea>
內:
下拉選單
在 HTML 中,一個典型的下拉選單是由一個 <select>
與多個 <option>
構成的:
<select>
<option>Where did you go to school?</option>
<option>What is your mother's name?</option>
</select>
在 Svelte,則是把 bind:value
加到 <select>
內,至於 <option>
,可以用原本的 HTML 方式,但往往我們也會把它和 JS 的變數綁定在一起,像這樣:
上面的範例中,<option>
是和 JS 的 questions
綁定,比較奇怪的點是 questions
陣列內都是 object,而 <option>
的 value
竟可照吃不誤,因為「Svelte doesn’t mind」。
而真正用到雙向綁定的是 <select>
,透過 bind:value={selected}
與 JS 的 selected
綁定,依照瀏覽器的邏輯,預設會幫我們帶入 <option>
的第一個項目,也就是「Where did you go to school?」,而在 JS 這邊,因為雙向綁定,selected
會是<option>
的 value
,也就是 { id: 1, text: 'Where did you go to school?' }
這個物件。
其他錦上添花的部份:
<select>
內的小函式,令選項改變時,清空答案框<form>
裡面加了preventDefault
的修飾器,防止頁面重載
多選選單
在 HTML 把 <select>
加上 multiple
屬性即為多選,拿前面的 checkbox 範例改造一下:
和單選的差別只在於多選的項目反應到 JS 上會是陣列的形式。
Contenteditable
賦予 contenteditable
屬性的元素,可以用 bind:innerHTML
或 bind:textContent
的方式綁定:
陣列與迴圈
在下面的例子裡,JS 的 todos
被放進 HTML 內做迴圈,迴圈內也可以針與 todos
的成員(todo
)做綁定:
至於那句陌生的樣式語法 class:done={todo.done}
在此先略過,如果本系列沒有斷尾的話會再提到。
這個範例也是前端框架最常見到的 to-do list 應用,就這樣不知不覺的做完了。:)
多媒體元素
對 <video>
與 <audio>
這類多媒體,Svetle 提供更多綁定的對象:
duration
(readonly) — the total duration of the video, in secondsbuffered
(readonly) — an array of{start, end}
objectsseekable
(readonly) — dittoplayed
(readonly) — dittoseeking
(readonly) — booleanended
(readonly) — booleancurrentTime
— the current point in the video, in secondsplaybackRate
— how fast to play the video, where1
is ‘normal’paused
— this one should be self-explanatoryvolume
— a value between 0 and 1muted
— a boolean value where true is muted
Videos additionally have readonly videoWidth
and videoHeight
bindings.
以上偷懶不解釋。:p
尺度
對於 HTMl 區塊類元素可以透過綁定機制取得 clientWidth
、clientWidth
、offsetWidth
、offsetHeight
,不過就如同原始的 web api 那樣,這些值也是唯讀的,只能單向讀取。
關於 clientWidth
與 offsetWidth
的差異,可以看〈一次搞懂 clientHeight/clientWidth/offSetHeight/offsetWidth/scrollHeight/scrollWidth/scrollTop/scrollLeft 的區別〉。
參見下面的例子:
裡面利用 range input 數值綁定的方式,動態變更字大小,以及利用前述的方式取得文字容器的寬高。
This
bind:this
令綁定的對象參照到元素(或元件)自身,取得元素自身的參照後,就能在 JS 區域去操作該元素。
看文字可能不好理解,實例如下:
我們用 <canvas bind:this={canvas}>
將 HTML 元素 <canvas>
參照到 JS 變數 canvas
上,或者我們可以把這件事理解為:
canvas = <canvas>
當然上面這行不是正確的語法,但就是這個意思。
令 canvas
為 <canvas>
元素後,就可以在 JS 內操控它,範例內剩下九成的程式碼是一系列 Canvas 花式操作,本人也是不懂裝懂的程度,請看〈Canvas 教學文件〉
範例中的 onMount()
的部份,容後敘明,只要本系列沒有停更…。
元件綁定
綁定機制不僅可用在原生的 HTML 元素上,也可用在 Svelte 元件上。
下面的例子有 App 父元件及子元件 Keypad,就是那令人又愛又恨的數字小鍵盤。
App 負責顯示遮蔽後的密碼、Keypad 負責顯示數字鍵盤,用戶按下的數字會以字串的形式存在 Keypad 的 value
,而 App 的 pin
又綁定到 Keypad 的 value
props,使 pin
可以得到 value
的值,最後再加上一個簡單的遮蔽函式,因此得以即時反應用戶按下的數字在密碼遮蔽區。
另一方面,用戶在 Keypad 按下 submit 後分派給父元件 App,而 App 接收到 submit
事件後觸發 handleSubmit()
彈出提示框。
綁定機制還可以直接用在元件上。
在下面的例子裡,用 <InputField bind:this={field}>
這句把調用的這份 InputField 元件(instance)與 field
變數綁定,並且 InputField 又暴露了 focus()
,因而我們可以用 field.focus()
的形式執行 InputField instance 的 focus()
:
小結
看了各種花式 bind:
是不是越來越混亂了呢?別擔心,我也是。:p