안녕하세요! 오늘은 스벨트에 대해 배워보려고 합니다. 스벨트는 리액트나 뷰와 같은 프론트엔드 프레임워크입니다. ‘스벨트’라는 단어는 날씬하고 우아하다는 뜻을 가지고 있으며, 이 프레임워크는 바로 그것을 목표로 하고 있습니다.

이 튜토리얼을 통해 기본적인 스벨트 앱을 만들어보면서 스벨트의 핵심 개념인 컴포넌트, 프롭스, 상태 관리, 조건부 렌더링, 스타일링 등에 대한 좋은 이해를 얻게 될 것입니다.

출발해볼까요? 

설정하기

이 튜토리얼을 진행하면서 보게 될 것처럼, 스벨트 작업은 일반적으로 다른 프레임워크보다 단계가 적고 코드가 더 적습니다.

시스템에 최신 버전의 Node.js가 설치되어 있어야 합니다. 아직 설치하지 않았다면 Node.js 공식 웹사이트에서 다운받을 수 있습니다.

스벨트 프로젝트를 만들기 위해서는 터미널 또는 명령 프롬프트를 열고 다음 명령어를 실행하세요:

npm create vite@latest svelte-intro -- --template svelte

이 명령어는 svelte-intro라는 새 폴더를 생성하고 그 안에 바닐라 스벨트 프로젝트(타입스크립트 없음)를 초기화합니다.

이제 새로 만든 svelte-intro 폴더로 이동합니다:

cd svelte-intro

그 다음 필요한 의존성을 설치하십시오:

npm install

설치가 완료되면 다음을 실행할 수 있습니다:

npm run dev

이제 첫 스벨트 앱 구축을 시작할 준비가 되었습니다.

참고: 스벨트 대비 [다른 프레임워크 삽입]

스벨트 튜토리얼을 읽고 있다면 이미 프레임워크에 대해 궁금해하고 있을 것이고, 다른 것들과 어떻게 비교되는지 궁금할 것입니다. 우리는 이미 스벨트 대비 리액트에 대한 상세한 비교를 가지고 있지만, 나머지는 어떨까요?

스벨트가 다른 자바스크립트 프레임워크와 차별화되는 주요 기능은 컴파일러 기반으로 되어 있다는 것입니다.

이게 무슨 의미일까요? 음, 앵귤러, 뷰, Next.js는 가상 DOM 등을 관리하기 위해 번들에 런타임을 추가합니다. 스벨트는 그렇지 않습니다. 스벨트 컴파일러는 빌드 타임에 작업을 수행하여 스벨트 코드를 일반 DOM 조작으로 바꿉니다. 그게 바로 파일 크기가 작은 이유입니다!

첫 번째 스벨트 컴포넌트

모든 “현대적인” 프론트엔드 프레임워크는 컴포넌트 개념을 중심으로 만들어졌으며 스벨트도 마찬가지입니다.

다른 프레임워크와 마찬가지로 스벨트 컴포넌트는 재사용 가능하고 독립적인 UI 조각(대부분의 경우)입니다. HTML, CSS, 자바스크립트는 .svelte 확장자가 있는 단일 파일 안에 있습니다.

자신의 상태를 관리할 수 있으며, 프로퍼티(“프롭스”)를 통해 부모 컴포넌트로부터 데이터를 받을 수 있습니다.

스벨트 컴포넌트 구조

카운터 컴포넌트라는 클래식한 예시를 사용하여 첫 번째 스벨트 컴포넌트를 만들어봅시다. Counter.svelte라고 명명됩니다.

스벨트 카운터는 다음과 같습니다:

<script>
  let count = 0;
  const increment = () => {
    count += 1;
  };
</script>

<button class="button" on:click={increment}>
  Count is {count}
</button>

<style>
  .button{
    border-radius: 100%;
  }
</style>

스벨트 컴포넌트가 어떻게 작동하는지 살펴보겠습니다:

JavaScript는 script 태그 내에 정의됩니다. 우리의 예제에서 상태는 ‘count’ 변수로 나타내며, 이 상태를 업데이트하는 증가 함수를 포함하는 로직을 가지고 있습니다.

Svelte의 아름다움은 그 script 태그 내의 대부분 코드가 단지 JavaScript일 뿐이라는 것입니다. JSX나 다른 프레임워크 특유의 언어를 다룰 필요가 없습니다. (반응성을 다룰 때는 특정 문법이 있지만, 대부분은 일반 JavaScript를 복사- 붙여넣기 할 수 있고, 그것이 정말 좋습니다.)

HTML은 두 번째 섹션에서 정의됩니다. 여기에서 버튼은 on:click 속성과 함께 설정되어 있으며, 이 속성은 script 태그 내에 increment 함수를 호출합니다. 중괄호 {}를 사용하면 count 변수에 바인딩을 할 수 있으며, 이는 변수의 값이 거기에 반영되어 자동으로 업데이트됨을 의미합니다.

CSS는 마지막에, style 태그 내에 옵니다. Svelte에서 스타일은 개별 컴포넌트에 로컬로 범위가 지정되며, 이는 스타일이 앱의 다른 부분에 영향을 주지 않음을 의미합니다.

컴포넌트 사용하기

다른 자바스크립드 프레임워크와 마찬가지로 다음과 같이 쉽게 임포트하여 사용할 수 있습니다.

<script>
  // in ./App.svelte
  import Counter from './lib/Counter.svelte';
</script>

<main>
  <h1>Hello Svelte!</h1>
  <Counter />
</main>



이제 Counter 컴포넌트는 App 컴포넌트의 일부가 되었고, 앱이 자동으로 리로드될 때마다 카운터가 증가하는 버튼을 볼 수 있습니다.

다 됐습니다, 첫 번째 스벨트 컴포넌트를 만들었습니다. 다음으로 프롭스가 어떻게 작동하는지 알아볼 것입니다.

스벨트에서의 Props

프롭스는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 수 있게 해줍니다. 스벨트에서 프롭스는 HTML 속성처럼 작동합니다.

제품 정보를 표시하는 데 사용할 새로운 스벨트 컴포넌트, Card를 만들어봅시다.

먼저 src/lib 디렉토리에 Card.svelte라는 이름의 새 파일을 생성합니다.

Card.svelte에 다음을 추가하세요:

<script>
  export let title = "";
  export let description = "";
  export let price = 0;
</script>

<div class="card">
  <h2>{title}</h2>
  <p>{description}</p>
  <p>${price}</p>
</div>

스벨트에서는 내부 변수를 프롭스로 사용할 수 있게 하기 위해 export 키워드를 추가해야 합니다.

여기서는 제목, 설명, 가격을 프롭스로 선언했습니다.

프롭스 전달하기

Card 컴포넌트에 프롭스를 전달하려면 다음과 같이 하면 됩니다:

먼저 App.svelte 파일 상단에 Card 컴포넌트를 가져옵니다.

<script>
  // in App.svelte
  import Card from './lib/Card.svelte';
</script>

<main>
  <h1>Hello Svelte!</h1>
  <Card 
    title="Product 1" 
    description="This is a nice product." 
    price={19.99}
  />
</main>


다 됐습니다! 이제 스벨트에서 프롭스를 사용하는 방법을 알게 되었습니다.

배열 순환/루프

스벨트는 {#each}{/each} 블록을 사용하여 아이템 배열을 순회합니다. 이 구문은 핸들바의 구문과 정확히 같지만 중괄호가 하나 덜 있습니다.

예시로 돌아가서, 여러 개의 제품을 표시하고 싶다면 모든 제품에 대한 배열을 생성하고 HTML 섹션에서 각 제품에 대한 Card 컴포넌트를 렌더링할 수 있습니다. 여기 어떻게 하는지 보여드릴게요.

app.svelte 컴포넌트에 제품 배열을 선언한 다음, html 섹션에서 {#each} 블록을 사용하여 각 제품에 대한 카드 컴포넌트를 렌더링합니다.

<script>
  import Card from './lib/Card.svelte';

  const products = [
    { title: "Product 1", description: "This is product 1.", price: 10 },
    { title: "Product 2", description: "This is product 2.", price: 20 },
    { title: "Product 3", description: "This is product 3.", price: 30 },
  ];
</script>
<main>
  <h1>Our Products</h1>
  {#each products as product}
    <Card 
      title={product.title} 
      description={product.description} 
      price={product.price} 
    />
  {/each}
</main>

여는 태그 {#each}와 닫는 태그 {/each}의 차이점을 주의해야 합니다. 그리고 제품의 타이틀, 설명, 가격을 프롭스로 넘겨주는 것을 잊지 말아야 합니다.

다음 섹션에서는 스벨트 컴포넌트에 스타일을 더하는 방법을 살펴보겠습니다.

전역 스타일 및 open-props 추가하기

저는 전형적인 개발자인데 디자인에 소질이 없거나 매우 느립니다. 프로젝트를 멋지게 보이게 하는 빠른 방법 중 하나는 open-props를 사용하는 것입니다. 이것은 애덤 아질레(Adam Argyle)가 만든 프로젝트로서, 여러분의 시스템의 밝기/어두운 모드를 지원하는 준비된 CSS 변수들을 제공합니다.

open-props를 사용하려면 CSS 파일을 가져와야 하며 스타일을 모든 다른 스벨트 컴포넌트에서 사용할 수 있도록 해야 합니다.

기본적으로 스타일은 컴포넌트에 범위가 지정되어 있으므로, 스타일을 전역으로 만들려면 style 태그에 global 속성을 추가해야 합니다.

<style global>
  @import "https://unpkg.com/open-props";
  @import "https://unpkg.com/open-props/normalize.min.css";
</style>

이제 open-props가 가져온 것으로 사용할 수 있습니다. 이 변형들을 사용하여 우리의 Card 컴포넌트에 스타일을 더해봅시다.

<style>
  .card {
      border: 1px solid tomato;
      width: 500px;
  }
  h2 {
      font-size: 30px;
      font-weight: var(--font-weight-8);
  }
  p {
      font-weight: var(--font-weight-4);
  }
</style>

이 예시에서 우리는 h2를 더욱 돋보이게 하기 위해 –font-weight-8과 –font-weight-4 변수를 사용합니다.

다음으로, 스벨트에서 상태 관리를 살펴봅시다. 다른 컴포넌트들 사이에서 공유 상태를 관리하기 위해 “store”의 개념을 어떻게 사용하는지 배울 것입니다.

상태 관리

상태 관리는 복잡한 주제입니다. 리액트의 세계에서는 Redux, Immer, React Query 등과 함께 전체 병렬 생태계가 존재합니다. 이제 이 특정 라이브러리에 대해 전체 책들이 쓰여졌으니 이것은 스벨트가 그것을 어떻게 하는지에 대한 간단한 개요입니다.

스벨트에는 “스토어”라는 개념을 통해 상태를 관리하는 내장 방법이 있다. 스벨트 스토어는 임의의 값들을 관찰하고, 이러한 값들의 변화를 컴포넌트에서 구독함으로써 상태를 관리하는 데 도움을 줍니다.

스벨트 스토어에는 두 가지 종류가 있습니다:

1. 쓰기 가능 스토어

쓰기 가능 스토어는 쓸 수 있는 파일과 같으며, 읽고 쓸 수 있습니다.

// create
// Creating a writable store with an initial value of 0
import { writable } from 'svelte/store';
export const count = writable(0);


컴포넌트에서는 스토어를 구독하고 그 값을 업데이트할 수 있습니다:

<script>
  import { count } from './store';
  let countValue = 0;
  count.subscribe(value => countValue = value);

  function increment() {
    count.update((n) => n + 1);
  }
</script>

<div>
  count: {countValue} <button on:click={increment}> + </button>
</div>

2. 읽기 가능 스토어

읽기 가능 스토어는 오직 읽을 수 있습니다. 읽기 가능한 스토어는 당신이 데이터를 읽는 것만을 허용합니다. 간단하죠.

// Creating a readable store with an initial value
import { readable } from 'svelte/store';
export const appName = readable('My Svelte App');


컴포넌트 내에서 스토어 구독을 통해 그 값을 읽을 수 있습니다:

<script>
  import { appName } from './store';
  let name;
  appName.subscribe(value => name = value);
</script>

<h1>Welcome to {name}!</h1>

부가가치세 관리를 위한 스토어 사용하기
우리 프로젝트에서는 제품의 가격에 부가가치세가 적용되어야 하는지를 관리하기 위해 스토어를 사용할 수 있습니다.

새로운 파일인 ./store.js를 만들고 svelte/store 모듈에서 writable 함수를 사용하여 쓰기 가능한 스토어를 생성하세요:

import { writable } from 'svelte/store';

export const hasVat = writable(true);


이 코드는 hasVat라는 이름의 쓰기 가능한 스토어를 생성하면서 true 값으로 초기화합니다.

Card 컴포넌트에서 hasVat 스토어를 구독하는 방법은 다음과 같습니다:

<script>
  import { hasVat } from './store';
  export let title, description, price = 0;
  let hasVatValue;
  hasVat.subscribe(value => {
    hasVatValue = value;
  });
</script>

<div class="card">
  <h2>{title}</h2>
  <p>{description}</p>
  <span>$ {hasVatValue ? price * 1.2 : price}</span>
</div>

우리는 hasVat 스토어를 가져와 구독합니다. 구독 콜백 내에서 스토어 값이 변경될 때마다 hasVatValue 변수를 업데이트합니다. HTML 내에서 이 값을 사용해 가격에 VAT를 조건부로 적용합니다.

상태를 여기저기 흩뿌리는 것보다, 저희는 모든 스토어를 단 한 파일에 깔끔하게 정리해 놓는 것이 좋습니다.