컴포넌트 기초 — .svelte 파일 하나로 끝내는 UI 단위
.svelte파일 하나에 HTML, CSS, JS가 다 들어있다 — 처음엔 어색하지만, 익숙해지면 이것만큼 편한 게 없습니다.
개념 정의
Svelte 컴포넌트는 재사용 가능한 UI 단위 를 .svelte 확장자 파일 하나로 정의합니다. <script>, 마크업(HTML), <style> 세 섹션으로 구성되며, 각 섹션은 선택 사항입니다.
컴포넌트 기본 구조
<script>
// 1. 로직 — 변수, 함수, import 등
let name = $state('Svelte');
</script>
<!-- 2. 마크업 — HTML 템플릿 -->
<p>안녕하세요, {name}님!</p>
<!-- 3. 스타일 — 스코프 CSS -->
<style>
p {
font-size: 1.2rem;
color: #333;
}
</style>
순서는 자유 이지만, 관습적으로 <script> → 마크업 → <style> 순서를 따릅니다.
컴포넌트 사용하기
<!-- App.svelte -->
<script>
// 컴포넌트를 import해서 사용합니다
import Greeting from './Greeting.svelte';
import UserCard from './UserCard.svelte';
</script>
<Greeting />
<UserCard />
React와 달리 export default가 필요 없습니다. .svelte 파일 자체가 하나의 컴포넌트이며, import하면 바로 사용할 수 있습니다.
표현식 삽입
중괄호 {}로 JavaScript 표현식을 마크업에 삽입합니다.
<script>
let name = $state('세계');
let items = $state([1, 2, 3]);
function getGreeting() {
return '안녕하세요';
}
</script>
<!-- 변수 -->
<p>{name}</p>
<!-- 함수 호출 -->
<p>{getGreeting()}</p>
<!-- 표현식 -->
<p>아이템 수: {items.length}</p>
<p>대문자: {name.toUpperCase()}</p>
<!-- 삼항 연산자 -->
<p>{items.length > 0 ? '있음' : '없음'}</p>
HTML 속성에 표현식 사용
<script>
let src = $state('/images/profile.png');
let alt = $state('프로필 사진');
let isActive = $state(true);
</script>
<!-- 일반 속성 바인딩 -->
<img {src} {alt} />
<!-- 변수명과 속성명이 같으면 단축 표기 가능 -->
<!-- 위 코드는 src={src} alt={alt}와 동일합니다 -->
<!-- 동적 클래스 -->
<div class={isActive ? 'active' : 'inactive'}>
상태 표시
</div>
단축 표기(shorthand): 변수명과 HTML 속성명이 같으면 {src}처럼 쓸 수 있습니다. React에는 없는 편의 기능입니다.
컴포넌트 조합
<!-- Button.svelte -->
<script>
let { label, variant = 'primary' } = $props();
</script>
<button class="btn btn-{variant}">
{label}
</button>
<style>
.btn { padding: 0.5rem 1rem; border: none; border-radius: 4px; cursor: pointer; }
.btn-primary { background: #ff3e00; color: white; }
.btn-secondary { background: #666; color: white; }
</style>
<!-- App.svelte -->
<script>
import Button from './Button.svelte';
</script>
<Button label="저장" />
<Button label="취소" variant="secondary" />
동적 컴포넌트
<script>
import Home from './Home.svelte';
import About from './About.svelte';
import Contact from './Contact.svelte';
// 조건에 따라 다른 컴포넌트를 렌더링합니다
const pages = { home: Home, about: About, contact: Contact };
let currentPage = $state('home');
</script>
<!-- svelte:component로 동적 렌더링 -->
<svelte:component this={pages[currentPage]} />
스타일 스코프
Svelte의 <style> 블록은 기본적으로 ** 해당 컴포넌트에만 적용 **됩니다.
<!-- Parent.svelte -->
<script>
import Child from './Child.svelte';
</script>
<p>부모의 텍스트</p>
<Child />
<style>
/* 이 스타일은 Parent의 <p>에만 적용됩니다 */
/* Child 안의 <p>에는 영향을 주지 않습니다 */
p {
color: red;
}
</style>
전역 스타일이 필요하면 :global() 수정자를 사용합니다.
<style>
/* 전역 스타일 */
:global(body) {
margin: 0;
font-family: sans-serif;
}
/* 이 컴포넌트 내부의 .active만 전역으로 */
.wrapper :global(.active) {
background: yellow;
}
</style>
면접 포인트
- "Svelte 컴포넌트와 React 컴포넌트의 차이는?": Svelte는 파일 자체가 컴포넌트이고, HTML/CSS/JS가 한 파일에 자연스럽게 공존합니다. React는 JSX라는 JS 확장 문법을 사용하며, CSS는 별도 처리가 필요합니다.
- "스타일 스코프는 어떻게 구현되나요?": 컴파일러가 각 컴포넌트의 요소에 고유한 클래스(예:
.svelte-abc123)를 추가하고, CSS 선택자도 그에 맞게 변환합니다. - "단축 표기가 가능한 이유는?": 컴파일러가 정적 분석을 통해 변수명과 속성명의 일치를 감지하기 때문입니다.
정리
Svelte 컴포넌트는 "하나의 파일, 하나의 관심사"라는 철학을 따릅니다. 복잡한 설정 없이 파일 하나에 로직, 마크업, 스타일을 다 담을 수 있다는 점이 생산성을 높여줍니다. 특히 CSS 스코프가 기본 내장이라는 건, CSS 충돌 걱정 없이 컴포넌트를 만들 수 있다는 의미입니다.
댓글 로딩 중...