.svelte 파일 하나에 HTML, CSS, JS가 다 들어있다 — 처음엔 어색하지만, 익숙해지면 이것만큼 편한 게 없습니다.

개념 정의

Svelte 컴포넌트는 재사용 가능한 UI 단위 를 .svelte 확장자 파일 하나로 정의합니다. <script>, 마크업(HTML), <style> 세 섹션으로 구성되며, 각 섹션은 선택 사항입니다.

컴포넌트 기본 구조

SVELTE
<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> 순서를 따릅니다.

컴포넌트 사용하기

SVELTE
<!-- App.svelte -->
<script>
  // 컴포넌트를 import해서 사용합니다
  import Greeting from './Greeting.svelte';
  import UserCard from './UserCard.svelte';
</script>

<Greeting />
<UserCard />

React와 달리 export default가 필요 없습니다. .svelte 파일 자체가 하나의 컴포넌트이며, import하면 바로 사용할 수 있습니다.

표현식 삽입

중괄호 {}로 JavaScript 표현식을 마크업에 삽입합니다.

SVELTE
<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 속성에 표현식 사용

SVELTE
<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에는 없는 편의 기능입니다.

컴포넌트 조합

SVELTE
<!-- 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>
SVELTE
<!-- App.svelte -->
<script>
  import Button from './Button.svelte';
</script>

<Button label="저장" />
<Button label="취소" variant="secondary" />

동적 컴포넌트

SVELTE
<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> 블록은 기본적으로 ** 해당 컴포넌트에만 적용 **됩니다.

SVELTE
<!-- Parent.svelte -->
<script>
  import Child from './Child.svelte';
</script>

<p>부모의 텍스트</p>
<Child />

<style>
  /* 이 스타일은 Parent의 <p>에만 적용됩니다 */
  /* Child 안의 <p>에는 영향을 주지 않습니다 */
  p {
    color: red;
  }
</style>

전역 스타일이 필요하면 :global() 수정자를 사용합니다.

SVELTE
<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 충돌 걱정 없이 컴포넌트를 만들 수 있다는 의미입니다.

댓글 로딩 중...