Svelte 속성
속성 내보내기
값은 주어진 컴포넌트 내에서만 액세스할 수 있는데, 실제 응용 프로그램에서는 한 컴포넌트의 데이터를 하위 컴포넌트로 전달해야 하는 경우가 많습니다.
한 컴포넌트에서 다른 컴포넌트로 데이터를 전달하기 위해서는 일반적으로 ‘props’로 축약되는 속성을 선언해야 하는데, Svelte에서는 export 키워드로 이를 수행할 수 있습니다.
export let answer;
다음의 코드는 상위 컴포넌트인 App.svelte에서 컴포넌트에 데이터를 전달하고, 하위 컴포넌트인 Nested.svelte에서 export
로 데이터를 전달받아 출력하는 예제입니다.
<script> import Nested from './Nested.svelte'; </script> <Nested answer={42}/>
<script> export let answer; </script> <p>The answer is {answer}</p>
속성 기본값 설정
하위 컴포넌트인 Nested.svelte에서는 속성의 데이터의 기본값을 쉽게 설정 할 수 있습니다.
export let answer = 'a mystery';
위 코드와 같이 export
할 데이터 속성에 기본 값을 넣어주기만 하면 answer
속성은 컴포넌트를 추가할 때 데이터를 전달하지 않아도 기본 값이 속성에 전달되기 때문에 해당 기본 값으로 컴포넌트가 표시됩니다.
<script> import Nested from './Nested.svelte'; </script> <Nested answer={42}/> <Nested/>
<script> export let answer = 'a mystery'; </script> <p>The answer is {answer}</p>
객체 속성 전달
속성 개체가 존재하는 경우에는 각각의 속성을 하나씩 지정하는 대신 {…pkg}와 같이 해당 객체를 직접 전달할 수 있습니다. 다음 코드와 같이 pkg
라는 객체를 만들어 객체에 필요한 속성들을 추가하여 객체를 전달하면, 해당 객체를 받는 컴포넌트에서는 export
로 데이터를 받을 수 있습니다.
<script> import Info from './Info.svelte'; const pkg = { name: 'svelte', version: 3, speed: 'blazing', website: 'https://svelte.dev' }; </script> <Info {...pkg}/>
<script> export let name; export let version; export let speed; export let website; </script> <p> The <code>{name}</code> package is {speed} fast. Download version {version} from <a href="https://www.npmjs.com/package/{name}">npm</a> and <a href={website}>learn more here</a> </p>
컴포넌트의 모든 속성 참조하기
반대로 export
로 선언되지 않은 것을 포함하여 컴포넌트에 전달된 모든 props
를 참조해야 하는 경우에는 $$props
에 직접 액세스하여 해당 데이터를 참조할 수 있습니다. 하지만 $$props로 참조하는 경우에는 Svelte로 최적화하기가 어렵기 때문에 권장되는 방법은 아닙니다.
블록 조건 구문
HTML은 조건문이나 루프같은 논리를 표현하는 방법이 없지만, Svelte는 블록 래핑으로 조건이나 반복을 수행하여 마크업을 렌더링할 수 있습니다.
if 블록 구문
다음의 코드는 if 블록으로 래핑하여 조건에 따라 컴포넌트의 일부만 렌더링하는 코드입니다.
<script> let user = { loggedIn: false }; function toggle() { user.loggedIn = !user.loggedIn; } </script> {#if user.loggedIn} <button on:click={toggle}> Log out </button> {/if} {#if !user.loggedIn} <button on:click={toggle}> Log in </button> {/if}
if-else 블록 구문
앞에서 살펴 본 if 문을 사용한 코드의 경우, if user.logged In
과 if !user.logged In
의 두 조건은 상호 배타적이기 때문에 else 블록을 사용하여 코드를 더 단순하게 표현할 수 있습니다.
<script> let user = { loggedIn: false }; function toggle() { user.loggedIn = !user.loggedIn; } </script> {#if user.loggedIn} <button on:click={toggle}> Log out </button> {:else} <button on:click={toggle}> Log in </button> {/if}
위 코드를 살펴보면 블록 구문은 항상 #으로 시작하고, / 문자로 닫는다는 것을 알 수 있는데, :else
와 같이 구문의 중간에 들어가는 : 문자는 블록의 연속 태그임을 나타내는 규칙입니다.
if-else-if 블록 구문
if 문을 사용하는 경우 if-else-if
와 같이 여러 조건을 연결하여 사용하는 경우가 많은데, Svelte의 블록 구문 역시 이런 문법을 지원하고 있습니다.
<script> let x = 7; </script> {#if x > 10} <p>{x} is greater than 10</p> {:else if 5 > x} <p>{x} is less than 5</p> {:else} <p>{x} is between 5 and 10</p> {/if}
블록 루프 구문
만약 HTML의 컴포넌트를 데이터 목록 만큼 반복해야 하는 경우에는 다음과 같이 each 블록을 사용할 수 있습니다.
<ul> {#each cats as cat} <li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}"> {cat.name} </a></li> {/each} </ul>
표현식은 임의의 배열 또는 배열과 유사한 객체가 될 수 있는데, each [...iterable]
을 사용하여 일반 HTML 컴포넌트를 반복할 수 있습니다.
만약 인덱스가 필요하면, 다음과 같이 두 번째 인수로 인덱스를 가져올 수 있습니다.
{#each cats as cat, i} <li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}"> {i + 1}: {cat.name} </a></li> {/each}
필요한 경우에는 each cats as { id, name }
와 같이 구조화 문법을 사용할 수 있는데, 구조화를 사용하는 경우에는 cat.id
와 cat.name
대신 id
와 name
으로 데이터를 가져올 수 있습니다.
<script> let cats = [ { id: 'J---aiyznGQ', name: 'Keyboard Cat' }, { id: 'z_AbfPXTKms', name: 'Maru' }, { id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' } ]; </script> <h1>The Famous Cats of YouTube</h1> <ul> {#each cats as { id, name }, i} <li><a target="_blank" href="https://www.youtube.com/watch?v={id}"> {i + 1}: {name} </a></li> {/each} </ul>
고유 식별자 지정하기
기본적으로 each
블록의 값을 수정하면 블록의 끝에 항목이 추가되거나 제거되고, 변경된 값이 업데이트 되어야 하는데, 다음의 코드를 실행해보면 Remove first thing 버튼을 클릭해도 첫 번째 요소가 제거되지 않고, 마지막 DOM 노드가 제거 된다는 것을 알 수 있습니다.
<script> import Thing from './Thing.svelte'; let things = [ { id: 1, name: 'apple' }, { id: 2, name: 'banana' }, { id: 3, name: 'carrot' }, { id: 4, name: 'doughnut' }, { id: 5, name: 'egg' }, ]; function handleClick() { things = things.slice(1); } </script> <button on:click={handleClick}> Remove first thing </button> {#each things as thing} <Thing name={thing.name}/> {/each}
<script> const emojis = { apple: "????", banana: "????", carrot: "????", doughnut: "????", egg: "????" } // prop 값이 변경될 때마다 이름 업데이트 export let name; // "emoji" 변수는 컴포넌트 초기화 시 고정됨 const emoji = emojis[name]; </script> <p> <span>The emoji for { name } is { emoji }</span> </p> <style> p { margin: 0.8em 0; } span { display: inline-block; padding: 0.2em 1em 0.3em; text-align: center; border-radius: 0.2em; background-color: #FFDFD3; } </style>
위 코드는 <Thing>
의 첫 번째 DOM 노드를 제거한 후, 나머지 DOM 노드에서 name
을 업데이트하지만 이모티콘은 제대로 업데이트가 되지 않는데, 만약 <Thing>
의 첫 번째 DOM 노드만 제거하고 나머지는 영향을 받지 않도록 하고 싶다면, 다음과 같이 each
블록에 고유한 식별자를 지정해 주어야 합니다.
{#each things as thing (thing.id)} <Thing name={thing.name}/> {/each}
위 코드에서 (thing.id)
는 컴포넌트가 업데이트될 때 변경할 DOM 노드를 Svelte에 알려주는 키의 역할을 하는데, Svelte는 내부적으로 Map을 사용하기 때문에 모든 객체를 키로 사용할 수 있습니다.
즉, (thing.id)
대신 (thing)
을 사용해도 되지만, 굳이 문자열이나 숫자를 사용하는 것을 권장하는 것은, 예를 들어 API 서버에서 최신 데이터로 업데이트할 때 참조 동등성 없이 ID가 지속된다는 의미가 되어, 더 안전한 동작을 수행할 수 있기 때문이라고 합니다.
<script> import Thing from './Thing.svelte'; let things = [ { id: 1, name: 'apple' }, { id: 2, name: 'banana' }, { id: 3, name: 'carrot' }, { id: 4, name: 'doughnut' }, { id: 5, name: 'egg' }, ]; function handleClick() { things = things.slice(1); } </script> <button on:click={handleClick}> Remove first thing </button> {#each things as thing (thing.id) } <Thing name={thing.name}/> {/each}
<script> const emojis = { apple: "????", banana: "????", carrot: "????", doughnut: "????", egg: "????" } // the name is updated whenever the prop value changes... export let name; // ...but the "emoji" variable is fixed upon initialisation of the component const emoji = emojis[name]; </script> <p> <span>The emoji for { name } is { emoji }</span> </p> <style> p { margin: 0.8em 0; } span { display: inline-block; padding: 0.2em 1em 0.3em; text-align: center; border-radius: 0.2em; background-color: #FFDFD3; } </style>
비동기 처리하기
대부분의 웹 애플리케이션은 어느 시점에서 비동기 데이터를 처리해야 하는데, Svelte에서는 마크업에서 직접 Promise의 값을 기다릴 수 있는 awit 문을 사용할 수 있습니다.
{#await promise} <p>...waiting</p> {:then number} <p>The number is {number}</p> {:catch error} <p style="color: red">{error.message}</p> {/await}
await
는 가장 최근의 promise
만 고려되기 때문에 다른 조건에 대해 고민할 필요가 없는데, 만약 promise
를 취소 할 수 없다는 것을 알고 있는 경우에는 catch
블록을 생략할 수 있고, 만약 Promise
가 수행될 때까지 아무것도 표시하고 싶지 않다면 첫 번째 블록을 생략할 수도 있습니다.
{#await promise then value} <p>the value is {value}</p> {/await}
다음은 promise를 수행하는 전체 샘플 코드입니다.
<script> async function getRandomNumber() { const res = await fetch(`/tutorial/random-number`); const text = await res.text(); if (res.ok) { return text; } else { throw new Error(text); } } let promise = getRandomNumber(); function handleClick() { promise = getRandomNumber(); } </script> <button on:click={handleClick}> generate random number </button> {#await promise} <p>...waiting</p> {:then number} <p>The number is {number}</p> {:catch error} <p style="color: red">{error.message}</p> {/await}