Svelte 表單資料綁定

tags: Svelte
category: Front-End
description: Svelte 表單資料綁定
created_at: 2023/04/04 20:00:00

cover image


回到 手把手開始寫 Svelte


前言

終於到了表單的部分了,有了框架的幫忙,可以讓我們處理表單變得更輕鬆

雖然還有一些套件可以更更更輕鬆,但不在這篇範圍內


雙向資料綁定

使用 bind 去做到這件事,簡單來說就是一個變數,他變動的時候畫面要變,而如果透過 input 之類的東西改變了 value,變數值也要同時更新。

簡單來說就是以往需要去監聽(例如 onInput)事件去修改變數,而變數修改過後還要去更新用到他的 DOM 的內容。


單向資料綁定

上面提雙向,就是兩邊都要同步,而單向則是上到下,變數改變的時候,被綁定上的屬性會即時更新。

簡單來說前面文章所用到的屬性綁定 prop={value},就是一個單向綁定,當 value 改變時 prop 會改變。


小結 - 資料綁定

上面文字描述可能很模糊,這邊附上一張圖(剛剛臨時畫的)

左邊那條線是單向資料綁定,加上右邊那條線就是雙向資料綁定。

  • 單向: APP 透過左邊那條線把 value 綁到 input 身上,而 input 沒辦法更改變數的值 (但如果變數值變化,inputvalue 也會改)
  • 雙向: 除了單向以外, input 值如果修改,變數值也會跟著修改 (如同單向,如果變數值變化,inputvalue 也會改)

data-binding


開始來 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}

也許可以稍微包裝一下,不然只有 truefalse

<script lang="ts">
    let checked = false;
    $: checkedValue = checked ? 'checked' : 'unchecked';
</script>

<input type="checkbox" bind:checked />

{checkedValue}

這樣就可以達到 Vue 當中的 true-valuefalse-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 也可以接受 optionvalue 不是 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 他會過濾掉一些人看不到的東西,有興趣可以查那三個的差異(innerHTMLinnerTexttextContent)


綁定 this

有時候我可能希望直接抓到某個 DOM 或是組件

像是 Vue 2this.$refs可能拼錯,但我相信用過的你懂我意思

或是說 ReactuseRef

總之就是想把 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>

這樣就又可以做很多花樣了




最後更新時間: 2023年04月04日.