일반적으로 Svelte는 부모 컴포넌트에서 자식 컴포넌트로 데이터가 전달되는 단방향 데이터 흐름을 가진다. 단방향 데이터 흐름으로 인해 부모 컴포넌트
에서 자식 컴포넌트
로 전달되는 데이터가 업데이트될 때 자식 컴포넌트는 영향을 받아 반응형 동작을 하게 되지만, 반대로 자식 컴포넌트
에서 부모 컴포넌트
로 전달받은 데이터가 업데이트될 때 부모 컴포넌트는 영향을 받지 않는다.
이런 규칙을 깨고 자식 컴포넌트에서 부모 컴포넌트로 전달받은 데이터가 업데이트될 때 부모 컴포넌트가 반응형 동작을 할 수 있게 하는 양방향 데이터 흐름을 가질 수 있도록 하는 것이 데이터 바인딩이다. 🙂
<script>
let name = 'world';
</script>
<input bind:value={name}>
<h1>Hello {name}!</h1>
이처럼 input에 입력된 값이 변경될 때마다 자동으로 name의 값 또한 변경되는 것을 볼 수 있다.
<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>
input 태그의 type 속성이 number 혹은 range일 경우 바인딩되는 값은 자동으로 Number 타입이 된다.
<script>
let yes = false;
</script>
<label>
<input type="checkbox" bind:checked={yes}>
CheckBox
</label>
type 속성이 checkbox인 경우, bind:checked를 사용해서 checked 속성을 바인딩해야 한다.
<script>
let picked = null;
</script>
<label>
<input type="radio" value="One" bind:group={picked}>
One
</label>
<label>
<input type="radio" value="Two" bind:group={picked}>
Two
</label>
<br>
<span>선택: {picked}</span>
radio의 경우 여러 개의 input 태그들이 동일한 데이터에 바인딩되어야 한다. 이때는 bind:group
을 사용한다. value 속성에서는 각각 input 태그들이 선택되었을 때 지정되어야 하는 데이터를 지정한다. bind:group은 type이 radio에서뿐만 아니라 동일한 데이터를 바인딩해야 할 경우에도 사용 가능하다.
<script>
let value = 'Some Text';
</script>
<style>
textarea { width: 100%; height: 200px;}
</style>
<!-- <textarea bind:value={value}></textarea> -->
<textarea bind:value></textarea> // 약어 사용 가능
<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 : '기다리는 중...'}</span>
bind:value로 데이터 바인딩되는 값의 타입은 Object, String 등 모든 타입이 가능하다. 바인딩 데이터의 초기값이 정의되어 있지 않다면 리스트이 첫 번째 값이 기본값으로 저장된다.
<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(',') : '기다리는 중...'}</span>
<script>
let html = '<p>텍스트를 입력해 주세요.</p>'
</script>
<div
contenteditable="true"
bind:innerHTML={html}
></div>
<div
contenteditable="true"
bind:innerHTML={html}
></div>
<pre>{html}</pre>
<style>
[contenteditable] {
padding: 0.5em;
border: 1px solid #eee;
border-radius: 4px;
}
</style>
contenteditable를 true로 설정하면 textContent와 innerHTML 속성을 바인딩하여 사용할 수 있다.
<script>
import { each } from "svelte/internal";
let todos = [
{done: false, text: '독서하기'},
{done: false, text: 'Svelte 공부하기'},
{done: false, text: '일기 쓰기'}
]
</script>
{#each todos as todo, index (index)}
<label class:done={todo.done}>
<input
type="checkbox"
bind:checked={todo.done}>
{todo.text}
</label>
{/each}
DOM 요소를 가져오고 싶거나 컴포넌트 인스턴스를 가져올 때 사용하는 것이 This 바인딩이다.
<script>
let value;
let input;
</script>
<input bind:value bind:this={input}>
<button on:click={() => input.focus()}>focus</button>
버튼을 클릭하면 input 태그가 포커스되고, bind:this={input}와 같이 작성하면 input 변수에 input 태그가 바인딩된다. 이벤트 핸들러 등의 다른 함수에서 바인딩된 DOM 요소를 접근해서 사용할 수 있다.
<!-- src/Inner.svelte -->
<script>
let value, input;
</script>
<input bind:value bind:this={input}>
<script>
import Inner from "./Inner.svelte";
let inner;
</script>
<Inner bind:this={inner} />
<button on:click={() => inner.$capture_state().input.focus()}>Focus</button>
This를 바인딩하여 컴포넌트 인스턴스를 가져온 경우, 바인딩된 컴포넌트 인스턴스의 $capture_state()를 호출하면 해당 컴포넌트의 데이터(State) 값을 가져올 수 있다.
HTML 속성을 바인딩한 것처럼 bind:Props를 사용하여 컴포넌트의 Props를 바인딩할 수도 있다.
<!-- src/Inner.svelte -->
<script>
export let value;
</script>
<input bind:value>
<script>
import Inner from "./Inner.svelte";
let value;
function handleClick() {
alert(value);
}
</script>
<Inner bind:value />
<button on:click={handleClick}>출력</button>
Inner 컴포넌트는 value라는 Props를 전달받고, App 컴포넌트는 bind:value로 value라는 Props를 바인딩했다. 이렇게 컴포넌트의 Props를 바인딩하면 Inner 컴포넌트에서도 App 컴포넌트의 value 값을 업데이트할 수 있는 양방향 데이터 흐름을 가지게 된다.