Svelte 5 Runes, Snippets, Event Handler

keemsebeen·2024년 9월 2일

작년 9월에 언급됐던 Svelte 5가 점차 구색을 갖춰나가고 있다.
Svelte Docs에서는 관련된 내용이 업데이트 되고 있으며, preview 페이지를 만들정도로 활발하게 개발이 진행되고 있음을 알 수 있다.

따라서 이번 포스트에서는 Svelte 5에서 새롭게 추가된, 혹은 바뀐 기능들에 대해 이야기하고자 한다.

Runes

코드를 더욱 직관적이고 표현력이 풍부하며 유지 관리하기 쉽게 만드는 것을 목표로 Svelte에서 반응성을 처리하는 방법이다. 이전 버전의 Svelte에서 사용된 $ 구문과 반응 문을 대체하는 반응 기본 요소입니다.

지향점

  • Svelte 구성 요소에서 반응성이 관리되는 방식을 단순화하고 향상시키는 것
  • 반응 상태 관리에 대한 보다 통합되고 선언적이며 예측 가능한 접근 방식을 제공하는 것

$state

이전 버전에서 사용하던 스토어(writable store)와 유사하게 작동하며, 반응형 변수를 컴포넌트 내에서 쉽게 선언할 수 있다. 값이 변경될 때마다 자동으로 UI를 업데이트한다.

이전에는 변수를 선언하고 초기값을 대입했는데, $state 를 사용하여 반응형 변수를 쉽게 선언이 가능하다.

<script>
	let count = 0;

	function incrementCount() {
    count++;
	}
</script>

<p>Counter: {count}</p>
<button on:click={incrementCount}>+1</button>
<script>
	let count = $state(0);

	function incrementCount() {
	   count++;
	}
</script>

<p>Counter: {count}</p>
<button onclick={incrementCount}>+1</button>

$derived

하나 이상의 의존성이 변경될 때마다 자동으로 재계산되는 파생 값을 생성하는 데 사용된다. 의존하는 상태가 변경될 때마다 자동으로 재계산된다.

<script>
	let count = 10;
	$: doubleCount = count * 2;
</script>

<div>{doubleCount}</div>
<script>
	let count = $state(10);
	const doubleCount = $derived(count * 2);
</script>

<div>{doubleCount}</div>

$effect

하나 이상의 반응형 값이 변경될 때마다 side effects을 실행하는 데 사용된다. $: { … } 반응문을 대체한다.

<script>
	import { onMount } from "svelte";

	let inputElement;

	onMount(() => {
	   inputElement.focus();
	});
</script>

<input bind:this={inputElement} />
<script>
	let inputElement;

	$effect(() => {
	  inputElement.focus();
	});
</script>
 
<input bind:this={inputElement} />

$props

부모 컴포넌트로부터 전달받은 속성을 쉽게 접근할 수 있습니다. export let 을 대체한다.

export let width;
export let height;

$: area = width * height;

$: console.log(area);
let { width, height } = $props(); 

const area = $derived(width * height);

$effect(() => {
	console.log(area);
});

Snippets

snippet 문법은 재사용 가능한 코드 블록을 정의하고, @render 문법은 이를 렌더링하는 기능을 제공합니다.

이 두 문법을 통해 특정 템플릿을 여러 번 재사용할 수 있어 코드의 중복을 줄이고, 더 모듈화된 컴포넌트 작성이 가능합니다.

{#each images as image}
	{#if image.href}
		<a href={image.href}>
			<figure>
				<img
					src={image.src}
					alt={image.caption}
					width={image.width}
					height={image.height}
				/>
				<figcaption>{image.caption}</figcaption>
			</figure>
		</a>
	{:else}
		<figure>
			<img
				src={image.src}
				alt={image.caption}
				width={image.width}
				height={image.height}
			/>
			<figcaption>{image.caption}</figcaption>
		</figure>
	{/if}
{/each}

snippet / render

{#snippet figure(image)}
	<figure>
		<img
			src={image.src}
			alt={image.caption}
			width={image.width}
			height={image.height}
		/>
		<figcaption>{image.caption}</figcaption>
	</figure>
{/snippet}
{#each images as image}
	{#if image.href}
		<a href={image.href}>
			{@render figure(image)}
		</a>
	{:else}
		{@render figure(image)}
	{/if}
{/each}

Event Handler

on:

on:clickonclick 으로 대체된다.

<button on:click={() => count++}>
<button onclick={() => count++}>

다음과 같이 사용이 가능하다.

<script>
	let count = $state(0);

	function onclick() {
		count++;
	}
</script>

<button {onclick}>
	clicks: {count}
</button>

Component events

createEventDispatcher

Svelte 5에서는 createEventDispatcher가 제거되고 대신 콜백 속성을 사용하여 이벤트를 처리하는 방식으로 전환된다. 컴포넌트는 특정 이벤트를 부모에게 디스패치하는 대신 부모로부터 전달된 콜백 함수를 호출하여 상호작용합니다.

  • 예시1 - 단방향 부모 컴포넌트 (단방향)
    <script>
    	import Pump from './Pump.svelte';
    
    	let size = $state(15);
    	let burst = $state(false);
    
    	function reset() {
    		size = 15;
    		burst = false;
    	}
    </script>
    
    <Pump
    	inflate={(power) => {
    		size += power;
    		if (size > 75) burst = true;
    	}}
    	deflate={(power) => {
    		if (size > 0) size -= power;
    	}}
    />
    
    {#if burst}
    	<button onclick={reset}>new balloon</button>
    	<span class="boom">💥</span>
    {:else}
    	<span class="balloon" style="scale: {0.01 * size}">
    		🎈
    	</span>
    {/if}
    자식 컴포넌트
    <script>
    	let { inflate, deflate } = $props();
    	let power = $state(5);
    </script>
    
    <button onclick={() => inflate(power)}>inflate</button>
    <button onclick={() => deflate(power)}>deflate</button>
    <button onclick={() => power--}>-</button>
    Pump power: {power}
    <button onclick={() => power++}>+</button>
  • 예시2 - 양방향 부모 컴포넌트(양방향)
    <script>
    	import Counter from './Counter.svelte'; // 자식 컴포넌트 임포트
    
    	let count = $state(0); 
    
    	function handleUpdate(newCount) {
    		count = newCount; 
    	}
    </script>
    
    <h1>양방향 전송 예제</h1>
    <p>부모 카운트: {count}</p>
    
    <!-- 자식 컴포넌트에 count와 handleUpdate 콜백 전달 -->
    <Counter {count} onUpdate={handleUpdate} />
    자식 컴포넌트
    <script>
    	let { count, onUpdate } = $props(); // 부모로부터 props 받기
    
    	function increment() {
    		const newCount = count + 1;
    		onUpdate(newCount); // 부모의 콜백 함수 호출하여 카운트 업데이트
    	}
    
    	function decrement() {
    		const newCount = count - 1;
    		onUpdate(newCount); // 부모의 콜백 함수 호출하여 카운트 업데이트
    	}
    </script>
    
    <p>자식 카운트: {count}</p>
    <button onclick={increment}>증가</button>
    <button onclick={decrement}>감소</button>
    

Event modifiers

HTML과 JavaScript 간의 논리가 분할되어 있어 문제가 생기게 된다.

로직의 일부(예: preventDefault 또는 once)는 템플릿에 정의되어 있고, 나머지 로직은 핸들러 함수 자체 내부에 있는 문제

<button on:click|once|preventDefault={handler}>...</button>

이벤트 핸들러의 동작이 두 위치로 분할되기 때문에 코드 유지 관리가 어려워질 수 있다는 점을 고려해 핸들러 함수 내에서 필요한 로직을 직접 구현하는 것을 권장한다고 합니다.

<script>
	function once(fn) {
		return function (event) {
			if (fn) fn.call(this, event);
			fn = null;
		};
	}

	function preventDefault(fn) {
		return function (event) {
			event.preventDefault();
			fn.call(this, event);
		};
	}
</script>

<button onclick={once(preventDefault(handler))}>...</button>

바뀐 Svelte 5 문법을 보면서 React와 비슷해지고 있는 것 같다는 생각을 하게되었다. Svelte만의 특유의 날씬한 코드 과정이 없어지는 것 같아 아쉽다는 생각을 하게됐던 것 같다.

관련 내용을 찾아보던 중 Reddit의 한 글을 봤을데 Svelte가 React화 되어가는 것이 아쉽다는 글이였다. 해당 댓글에는 실제 Svelte 개발자가 댓글을 달았는데 해당 댓글이 너무 인상적이였다.

One other important thing to point out is that developers typically spend around 90% of their time reading code, and only 10% of their time writing code. We've already had plenty of feedback that teams that have migrated their Svelte 4 apps to Svelte 5 have seen noticeable improvements in reading and understanding their team's code. With the new compiler warnings in Svelte 5, and the improved type-safety around props and templates, combined with all the other improvements, should help make better apps that scale far better with your requirements as new features get added.

요약을 하자면 개발자는 90%는 코드를 읽는데 사용하고, 10%만 코드를 작성하는데 Svelte 5로 바꾸고 난 뒤로 팀의 코드를 읽고 이해하는 데 눈에 띄는 개선을 보였다는 이야기이다.

너무 공감이 되는 이야기였고, 나는 읽기 쉬운 코드를 짜고 있는가? 하는 생각이 들었다. (앞으로 가야할 길이 멀다..🚀 )

Svelte 5가 의미 없는 변화가 아니며, 새롭게 변화된 스타일의 매력을 느껴보고 싶다는 생각이 들었다!!


참고

https://component-party.dev/compare/svelte4-vs-svelte5
https://svelte-5-preview.vercel.app/docs/introduction

profile
프론트엔드 공부 중인 김세빈입니다. 👩🏻‍💻

0개의 댓글