스벨트 공식 사이트에서는 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 태그를 구분할 수 있도록 하기 위해서입니다.
- <script>
- import Nested from './Nested.svelte';
- </script>
This is paragraph.
- ><Nested/>
일반적인 문자열은 일반 텍스트로 삽입합니다. 때로는 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 키워드로 이를 수행합니다.
- <script>
- import Nested from './Nested.svelte';
- </script>
- <Nested answer={42}/>
- <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)를 만들어야 합니다.
- <script>
- import Inner from './Inner.svelte';
- function handleMessage(event) {
- alert(event.detail.text);
- }
- </script>
- <Inner on:message={handleMessage}/>
- <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 이벤트 지시문은 ‘모든 메시지 이벤트 전달’을 의미합니다.
- <script>
- import Outer from './Outer.svelte';
- function handleMessage(event) {
- alert(event.detail.text);
- }
- </script>
- <Outer on:message={handleMessage}/>
- <script>
- import Inner from './Inner.svelte';
- </script>
- <Inner on:message/>
- <script>
- import { creatEventDispatcher } from 'svelte';
- const dispatch = creatEventDispatcher( );
- function sayhello( ) {
- dispatch('message', {
- text: 'Hello!'
- });
- }
- </script>
- <button on:click={sayHello}>
- Click to say hello
- ></button>
이벤트 전달은 돔 이벤트에서도 작동합니다.
- <script>
- import CustomButton from './CustomButton.svelte';
- function handleClick( ) {
- alert('Button Clicked');
- }
- </script>
- <CustomButton on:click={handleClick}/>
- <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대 프레임워크에 지각 변동을 일으키며 등극할 가능성도 충분하다고 생각합니다. 새로운 트렌드와 신기술에 관심이 많은 프론트엔드 개발자라면 스벨트를 익혀보고 신규 프로젝트에 적용해 보는 것을 권하는 바입니다.
[그림 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차 가공 및 영리적인 이용을 금하고 있습니다.