Beomy

[Svelte] 데이터 바인딩 기초

  15 mins read  

이번 포스트에서는 데이터 바인딩을 이야기합니다. Vue.js에서 v-model로 데이터 바인딩 하는 방법을 Svelte에서는 어떻게 바인딩 하는지 알아보도록 하겠습니다.

input

<input> 태그의 데이터 바인딩을 살펴보도록 하겠습니다. 데이터 바인딩을 하지 않으면, value 속성에 값을 할당하고 input 이벤트가 발생할 때마다 value 속성을 업데이트해 줘야 합니다. 데이터 바인딩으로 이 과정을 생략할 수 있습니다.

type="text"

<input> 태그의 type 속성이 text 혹은 정의되어 있지 않을 경우, Vue.js는 아래와 같이 v-model을 사용하여 아래와 같이 데이터 바인딩을 구현할 수 있습니다.

<template>
  <div>
    <input v-model="name">
    <h1>Hello {{ name }}!</h1>
  </div>
</template>
<script>
  export default {
    data () {
      return {
        name: 'world'
      }
    }
  }
</script>

위의 코드를 Svelte에서는 아래 코드와 같이 구현할 수 있습니다.

<script>
  let name = 'world';
</script>

<input bind:value="{name}">
<h1>Hello {name}!</h1>

Svelte의 bind:value는 Vue.js의 v-model과 동일한 역할을 합니다.

type="number", type="range"

<input> 태그 value 속성의 값의 데이터 타입이 Number로 지정하는 방법을 살펴보도록 하겠습니다. Vue.js에서는 아래와 같이 구현할 수 있습니다.

<template>
  <div>
    <label>
      <input v-model.number="a" type="number" min="0" max="10">
      <input v-model.number="a" type=range min="0" max="10">
    </label>

    <label>
      <input v-model.number="b" type="number" min="0" max="10">
      <input v-model.number="b" type=range min="0" max="10">
    </label>

    <p>{{ a }} + {{ b }} = {{ a + b }}</p>
  </div>
</template>
<script>
  export default {
    data () {
      return: {
        a: 1,
        b: 2
      }
    }
  }
</script>

Vue.js는 v-model.number을 사용하여 바인딩 된 데이터 타입을 Number로 지정할 수 있습니다. Svelte는 아래 코드와 같이 구현할 수 있습니다.

<script>
  let a = 1;
  let b = 2;
</script>

<label>
  <input type="number" bind:value="{a}" min="0" max="10">
  <input type="range" bind:value="{a}" min="0" max="10">
</label>

<label>
  <input type="number" bind:value="{b}" min="0" max="10">
  <input type="range" bind:value="{b}" min="0" max="10">
</label>

<p>{a} + {b} = {a + b}</p>

Svelte에서는 Vue.js와는 달리 별도의 코드를 추가할 필요 없이 <input> 태그의 type 속성이 number 또는 range일 경우 자동으로 Number 타입으로 지정됩니다.

type="checkbox"

<input type="checkbox">를 살펴보도록 하겠습니다. Vue.js에서는 아래 코드와 같이 <input type="checkbox">을 사용할 수 있습니다.

<template>
  <div>
    <label>
      <input v-model="yes" type="checkbox">
      Yes! Send me regular email spam
    </label>

    <p v-if="yes">Thank you. We will bombard your inbox and sell your personal details.</p>
    <p v-else>You must opt in to continue. If you're not paying, you're the product.</p>

    <button :disabled="!yes">
      Subscribe
    </button>
  </div>
</template>
<script>
  export default {
    data () {
      return {
        yes: false
      }
    }
  }
</script>

위의 코드를 Svelte에서는 아래와 같이 구현할 수 있습니다.

<script>
  let yes = false;
</script>

<label>
  <input type="checkbox" bind:checked="{yes}">
  Yes! Send me regular email spam
</label>

{#if yes}
  <p>Thank you. We will bombard your inbox and sell your personal details.</p>
{:else}
  <p>You must opt in to continue. If you're not paying, you're the product.</p>
{/if}

<button disabled="{!yes}">
  Subscribe
</button>

type="radio"

<input type="radio">의 경우 여러 개의 <input> 태그들에 동일한 데이터를 바인딩 해야 합니다. Vue.js에서는 아래와 같이 구현할 수 있습니다.

<template>
  <div>
    <label>
      <input type="radio" value="One" v-model="picked">
      One
    </label>
    <br>
    <label>
      <input type="radio" value="Two" v-model="picked">
      Two
    </label>
    <br>
    <span>선택: {{picked}}</span>
  </div>
</template>
<script>
  export default {
    data () {
      return {
        picked: null
      }
    }
  }
</script>

Vue.js에서는 v-model에 동일한 데이터를 바인딩하고 value에 각각의 <input> 태그들을 선택했을 경우에 저장되는 데이터를 지정합니다. Svelte에서는 아래와 같이 구현할 수 있습니다.

<script>
  let picked = null;
</script>
<label>
  <input type="radio" value="One" bind:group="{picked}">
  One
</label>
<br>
<label>
  <input type="radio" value="Two" bind:group="{picked}">
  Two
</label>
<br>
<span>선택: {picked}</span>

Svelte에서는 bind:group을 사용하여 데이터를 바인딩하고 value에 각각의 <input> 태그들을 선택했을 경우에 저장되는 데이터를 지정합니다.

bind:group

bind:group<input type="radio">에서뿐만 아니라 여러 개의 <input> 태그에 하나의 데이터를 바인딩 해야 할 때 사용할 수 있습니다. Vue.js에서는 아래와 같이 구현할 수 있습니다.

<template>
  <div>
    <label v-for="(name, index) of names" :key="index">
      <input type="checkbox" :value="name" v-model="checkedNames">
      {{ name }}
    </label>
    <br>
    <span>체크한 이름: {{ checkedNames }}</span>
  </div>
</template>
<script>
  export default {
    data () {
      return {
        names: ['jack', 'John', 'Mike'],
        checkedNames: []
      }
    }
  }
</script>

위의 코드를 Svelte에서는 아래 코드와 같이 구현할 수 있습니다.

<script>
  const names = ['jack', 'John', 'Mike'];
  let checkedNames = [];
</script>
{#each names as name, index (index)}
  <label>
    <input type="checkbox" value="{name}" bind:group="{checkedNames}">
    {name}
  </label>
{/each}
<br>
<span>체크한 이름: {checkedNames}</span>

위의 코드와 같이 Svelte에서 bind:group<input type="radio">에서뿐만 아니라 동일한 데이터를 바인딩을 해야 할 경우 사용할 수 있습니다.

textarea

<textarea> 태그의 데이터 바인딩 방법을 살펴보도록 하겠습니다. Vue.js에서는 아래와 같이 사용할 수 있습니다.

<template>
  <textarea v-model={value}></textarea>
</template>
<script>
  export default {
    data () {
      return {
        value: 'Some Text'
      }
    }
  }
</script>
<style>
	textarea { width: 100%; height: 200px; }
</style>

위의 코드를 Svelte에서는 아래와 같이 구현할 수 있습니다.

<script>
  let value = 'Some Text';
</script>

<style>
  textarea { width: 100%; height: 200px; }
</style>

<textarea bind:value="{value}"></textarea>

바인딩 해야 하는 속성 이름과 변수의 이름이 동일할 때(위의 코드에서는 bind:valuevaluelet valuevalue), 아래와 같이 약어 기능을 제공합니다.

<textarea bind:value></textarea>

<textarea> 태그의 데이터 바인딩 방법은 <input type="text"> 태그와 동일합니다.

select

<select> 태그의 데이터 바인딩 방법을 살펴보도록 하겠습니다. Vue.js에서는 아래와 같이 사용할 수 있습니다.

<template>
  <div>
    <select v-model="selected">
      <option
        v-for="item of list"
        :key="item.id"
        :value="item"
      >
        {{ item.text }}
      </option>
    </select>
    <span>선택함: {{ selected ? selected.id : 'waiting...' }}</span>
  </div>
</template>
<script>
  export default {
    data () {
      return {
        list: [
          { id: 1, text: 'A' },
          { id: 2, text: 'B' },
          { id: 3, text: 'C' }
        ],
        selected: null
      }
    }
  }
</script>

위의 코드를 Svelte는 아래 코드와 같이 구현할 수 있습니다.

<script>
  const list = [
    { id: 1, text: 'A' },
    { id: 2, text: 'B' },
    { id: 3, text: 'C' }
  ]
  let selected
</script>
<select bind:value="{selected}">
  {#each list as item (item.id)}
    <option value="{item}">{item.text}</option>
  {/each}
</select>
<span>선택함: {selected ? selected.id : 'waiting...'}</span>

bind:value로 데이터 바인딩 되는 값의 타입은 Object, String은 물론 모든 타입이 가능합니다. 바인딩 데이터의 초깃값이 정의되어 있지 않다면 리스트의 첫 번째 값이 디폴트 값으로 저장됩니다.

multiple

<select> 태그는 multiple 속성을 지원합니다. multiple 속성을 설정하면 바인딩 된 데이터는 배열 타입이 됩니다. Vue.js에서는 아래 코드와 같이 사용할 수 있습니다.

<template>
  <div>
    <select v-model="selected" multiple>
      <option
        v-for="item of list"
        :key="item.id"
        :value="item"
      >
        {{ item.text }}
      </option>
    </select>
    <span>선택함: {{ selected ? selected.map(x => x.id).join(',') : 'waiting...' }}</span>
  </div>
</template>
<script>
  export default {
    data () {
      return {
        list: [
          { id: 1, text: 'A' },
          { id: 2, text: 'B' },
          { id: 3, text: 'C' }
        ],
        selected: []
      }
    }
  }
</script>

위의 코드를 Svelte에서는 아래와 같이 작성할 수 있습니다.

<script>
  const list = [
    { id: 1, text: 'A' },
    { id: 2, text: 'B' },
    { id: 3, text: 'C' }
  ]
  let selected = []
</script>
<select multiple bind:value="{selected}">
  {#each list as item (item.id)}
    <option value="{item}">{item.text}</option>
  {/each}
</select>
<span>선택함: {selected ? selected.map(x => x.id).join(',') : 'waiting...'}</span>

contenteditable

contenteditable="true"를 사용하면 textContentinnerHTML 바인딩을 지원합니다. Vue.js에서는 지원하지 않는 기능입니다. Svelte는 아래와 같이 사용할 수 있습니다.

<script>
  let html = '<p>Write some text!</p>';
</script>

<div
  contenteditable="true"
  bind:innerHTML={html}
></div>

<pre>{html}</pre>

<style>
  [contenteditable] {
    padding: 0.5em;
    border: 1px solid #eee;
    border-radius: 4px;
  }
</style>

Each 블록 바인딩

이번에는 반복문 블록 안에서 데이터 바인딩 하는 방법을 알아보도록 하겠습니다. Vue.js에서는 아래 코드와 같이 사용할 수 있습니다.

<template>
  <h1>Todos</h1>

  <div
    v-for="(todo, index) of todos"
    :key="index"
    :class="{ done: todo.done }"
  >
    <input
      v-model="todo.done"
      type="checkbox"
    >

    <input
      v-model="todo.text"
      placeholder="What needs to be done?"
    >
  </div>

  <p>{{ remaining }} remaining</p>

  <button @click="add">Add new</button>
  <button @click="clear">Clear completed</button>
</template>
<script>
  export default {
    data () {
      return {
        todos: [
          { done: false, text: 'finish Svelte tutorial' },
          { done: false, text: 'build an app' },
          { done: false, text: 'world domination' }
        ]
      }
    },
    computed: {
      remaining () {
        return this.todos.filter(t => !t.done).length
      }
    },
    methods: {
      add () {
        this.todos.push({ done: false, text: '' })
      },
      clear () {
        this.todos = this.todos.filter(t => !t.done);
      }
    }
  }
</script>
<style>
  .done {
    opacity: 0.4;
  }
</style>

위의 코드를 Svelte에서 아래와 같이 구현할 수 있습니다.

<script>
  let todos = [
    { done: false, text: 'finish Svelte tutorial' },
    { done: false, text: 'build an app' },
    { done: false, text: 'world domination' }
  ];

  function add() {
    todos = todos.concat({ done: false, text: '' });
  }

  function clear() {
    todos = todos.filter(t => !t.done);
  }

  $: remaining = todos.filter(t => !t.done).length;
</script>

<style>
  .done {
    opacity: 0.4;
  }
</style>

<h1>Todos</h1>

{#each todos as todo, index (index)}
  <div class:done={todo.done}>
    <input
      type=checkbox
      bind:checked={todo.done}
    >

    <input
      placeholder="What needs to be done?"
      bind:value={todo.text}
    >
  </div>
{/each}

<p>{remaining} remaining</p>

<button on:click={add}>Add new</button>
<button on:click={clear}>Clear completed</button>

위의 Svelte 코드는 많은 기능을 사용한 코드입니다.

  • addclear 함수는 반응형 동작을 위해 todos를 재할당 합니다.
  • $:todos가 변경되었을 때 실행됩니다. !donetodos의 개수를 remaining에 저장합니다.
  • class:donetodo.done이 ture일 경우 done 클래스가 추가됩니다.
  • {#each todos as todo, index (index)} 블록 내에 bind:checkedbind:value를 사용하여 todos의 변경 내용이 반영합니다. Vue.js에서 v-for 블록문 안에 v-model을 사용하는 것과 유사합니다. Svelte는 {# each} 블록문 안에 bind:속성 이름으로 블록문 안에서 데이터 바인딩 할 수 있습니다.

참고