개발자가 사랑하는 프론트엔드 프레임워크2
스벨트(Svelte) 사용법

개발자가 사랑하는 프론트엔드 프레임워크2 스벨트(Svelte) 사용법

스벨트 공식 사이트에서는 REPL(Read-Eval-Print Loop)을 제공합니다. 또한 degit을 사용하여 롤업(Rollup) 기반의 프로젝트를 생성할 수 있습니다.

  • npx degit sveltejs/template my-svelte-project
  • cd my-svelte-project
  • npm install
  • npm run dev

스벨트에서 애플리케이션은 하나 이상의 컴포넌트로 구성됩니다. HTML의 수퍼셋을 사용하여 .svelte 파일에 작성됩니다. 스크립트·스타일·마크업의 세 섹션으로 나뉘며 각 섹션은 모두 선택 사항입니다.

  • <script>
  • // logic goes here
  • </script>
  • <style>
  • /* style go here */
  • </style>

마크업의 중괄호 안에 원하는 자바스크립트를 넣을 수 있습니다.

  • <script>
  • let name='world';
  • </script>
  • hello {name}!

또한 중괄호를 사용하여 요소 속성을 제어할 수 있습니다.

  • <script>
  • let src='tutorial/image.gif';
  • </script>
  • <img src = { src }>

<style> 태그를 사용하여 스타일을 추가할 수 있습니다. 스타일은 해당 컴포넌트에만 적용됩니다. 앱의 다른 곳에서 p 요소의 스타일을 변경하지 않습니다.

  • This is paragraph.

  • <style>
  • p {
  • color: purple;
  • font-family: 'comic Sans MS', cursive;
  • font-size: 2em;
  • }
  • </style>

요소를 포함하는 것처럼 다른 파일에서 컴포넌트를 가져와 포함할 수 있습니다. 또한 컴포넌트 이름은 대문자로 표시합니다. 이는 사용자 정의 컴포넌트와 HTML 태그를 구분할 수 있도록 하기 위해서입니다.

App.svelte
  • <script>
  • import Nested from './Nested.svelte';
  • </script>
  • This is paragraph.

  • ><Nested/>
Nested.svelte
This is another paragraph.

일반적인 문자열은 일반 텍스트로 삽입합니다. 때로는 HTML을 직접 렌더링해야 하는 경우도 있습니다. 이 때는 특별한 {@html …} 태그를 사용합니다.

  • <script>
  • let string = 'this string contains some HTML!!!';
  • </script>
  • {@html string}

스벨트의 중심에는 이벤트에 대한 응답과 같이 돔을 애플리케이션 상태와 동기화하기 위한 강력한 반응성 시스템이 있습니다. 스벨트는 변수 값 할당을 계측하여 돔을 업데이트해야 한다고 알려줍니다.

  • <script>
  • let count = 0';
  • function incrementCount() {
  • count +=1;
  • }
  • </script>
  • <button on : click = { incrementCount }>
  • Clicked {count} {count ===1? 'time': 'times'}
  • </button>

스벨트는 컴포넌트의 상태가 변경될 때 돔을 자동으로 업데이트합니다. 종종 컴포넌트의 상태 일부는 다른 부분에서 계산 및 변경될 때마다 다시 계산되어야 합니다. 이를 위한 반응성 선언 $:이 있습니다. 반응성 값은 여러 번 참조해야 하거나 다른 반응성 값에 의존하는 값이 있는 경우에 유용합니다.

  • <script>
  • let count = 0';
  • $: double = count * 2;
  • function handleClick() {
  • count +=1;
  • }
  • </script>
  • <button on:click={handleClick}>>
  • Clicked {count} {count ===1? 'time': 'times'}
  • </button>
  • {count} double is {doubled}

스벨트의 반응성은 할당에 의해 트리거되기 때문에 push 및 splice와 같은 배열 메서드를 사용하면 자동으로 업데이트가 발생하지 않습니다. 이를 수정하는 한 가지 방법은 아래 예시와 같이 중복되는 할당을 추가하는 것입니다.

  • <script>
  • let numbers = [1, 2, 3, 4];
  • function addNumber() {
  • numbers=[...numbers, numbers.length + 1];
  • }
  • $: sum - numbers.reduce((t, n) => t + n, 0);
  • </script>
  • {numbers.join(' + ')} = {sum}

  • <button on:click={addnumber}>
  • add a number
  • ></button>

지금까지의 설명에서 값은 주어진 컴포넌트 내에서만 접근 가능했습니다. 실제 애플리케이션에서는 한 컴포넌트에서 하위 컴포넌트로 데이터를 전달해야 합니다. 그렇게 하려면 일반적으로 props로 축약되는 속성을 선언해야 합니다. 스벨트는 다음과 같이 export 키워드로 이를 수행합니다.

App.svelte
  • <script>
  • import Nested from './Nested.svelte';
  • </script>
  • <Nested answer={42}/>
Nested.svelte
  • <script>
  • export let answer;
  • </script>
  • <p>The answer is {answer}</p>

HTML에는 조건문 및 루프와 같은 논리를 표현하는 방법이 없습니다. 마크업을 조건부로 렌더링하기 위해 if 블록으로 감쌉니다.

  • <script>
  • let user ={ loggedin: false };;
  • function toggle() {
  • user.loggin = !user.loggIn;
  • }
  • </script>
  • {#If user.loggIn}
  • <button on:click={toggle}>>
  • Log out
  • </button>
  • {/if}
  • {#If !user.loggIn}
  • <button on:click={toggle}>
  • Log in
  • </button>
  • {/if}

상호 배타적인 두 조건이 있을 경우 else 블록을 사용할 수 있습니다.

  • <script>
  • let user ={ loggedin: false };;
  • function toggle() {
  • user.loggin = !user.loggIn;
  • }
  • </script>
  • {#If user.loggIn}
  • <button on:click={toggle}>>
  • Log out
  • </button>
  • {/else}
  • <button on:click={toggle}>
  • Log in
  • </button>
  • {/if}

여러 조건일 경우 else if와 함께 연결할 수 있습니다.

  • <script>
  • let x=7;
  • </script>
  • {#if x > 10}
  • {x} is greater than 10

  • {:else if 5 > x}
  • {x} is less than 5

  • {:else}
  • {x} is between 5 and 10

  • {.if}

데이터 목록을 반복해야 하는 경우 each 블록을 사용합니다. 두 번째 인수로 현재 인덱스를 가져올 수 있습니다.

  • <script>
  • let cats= [
  • { id: 'j---aiyznGQ', name: 'Keyboard Cat' },
  • { id: 'z---AbfPXTKms', name: 'Maru' },
  • { id: 'j---OUtn3pvWmpg', name: 'Henri The Existential Cat' },
  • ];
  • </script>
  • The Famous Cats of TouTube

  • <ul>
  • {#each cats as cat, i}
  • <li> <a target="_blank" href="http://www.youtube.com/watch?v={cat.id}"></a>
  • {i+1}: {cat.name}
  • </a>
  • {/each}
  • </ul>

대부분의 애플리케이션은 비동기 데이터를 처리해야 합니다. await 블록을 사용하면 마크업에서 직접 값을 기다릴 수 있습니다.

  • <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>
  • {#If user.loggIn}
  • <button on:click={handleClick}>
  • generate random numbert
  • </button>
  • {#await promise}
  • ...waiting

  • {:then number}
  • The number is {number}

  • {:catch error}
  • <p style="color:red'> {error.message}</p>
  • {/awaitr}

on : 지시어를 사용하여 요소의 모든 이벤트를 수신할 수 있습니다.

  • <script>
  • let m= {x=0, y=0};
  • function handleMousemove(event) {
  • m.x=event.clintX;
  • m.y=event.clinty;
  • }
  • </script>
  • <div on:mousemove={handleMousemove}>
  • the mouse position is {m.x} x {m.y}
  • ></div>
  • ><style>
  • div {width:100%; height:100%;}
  • ></style>

또한 이벤트 핸들러를 인라인으로 선언할 수도 있습니다.

  • <script>
  • let m= {x=0, y=0};
  • </script>
  • <div on:mousemove={e => m ={ x: e.clintX, y:e.clientY}}">
  • the mouse position is {m.x} x {m.y}
  • ></div>
  • ><style>
  • div {width:100%; height:100%;}
  • ></style>

돔 이벤트 핸들러는 동작을 변경하는 수정자를 가질 수 있습니다. 예를 들어, once 수정자가 있는 핸들러는 한 번만 실행됩니다.

  • <script>
  • function handleClick( ) {
  • alert('no more alerts')
  • }
  • </script>
  • <button on:click|once={handleClick}>
  • Click me
  • ></button>

컴포넌트는 이벤트를 전달할 수도 있습니다. 그렇게 하려면 이벤트 디스패처(dispatcher)를 만들어야 합니다.

App.svelte
  • <script>
  • import Inner from './Inner.svelte';
  • function handleMessage(event) {
  • alert(event.detail.text);
  • }
  • </script>
  • <Inner on:message={handleMessage}/>
Inner.svelte
  • <script>
  • import { creatEventDispatcher } from 'svelte';
  • const dispatch = creatEventDispatcher( );
  • function sayhello( ) {
  • dispatch('message', {
  • text: 'Hello!'
  • });
  • }
  • </script>
  • <button on:click={sayHello}>
  • Click to say hello
  • ></button>

돔 이벤트와 달리 컴포넌트 이벤트는 버블링되지 않습니다. 깊게 중첩된 컴포넌트에서 이벤트를 수신하려면 중간 컴포넌트가 이벤트를 전달해야 합니다. 값이 없는 on:message 이벤트 지시문은 ‘모든 메시지 이벤트 전달’을 의미합니다.

App.svelte
  • <script>
  • import Outer from './Outer.svelte';
  • function handleMessage(event) {
  • alert(event.detail.text);
  • }
  • </script>
  • <Outer on:message={handleMessage}/>
Outer.svelte
  • <script>
  • import Inner from './Inner.svelte';
  • </script>
  • <Inner on:message/>
Inner.svelte
  • <script>
  • import { creatEventDispatcher } from 'svelte';
  • const dispatch = creatEventDispatcher( );
  • function sayhello( ) {
  • dispatch('message', {
  • text: 'Hello!'
  • });
  • }
  • </script>
  • <button on:click={sayHello}>
  • Click to say hello
  • ></button>

이벤트 전달은 돔 이벤트에서도 작동합니다.

App.svelte
  • <script>
  • import CustomButton from './CustomButton.svelte';
  • function handleClick( ) {
  • alert('Button Clicked');
  • }
  • </script>
  • <CustomButton on:click={handleClick}/>
CustomButton.svelte
  • <button on:click>
  • Click me
  • ></button>

bind:value 지시어를 사용할 수 있습니다. name 값을 변경하면 입력 값이 업데이트될 뿐만 아니라 입력 값이 변경되면 name도 업데이트됩니다.

  • <script>
  • let name = 'world';
  • </script>
  • <input bind:value={name}>
  • <h1>Hello{name}!</h1>

읽기 전용 this 바인딩은 모든 요소에 적용되며 렌더링된 요소에 대한 참조를 얻을 수 있습니다. 예를 들어 <canvas> 요소에 대한 참조를 얻을 수 있습니다.

  • <script>
  • import { onMount } from 'svelte';
  • let canvas;
  • onMount(( ) => {
  • const ctx = canvas.getContext('2d');
  • });
  • </script>
  • <canvas
  • bind:this={canvas}
  • width={32}
  • height={32}
  • />

모든 컴포넌트는 생성될 때 시작되고 소멸될 때 끝나는 수명 주기가 있습니다. 해당 수명 주기 동안 중요한 순간에 코드를 실행할 수 있는 몇 가지 기능이 있습니다. onMount는 컴포넌트가 돔에 처음 렌더링한 후 실행됩니다.

  • <script>
  • import { onMount } from 'svelte';
  • let photos = [ ];
  • onMount(async ( ) => {
  • const res = await fetch('http://jsonplaceholder.typicode.com/photos?_limit=20');
  • photos = await res.json( );
  • });
  • </script>
  • <h1>photo album</h1>
  • <div class="photos">
  • {#each photos as photo}
  • <figure> }
  • <img src={photo.thumbnailUrl} alt={photo.title}>
  • <figcaption>{photo.title}</figcaption>
  • </figure>
  • {:else}
  • <!--this block renders when photos.length === 0 -->>
  • <p>loading...</p>
  • {/each}
  • </div>

구성 요소가 소멸될 때 코드를 실행하려면 onDestroy를 사용합니다.

  • import { onDestory } from 'svelte';
  • export function inInterval(callback, milliseconds) {
  • const interval = setInterval(callback, milliseconds);
  • onDestory(( ) => {
  • clearInterval(interval);
  • });
  • }

beforeUpdate 함수는 돔이 업데이트된기 직전에 발생하도록 작업을 예약합니다. afterUpdate 함수는 돔이 데이터와 동기화되면 코드를 실행하는 데 사용됩니다.

  • import Eliza from 'elizabot';
  • import { beforeUpdate, afterUpdate } from 'svelte';
  • let div;
  • let autoscroll;
  • beforeUpdate(( ) => {
  • autoscrolll = div ∂∂
  • (div.offsetHeight + div.scrollTop) > (div.scrollHeight -20);
  • });
  • afterUpdate(( ) => {
  • if l(autoscroll) div.scrollTo(0, div.scrollHeight);
  • });

tick 함수는 언제든지 호출할 수 있다는 점에서 다른 수명 주기 함수와 다릅니다. pending 중인 상태 변경 사항이 돔에 적용되는 즉시 resolve되는 promise를 반환합니다.

  • <script>
  • import { tick } from 'svelte';
  • let text = Select some text and hit the tab key to toggle uppercase;
  • async function handleKeydown(event) {
  • if (event.key !== 'Tab') return;
  • event.preventDefault( );
  • const (selectionStart, selectionEnd, Value } = this;
  • const selection - value.selectionStart, selectionEnd);
  • const replacement = /[a-z]/.test(selection)
  • ? selection,toUpperCase( )
  • ? selection,toLowerCase( );
  • text = (
  • value.slice(0, selectionStart) +
  • replacement +
  • value.slice(selectionEnd)
  • );
  • await tick( );
  • this.selectionStart =selectionStart;
  • this.selectionEnd =selectionEnd;
  • }
  • </script>

마치며

스벨트는 3대 프론트엔드 프레임워크와 다른 방향성을 가지고 만들어진 프레임워크로 빠른 속도와 간결한 코드를 자랑하고 러닝 커브가 높지 않은 편입니다. 아직은 한창 성장하고 있는 기술이나 뛰어난 장점으로 인해 전세계적으로 큰 주목을 받고 있는 만큼 조만간 3대 프레임워크에 지각 변동을 일으키며 등극할 가능성도 충분하다고 생각합니다. 새로운 트렌드와 신기술에 관심이 많은 프론트엔드 개발자라면 스벨트를 익혀보고 신규 프로젝트에 적용해 보는 것을 권하는 바입니다.

square,1password,IBM,schneider,LesEchos,philips,The New York Times,Razorpay,Godaddy,Alaska airlines,Rakuten,FusionCharts,avast,Transloadit,Chess.com [그림 6] 스벨트를 사용하는 기업들

References
[1] https://svelte.dev
[2] https://2020.stateofjs.com/en-US/technologies/front-end-frameworks
[3] https://insights.stackoverflow.com/survey/2021#section-most-loved-dreaded-and-wanted-web-frameworks



▶ 해당 콘텐츠는 저작권법에 의하여 보호받는 저작물로 기고자에게 저작권이 있습니다.
▶ 해당 콘텐츠는 사전 동의 없이 2차 가공 및 영리적인 이용을 금하고 있습니다.


공유하기 열기
이윤석
이윤석

에스코어㈜ 소프트웨어사업부 개발플랫폼그룹

에스코어에서 프론트엔드 개발을 하고 있습니다.