Svelte 表單資料綁定
tags: Svelte
category: Front-End
description: Svelte 表單資料綁定
created_at: 2023/04/04 20:00:00
前言
終於到了表單的部分了,有了框架的幫忙,可以讓我們處理表單變得更輕鬆
雖然還有一些套件可以更更更輕鬆,但不在這篇範圍內
雙向資料綁定
使用 bind
去做到這件事,簡單來說就是一個變數,他變動的時候畫面要變,而如果透過 input
之類的東西改變了 value
,變數值也要同時更新。
簡單來說就是以往需要去監聽(例如 onInput
)事件去修改變數,而變數修改過後還要去更新用到他的 DOM
的內容。
單向資料綁定
上面提雙向,就是兩邊都要同步,而單向則是上到下,變數改變的時候,被綁定上的屬性會即時更新。
簡單來說前面文章所用到的屬性綁定 prop={value}
,就是一個單向綁定,當 value
改變時 prop
會改變。
小結 - 資料綁定
上面文字描述可能很模糊,這邊附上一張圖(剛剛臨時畫的)
左邊那條線是單向資料綁定,加上右邊那條線就是雙向資料綁定。
- 單向:
APP
透過左邊那條線把value
綁到input
身上,而input
沒辦法更改變數的值 (但如果變數值變化,input
的value
也會改) - 雙向: 除了單向以外,
input
值如果修改,變數值也會跟著修改 (如同單向,如果變數值變化,input
的value
也會改)
開始來 bind 各種 input
- text
首先當然是最常用的 text
<script lang="ts">
let textValue = 'Hello World';
</script>
<input type="text" bind:value={textValue} />
<div>input: {textValue}</div>
嘗試去輸入東西,會發現下面的文字也跟著變動。
也有偷懶的寫法,如果你要 bind
的變數剛好跟 prop
一樣,可以這樣簡寫
<script lang="ts">
let value = 'Hello World';
</script>
<input type="text" bind:value />
<div>input: {value}</div>
只是第一時間不這樣寫,怕剛開始看的人會誤會
- number & range
<script lang="ts">
let a = 1;
let b = 2;
</script>
<input type="number" bind:value={a} min="0" max="10" />
<input type="range" bind:value={b} min="0" max="10" />
<div>{a} + {b} = {a + b}</div>
這邊 Svelte
很貼心,有幫忙轉換成 number
型態,不然會引發悲劇(?)
一般情況下 input.value
你會拿到 string
,也就是說 1 + 1 = 11
- radio & checkbox
先來看看 checkbox
<script lang="ts">
let isCheck = false;
</script>
<input type="checkbox" bind:checked={isCheck} />
{isCheck}
也許可以稍微包裝一下,不然只有 true
和 false
<script lang="ts">
let checked = false;
$: checkedValue = checked ? 'checked' : 'unchecked';
</script>
<input type="checkbox" bind:checked />
{checkedValue}
這樣就可以達到 Vue
當中的 true-value
與 false-value
搭配 v-model
使用的結果。
假設我想要多選,然後選取的東西幫我丟到一個陣列中,可以使用 bind:group
這個語法。
typeof
可以不要看,如果你是 JavaScript
使用者的話。
<script lang="ts">
const items = ['A', 'B', 'C'];
let selectedItems: typeof items = [];
</script>
{#each items as item}
<label>
<input type="checkbox" bind:group={selectedItems} value={item} />
{item}
</label>
{/each}
<div>
{selectedItems}
</div>
這樣的話當你核取東西,下方就會顯示出你選取的項目。
等同於 Vue
當中的
<script setup>
import { ref } from "vue";
const categories = ["A", "B", "C"];
const values = ref([]);
</script>
<template>
<label v-for="category in categories" :key="category">
<input type="checkbox" v-model="values" :value="category" />
{{ category }}
</label>
{{ values }}
</template>
這也是從 Vue 3
文章貼過來的
換到 radio
概念相同
<script lang="ts">
const items = ['A', 'B', 'C'];
let selectedItem = 'A';
</script>
{#each items as item}
<label>
<input type="radio" bind:group={selectedItem} value={item} />
{item}
</label>
{/each}
<div>
{selectedItem}
</div>
幾乎長的一樣,總之就是那個 bind:group
- textarea
這基本上跟 text
一模一樣
<script lang="ts">
let value = 'Hello World';
</script>
<textarea bind:value />
<div>{value}</div>
- select
基本用法
<script lang="ts">
let options = ['A', 'B', 'C'];
let selected = options[0];
</script>
<select bind:value={selected}>
{#each options as option}
<option value={option}>{option}</option>
{/each}
</select>
<div>{selected}</div>
而 Svelte
也可以接受 option
的 value
不是 string
,也就是你還可以這樣
<script lang="ts">
let options = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
{ id: 3, name: 'three' }
];
let selected = options[0];
</script>
<select bind:value={selected}>
{#each options as option}
<option value={option}>{option.name}</option>
{/each}
</select>
<div>{selected.id}</div>
<div>{selected.name}</div>
可能會常常有一種需求,一開始 select
要選到一個預設的 請選擇項目
之類的 option
,當使用者選了其他才有辦法送出。
<script lang="ts">
let options = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
{ id: 3, name: 'three' }
];
let selected: (typeof options)[0];
</script>
<form method="post">
<select bind:value={selected} required>
<option disabled selected value="">Please select item</option>
{#each options as option}
<option value={option}>{option.name}</option>
{/each}
</select>
<button type="submit">Submit</button>
</form>
{#if selected}
<div>{selected.id}</div>
<div>{selected.name}</div>
{/if}
需要的直接拿去改
至於 multiple
版本的 select
基本上大同小異,而且很少用,這邊就偷懶跳過了。
bind 其他東西
當一個元素設定了 contenteditable
屬性,則可以直接在上面做編輯,設定了之後也可以做雙向資料綁定,如下:
<script lang="ts">
let textContent = 'Hello World';
</script>
<div contenteditable="true" bind:textContent />
{textContent}
建議 bind
在
textContent
innerHTML
而不是綁在 innerText
身上,至少我的 IDE
會噴,但 Svelte
看起來是接受的 QQ
我的理由是因為 textContent
才是真正完整的 content
,而 innerText
他會過濾掉一些人看不到的東西,有興趣可以查那三個的差異(innerHTML
、innerText
、textContent
)
綁定 this
有時候我可能希望直接抓到某個 DOM
或是組件
像是 Vue 2
的 this.$refs
,可能拼錯,但我相信用過的你懂我意思
或是說 React
的 useRef
總之就是想把 DOM
抓出來存到一個變數,那你可以這樣做
<script lang="ts">
let dom: HTMLElement;
$: {
if (dom) {
dom.click();
}
}
</script>
<button bind:this={dom} on:click={() => console.log('click!')}> Click </button>
透過 bind:this
去達到這個功能。
也可以透過這種方式抓到你自訂的組件:
<script lang="ts">
import Child from '../components/Child.svelte';
let child: Child;
const onClick = () => {
child.callChild();
};
</script>
<Child bind:this={child} />
<button on:click={onClick}>Call child</button>
假設 Child.svelte
有暴露一個 callChild
函數出去,如下:
<script lang="ts">
export function callChild() {
console.log('call child');
}
</script>
<div>Child</div>
這樣就又可以做很多花樣了