Svelte 컴포넌트의 기본 구조
컴포넌트는 화면을 나누는 가장 작은 단위다
Svelte에서 개발할 때 가장 자주 만지는 파일은 .svelte 파일입니다. 이 파일 하나가 하나의 컴포넌트가 됩니다. 컴포넌트는 화면의 한 조각입니다. 버튼 하나일 수도 있고, 상품 카드 하나일 수도 있고, 전체 페이지일 수도 있습니다.
예를 들어 쇼핑몰 화면을 생각해 봅시다.
상단 메뉴
검색창
상품 카드
장바구니 버튼
하단 정보 영역
이런 조각을 각각 컴포넌트로 만들 수 있습니다. 그리고 작은 컴포넌트를 조립해 더 큰 화면을 만듭니다.
Svelte 컴포넌트의 기본 모양은 다음과 같습니다.
<script>
let name = 'Svelte';
</script>
<p>안녕하세요, {name}!</p>
<style>
p {
color: royalblue;
font-weight: bold;
}
</style>이 짧은 코드 안에 세 가지가 들어 있습니다.
<script>: JavaScript 로직을 작성하는 곳마크업: 실제 화면 구조를 작성하는 곳
<style>: 이 컴포넌트에 적용할 CSS를 작성하는 곳
HTML, CSS, JavaScript를 따로 흩어 놓는 대신, 하나의 화면 조각에 필요한 코드를 한 파일에 모아 둡니다. 그래서 컴포넌트를 열면 "이 화면이 어떤 데이터를 쓰고, 어떻게 보이고, 어떤 스타일을 가지는지" 한눈에 확인할 수 있습니다.
AI 생성 이미지
<script>는 컴포넌트의 생각을 담는다
<script> 영역에는 컴포넌트에서 사용할 JavaScript 코드를 작성합니다. 변수, 함수, 상태 값을 이곳에 둡니다.
<script>
let productName = '무선 키보드';
let price = 39000;
function formatPrice(value) {
return value.toLocaleString('ko-KR') + '원';
}
</script>
<h2>{productName}</h2>
<p>{formatPrice(price)}</p>여기서 productName, price, formatPrice는 모두 JavaScript입니다. Svelte는 이 값을 마크업 영역에서 바로 사용할 수 있게 해 줍니다.
중요한 점은 <script>가 화면에 직접 보이지 않는다는 것입니다. <script>는 화면을 만들기 위해 필요한 값과 동작을 준비합니다. 실제로 브라우저에 보이는 부분은 그 아래의 마크업입니다.
Svelte 5에서는 값이 바뀔 때 화면까지 함께 바뀌어야 한다면 $state를 사용합니다.
<script>
let count = $state(0);
</script>
<p>현재 값: {count}</p>count는 단순한 숫자처럼 보이지만, Svelte는 이 값이 화면과 연결되어 있음을 알고 있습니다. 나중에 count가 바뀌면 {count}가 있는 화면도 다시 갱신됩니다. 이 부분은 다음 장에서 더 자세히 다루고, 지금은 "화면과 연결되는 값은 $state로 만든다" 정도로 이해하면 충분합니다.
마크업은 화면의 뼈대를 만든다
<script> 아래에는 HTML과 거의 같은 문법으로 화면 구조를 작성합니다.
<script>
let title = '오늘의 할 일';
let todo = 'Svelte 컴포넌트 구조 익히기';
</script>
<section>
<h2>{title}</h2>
<p>{todo}</p>
</section>이 영역을 보통 마크업 영역이라고 부릅니다. 일반 HTML처럼 section, h2, p, button 같은 태그를 사용합니다.
Svelte에서 특별한 점은 JavaScript 값을 중괄호 {} 안에 넣어 화면에 표시할 수 있다는 것입니다.
<p>{todo}</p>이 코드는 todo라는 글자를 그대로 보여 주는 것이 아닙니다. <script>에 있는 todo 변수의 값을 가져와 그 자리에 넣습니다. 그래서 화면에는 다음처럼 보입니다.
Svelte 컴포넌트 구조 익히기중괄호 안에는 간단한 JavaScript 표현식도 사용할 수 있습니다.
<script>
let price = 12000;
let quantity = 3;
</script>
<p>총액: {price * quantity}원</p>화면에는 총액: 36000원이 표시됩니다.
다만 너무 복잡한 계산을 마크업 안에 길게 쓰는 것은 좋지 않습니다. 화면 구조를 읽기 어려워지기 때문입니다. 복잡한 계산은 <script>에서 변수나 함수로 정리하고, 마크업에서는 그 결과를 보여 주는 식으로 작성하는 편이 좋습니다.
<script>
let price = 12000;
let quantity = 3;
function totalPrice() {
return price * quantity;
}
</script>
<p>총액: {totalPrice()}원</p>작은 예제에서는 차이가 크지 않지만, 컴포넌트가 커질수록 이런 정리가 코드를 읽기 쉽게 만듭니다.
<style>은 컴포넌트의 옷을 입힌다
<style> 영역에는 CSS를 작성합니다.
<script>
let message = 'Svelte는 스타일도 컴포넌트 안에 둡니다.';
</script>
<div class='notice'>
{message}
</div>
<style>
.notice {
padding: 16px;
border-radius: 8px;
background: #eef4ff;
color: #1f3a8a;
}
</style>여기서 .notice 스타일은 이 컴포넌트 안의 .notice에 적용됩니다. Svelte의 중요한 특징은 컴포넌트 안에 작성한 CSS가 기본적으로 그 컴포넌트에만 적용된다는 점입니다.
예를 들어 ProductCard.svelte 안에 다음 스타일이 있다고 해 봅시다.
<style>
h2 {
color: tomato;
}
</style>이 스타일은 기본적으로 ProductCard.svelte 안의 h2에만 적용됩니다. 다른 컴포넌트에 있는 h2까지 전부 빨갛게 만들지 않습니다.
이를 scoped style이라고 부릅니다. scoped는 "범위가 정해져 있다"는 뜻입니다. Svelte는 컴파일 과정에서 스타일이 특정 컴포넌트 안에서만 동작하도록 처리합니다. 덕분에 클래스 이름이 우연히 같아도 다른 컴포넌트의 스타일을 망가뜨릴 가능성이 줄어듭니다.
이 방식은 작은 프로젝트보다 컴포넌트가 많아지는 프로젝트에서 더 큰 장점이 됩니다. 버튼, 카드, 목록, 모달 같은 컴포넌트가 늘어나도 각 컴포넌트의 스타일을 비교적 안전하게 관리할 수 있습니다.
전역 CSS가 필요한 순간도 있다
그렇다고 모든 CSS를 컴포넌트 안에만 써야 하는 것은 아닙니다. 어떤 스타일은 앱 전체에 적용되는 편이 자연스럽습니다.
예를 들면 다음과 같은 스타일입니다.
body의 기본 여백 제거전체 글꼴 설정
링크의 기본 색상
공통 CSS 변수
접근성을 위한 공통 유틸리티 클래스
이런 스타일은 특정 컴포넌트보다 앱 전체의 기본 규칙에 가깝습니다. SvelteKit 프로젝트에서는 보통 src/app.css 같은 파일에 전역 CSS를 작성하고, 루트 레이아웃에서 불러옵니다.
/* src/app.css */
body {
margin: 0;
font-family: system-ui, sans-serif;
background: #f8fafc;
color: #0f172a;
}
button {
font: inherit;
}전역 CSS는 강력하지만 조심해서 써야 합니다. 모든 화면에 영향을 주기 때문입니다. button, h1, .card 같은 선택자를 전역으로 넓게 잡으면 나중에 예상하지 못한 곳의 디자인이 바뀔 수 있습니다.
기준은 간단합니다.
앱 전체의 기본값이면 전역 CSS
특정 컴포넌트의 모양이면 컴포넌트 안의
<style>
가끔 컴포넌트 안에서 전역 선택자가 필요할 때도 있습니다. 이때는 :global(...)을 사용할 수 있습니다.
<style>
:global(body) {
margin: 0;
}
</style>하지만 :global은 꼭 필요한 경우에만 사용하는 것이 좋습니다. 컴포넌트 스타일의 장점은 범위가 좁다는 데 있습니다. :global을 많이 쓰면 그 장점이 약해집니다.
카운터 컴포넌트 만들기
이제 세 영역을 한 번에 연결해 보겠습니다. 버튼을 누르면 숫자가 증가하는 간단한 카운터 컴포넌트입니다.
<script>
let count = $state(0);
function increase() {
count += 1;
}
</script>
<div class='counter'>
<p class='label'>현재 숫자</p>
<strong class='value'>{count}</strong>
<button onclick={increase}>1 증가</button>
</div>
<style>
.counter {
width: 240px;
padding: 20px;
border: 1px solid #dbeafe;
border-radius: 16px;
background: white;
text-align: center;
}
.label {
margin: 0 0 8px;
color: #64748b;
font-size: 14px;
}
.value {
display: block;
margin-bottom: 16px;
color: #2563eb;
font-size: 40px;
}
button {
border: 0;
border-radius: 999px;
padding: 10px 16px;
background: #2563eb;
color: white;
cursor: pointer;
}
button:hover {
background: #1d4ed8;
}
</style>이 컴포넌트에서 각 영역은 역할이 분명합니다.
<script>에서는 상태와 동작을 준비합니다.
let count = $state(0);
function increase() {
count += 1;
}count는 화면에 표시할 숫자입니다. increase 함수는 count를 1 증가시킵니다.
마크업에서는 이 값을 보여 주고, 버튼과 함수를 연결합니다.
<strong class='value'>{count}</strong>
<button onclick={increase}>1 증가</button>{count}는 현재 숫자를 화면에 표시합니다. onclick={increase}는 버튼을 클릭했을 때 increase 함수를 실행하라는 뜻입니다.
<style>에서는 카운터의 모양을 정합니다. .counter, .label, .value, button 스타일은 이 컴포넌트 안에서만 적용됩니다. 다른 컴포넌트의 버튼까지 모두 파란색으로 바꾸지 않습니다.
이 작은 예제 안에 Svelte 컴포넌트의 핵심이 들어 있습니다.
상태는
<script>에서 만든다.화면은 마크업에서 구성한다.
JavaScript 값은
{}로 표시한다.사용자의 동작은 이벤트 속성으로 함수와 연결한다.
스타일은
<style>에 작성하고, 기본적으로 컴포넌트 안에만 적용된다.
컴포넌트를 읽는 순서
처음에는 .svelte 파일이 낯설 수 있습니다. 한 파일 안에 JavaScript, HTML, CSS가 모두 있기 때문입니다. 하지만 읽는 순서를 정해 두면 어렵지 않습니다.
먼저 마크업을 봅니다. 화면에 어떤 요소가 있는지 확인합니다.
그다음 <script>를 봅니다. 화면에서 사용하는 값과 함수가 어디서 오는지 확인합니다.
마지막으로 <style>을 봅니다. 화면 요소가 어떤 모양으로 꾸며지는지 확인합니다.
작성할 때는 반대로 해도 됩니다. 필요한 상태를 먼저 만들고, 화면을 붙이고, 마지막에 스타일을 다듬을 수 있습니다. 중요한 것은 세 영역이 서로 떨어진 것이 아니라 하나의 컴포넌트를 만들기 위해 협력한다는 점입니다.
좋은 컴포넌트는 작고 분명하다
처음부터 컴포넌트를 너무 크게 만들 필요는 없습니다. 오히려 작게 시작하는 편이 좋습니다.
예를 들어 상품 목록을 만든다면 처음부터 전체 쇼핑몰 화면을 하나의 파일에 모두 넣기보다, 먼저 상품 카드 하나를 컴포넌트로 생각해 볼 수 있습니다.
<script>
let name = '기계식 키보드';
let price = 89000;
</script>
<article class='card'>
<h2>{name}</h2>
<p>{price.toLocaleString('ko-KR')}원</p>
</article>
<style>
.card {
padding: 16px;
border: 1px solid #e2e8f0;
border-radius: 12px;
}
</style>이 컴포넌트는 아직 단순합니다. 하지만 화면 조각으로서 역할이 분명합니다. 상품 이름과 가격을 보여 주는 카드입니다.
나중에는 이 카드에 props를 사용해 바깥에서 상품 정보를 전달할 수 있습니다. 여러 개의 상품을 반복해서 보여 줄 수도 있습니다. 장바구니 버튼을 추가할 수도 있습니다. 하지만 출발점은 항상 같습니다.
하나의 .svelte 파일 안에서 로직, 마크업, 스타일을 연결하는 것입니다.
이 구조에 익숙해지면 Svelte 코드를 읽는 속도가 빨라집니다. 어떤 값이 화면에 표시되는지, 어떤 버튼이 어떤 함수를 실행하는지, 어떤 스타일이 어디까지 영향을 주는지 자연스럽게 보이기 시작합니다. 다음 장에서는 이 구조 위에서 값이 바뀔 때 화면이 어떻게 함께 바뀌는지, Svelte 5의 runes 문법을 중심으로 더 자세히 살펴보겠습니다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.