Svelte에서 중괄호
{}는 템플릿 문법에서 중요한 역할을 한다.
중괄호는 Svelte 컴포넌트의 HTML에서 JS표현식이나 변수 값을 삽입하거나, 조건부 렌더링, 반복문 등을 사용할 때 필요하다.
Svelte는 이러한 중괄호를 통해 데이터를 효율적으로 렌더링한다.
<script>
let name = 'Svelte';
</script>
<h1>Hello, {name}!</h1>
중괄호 안에서는 단수 변수뿐만 아니라 JS표현식을 사용할 수 있다.
<script>
let count = 5;
</script>
<p>{count * 2}</p> <!-- 출력: 10 -->
<p>{count > 3 ? 'Greater than 3' : 'Less than or equal to 3'}</p> <!-- 출력: Greater than 3 -->
중괄호 안에서는 산술 연산, 조건부 연산 등 다양한 JS표현식이 사용될 수 있다.
Svelte는 중괄호와 함께 if/else문법을 사용하여 조건부 렌더링을 할 수 있다.
<script>
let isLoggedIn = true;
</script>
{#if isLoggedIn}
<p>Welcome back!</p>
{:else}
<p>Please log in.</p>
{/if}
#if와 {/if}로 감싼 블록은 조건에 따라 렌더링 된다.Array like와 같은 것을 순차적으로 출력할 수 있다.
<script>
let fruits = ['Apple', 'Banana', 'Cherry'];
</script>
<ul>
{#each fruits as fruit}
<li>{fruit}</li>
{/each}
</ul>
<!-- App.svelte -->
<script>
export let name;
let age = 30;
function incrementAge() {
age += 1;
}
</script>
<style>
h1 {
color: purple;
}
</style>
<h1>Hello {name}, my age is {age}!</h1>
<button on:click="{incrementAge()}">Change Age</button>
<!-- main.js -->
import App from './App.svelte';
const app = new App({
target: document.body,
props: {
name: 'world'
}
});
export default app;

컴포넌트의 상태가 변경될 때 자동으로 UI가 업데이트되는 중요한 개념
Svelte는 다른 프레임워크와는 달리 자동으로 상태 변화를 추적하고, 해당 상태와 연결된 UI를 다시 렌더링 한다. 반응형 변수는 크게 두가지로 사용할 수 있는데, 일반 변수와 $:문법을 사용하는 반응형 선언이다.
Svelte는 기본적으로 변수가 변경될 때 이를 추적하여 UI를 자동으로 업데이트한다. 예를 들어, 일반 변수의 값이 변경되면 이를 참조하는 템플릿은 자동으로 다시 렌더링 된다.
<script>
let count = 0;
function increment() {
count += 1;
}
</script>
<p>Count: {count}</p>
<button on:click={increment}>Increment</button>
count가 변경되면 {count}를 사용한 템플릿 부분이 자동으로 업데이트 된다. 특별한 선언 없이도 Svelte는 count값 변화를 감지하여 UI를 갱신한다.
특정 변수나 표현식의 값이 변경될 때마다 다른 변수나 로직을 재실행하고 싶을 때는 반응형 선언을 사용한다.
<script>
let a = 1;
let b = 2;
let sum;
$: sum = a + b; // a 또는 b가 변경될 때마다 sum이 자동으로 갱신됨
</script>
<p>Sum: {sum}</p>
<button on:click={() => a += 1}>Increment A</button>
<button on:click={() => b += 1}>Increment B</button>
$: sum = a + b; 선언은 a나 b가 변경될 때마다 sum을 자동으로 업데이트한다. 이를 통해 sum을 사용하는 템플릿이 다시 렌더링된다.굳이, 먼저 let이나 const로 선언하지 않아도 반응형 선언을 이용할 수도 있다.
<script>
export let name;
let age = 30;
$: uppercaseName = name.toUpperCase();
// Svelte의 반응형 시스템 덕분에 별도의 추가 로직 없이도 name 값이 변경될 때마다 uppercaseName이 다시 계산된다.
$: console.log(name);
// 이름이 바뀔때 출력
$: if (name === 'Maximilian') {
age = 31;
}
// 이름에 조건에 맞으면 age = 31이 된다.
function incrementAge() {
age += 1;
}
function changeName() {
name = 'Maximilan';
}
</script>
<style>
h1 {
color: purple;
}
</style>
<h1>Hello {uppercaseName}, my age is {age}!</h1>
<button on:click={incrementAge}>Change Age</button>
<button on:click={changeName}>Change Name</button>
<!-- App.svelte -->

위처럼 변수를 업데이트할 뿐만 아니라, 복잡한 로직도 $:를 사용해 반응형으로 처리할 수 있다.
<script>
let count = 0;
// count가 변경될 때마다 이 블록이 실행됨
$: {
if (count > 10) {
console.log("Count is greater than 10");
}
}
</script>
<p>{count}</p>
<button on:click={() => count += 1}>Increment</button>
count가 변경될 때마다 블록 안의 로직이 실행된다. 여기서 count가 10을 넘으면 콘솔에 로그가 출력된다.$:선언은 상단에 있는 변수들이 변경될 때 트리거가 된다. 즉, $:선언 블록은 해당하는 변수의 값이 변경될 때만 실행된다.<script>
let x = 1;
let y = 2;
// x 또는 y가 변경될 때마다 이 블록이 실행됨
$: console.log(`x: ${x}, y: ${y}`);
</script>
<p>x: {x}, y: {y}</p>
<button on:click={() => x += 1}>Increment X</button>
<button on:click={() => y += 1}>Increment Y</button>
<script>
let count = 0;
function increment() {
$: count += 1;
}
</script>
<p>Count: {count}</p>
<button on:click={increment}>Increment</button>
<!-- App.svelte -->
<script>
export let name;
let age = 30;
$: uppercaseName = name.toUpperCase();
// Svelte의 반응형 시스템 덕분에 별도의 추가 로직 없이도 name 값이 변경될 때마다 uppercaseName이 다시 계산된다.
$: console.log(name);
// 이름이 바뀔때 출력
$: if (name === 'Maximilian') {
age = 31;
}
function incrementAge() {
age += 1;
}
function changeName() {
name = 'Maximilian';
}
</script>
<style>
h1 {
color: purple;
}
</style>
<h1>Hello {uppercaseName}, my age is {age}!</h1>
<button on:click={incrementAge}>Change Age</button>
<!--<button on:click={changeName}>Change Name</button>-->
<input type="text" value="{name}">
위에 input에 글자를 입력해도, name이 바뀌지는 않는다.

기본적으로 Svelte의 데이터 플로우는 단방향으로 위처럼, 선언하면 데이터를 출력하기만 하고 input 요소의 변경 사항에 따라 반응해 name 변수를 바꾸지 않고 있다.
이를 반영하려면
on:input리스너를 추가해야 한다.
<!-- App.svelte -->
<script>
export let name;
let age = 30;
$: uppercaseName = name.toUpperCase();
// Svelte의 반응형 시스템 덕분에 별도의 추가 로직 없이도 name 값이 변경될 때마다 uppercaseName이 다시 계산된다.
$: console.log(name);
// 이름이 바뀔때 출력
$: if (name === 'Maximilian') {
age = 31;
}
function incrementAge() {
age += 1;
}
function changeName() {
name = 'Maximilian';
}
function nameInput(event) {
name = event.target.value;
}
</script>
<style>
h1 {
color: purple;
}
</style>
<h1>Hello {uppercaseName}, my age is {age}!</h1>
<button on:click={incrementAge}>Change Age</button>
<input type="text" value={name} on:input={(e) => nameInput(e)} />
<!--<button on:click={changeName}>Change Name</button>-->

<input type="text" bind:value="{name}"/>
이렇게 값을 bind하면, 양방향 바인드가 된다.
거의 모든 HTML프로퍼티를 바인딩 할 수 있다. 하지만, 너무 남발하지 않는게 좋은데, 단방향 데이터 플로우의 원칙은 이유가 있을 때만 어겨야 한다.
다음은 양방향 바인딩의 단점이다!!
데이터 추적이 어려워짐: 데이터가 UI에서 변경되면 모델에도 자동으로 적용되고, 반대로 모델이 변경되면 UI에도 자동으로 반영된다. 이로 인해 데이터의 변경 원인이 어디에서 비롯되었는지 추적하기 어려워질 수 있다.데이터 변경의 혼란: 모델과 UI가 서로 영향을 주기 때문에 어느 시점에 데이터가 변경되었는지 예측하기 어렵다. 예를 들어, 여러 컴포넌트에서 동일한 데이터를 양방향 바인딩하면 예상치 못한 충돌이나 변경이 발생할 수 있다.유지보수성 저하: 양방향 바인딩을 남용하면 코드의 복잡성이 증가하며, 데이터 흐름이 비직관적이 되어 문제가 발생했을 때 해결하기 어려워진다.on:inputon:input은 사용자가 입력할 때마다 즉시 트리거된다. 입력 필드의 내용이 변경될 때마다 이벤트가 발생하여, 테긋트가 변경될 때 마다 실시간으로 반응할 수 있다.name변수에 반영되어 Svelte의 반응형 시스템에 의해 화면이 즉시 업데이트된다.on:changeon:change는 사용자가 입력 후 포커스를 벗어나거나(blur), Enter키를 눌러 값을 확정한 시점에 한 번만 트리거된다. 즉, 입력 필드의 내용을 다 작성한 후에 이벤트가 발생한다.<!-- ContactCard.svelte -->
<style>
.contact-card {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
max-width: 30rem;
border-radius: 5px;
margin: 1rem 0;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
height: 7rem;
}
.thumb {
width: 33%;
height: 100%;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.user-data {
width: 67%;
display: flex;
flex-direction: column;
justify-content: center;
}
h1 {
font-size: 1.25rem;
font-family: "Roboto Slab", sans-serif;
margin: 0.5rem 0;
}
h2 {
font-size: 1rem;
font-weight: normal;
color: #5a5a5a;
margin: 0;
margin-bottom: 0.5rem;
}
.description {
border-top: 1px solid #ccc;
padding: 1rem;
}
</style>
<div class="contact-card">
<header>
<div class="thumb">
<img src="" alt="" />
</div>
<div class="user-data">
<h1>User Name</h1>
<h2>Job Title</h2>
</div>
</header>
<div class="description">
<p>A short description</p>
</div>
</div>
<!-- App.svelte -->
<script>
import ContactCard from "./ContactCard.svelte";
export let name;
let age = 30;
$: uppercaseName = name.toUpperCase();
// Svelte의 반응형 시스템 덕분에 별도의 추가 로직 없이도 name 값이 변경될 때마다 uppercaseName이 다시 계산된다.
$: console.log(name);
// 이름이 바뀔때 출력
$: if (name === 'Maximilian') {
age = 31;
}
function incrementAge() {
age += 1;
}
function changeName() {
name = 'Maximilian';
}
function nameInput(event) {
name = event.target.value;
}
</script>
<style>
h1 {
color: purple;
}
</style>
<h1>Hello {uppercaseName}, my age is {age}!</h1>
<button on:click={incrementAge}>Change Age</button>
<input type="text" bind:value="{name}"/>
<ContactCard/>
<!--<button on:click={changeName}>Change Name</button>-->
<!--<input type="text" value="{name}" on:input={(e) => nameInput(e)} />-->

부모 컴포넌트가 자식 컴포넌트에 데이터를 전달할 때 props를 사용한다. Svelte에서 자식 컴포넌트는 export키워드를 사용하여 부모로부터 받은 데이터를 처리할 수 있다.
<!-- App.svelte -->
<script>
import ContactCard from "./ContactCard.svelte";
export let name;
let title = "";
let image = "";
let description = "";
let age = 30;
$: uppercaseName = name.toUpperCase();
// Svelte의 반응형 시스템 덕분에 별도의 추가 로직 없이도 name 값이 변경될 때마다 uppercaseName이 다시 계산된다.
$: console.log(name);
// 이름이 바뀔때 출력
$: if (name === 'Maximilian') {
age = 31;
}
function incrementAge() {
age += 1;
}
function changeName() {
name = 'Maximilian';
}
function nameInput(event) {
name = event.target.value;
}
</script>
<style>
h1 {
color: purple;
}
</style>
<h1>Hello {uppercaseName}, my age is {age}!</h1>
<button on:click={incrementAge}>Change Age</button>
<input type="text" bind:value={name}/>
<input type="text" bind:value={title}/>
<input type="text" bind:value={image}/>
<textarea rows="3" bind:value={description}></textarea>
<ContactCard
userName={name}
userImage={image}
jobTitle={title}
description={description}
/>
<!--<button on:click={changeName}>Change Name</button>-->
<!--<input type="text" value="{name}" on:input={(e) => nameInput(e)} />-->
<!-- ContactCard -->
<script>
export let userName;
export let userImage;
export let jobTitle;
export let description;
</script>
<style>
.contact-card {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
max-width: 30rem;
border-radius: 5px;
margin: 1rem 0;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
height: 7rem;
}
.thumb {
width: 33%;
height: 100%;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.user-data {
width: 67%;
display: flex;
flex-direction: column;
justify-content: center;
padding-left: 1rem;
}
h1 {
font-size: 1.25rem;
font-family: "Roboto Slab", sans-serif;
margin: 0.5rem 0;
}
h2 {
font-size: 1rem;
font-weight: normal;
color: #5a5a5a;
margin: 0;
margin-bottom: 0.5rem;
}
.description {
border-top: 1px solid #ccc;
padding: 1rem;
}
</style>
<div class="contact-card">
<header>
<div class="thumb">
<img src={userImage} alt="sss" />
</div>
<div class="user-data">
<h1>{userName}</h1>
<h2>{jobTitle}</h2>
</div>
</header>
<div class="description">
<p>{description}</p>
</div>
</div>

Svelte에서 기본적ㅈ으로 데이터는 단방향으로 흐른다. 부모에서 자식으로 props가 전달되고, 자식은 이를 수정할 수 없다. 자식 컴포넌트는 부모의 데이터를 읽기만 할 수 있으며, 상태를 변경하려면 이벤트를 통해 부모에게 요청해야 한다. 이는 데이터 흐름을 명확하게 하고, 버그를 예방하는 데 도움이 된다.
Svelte에서는 props이름과 변수 이름이 동일하면 중복을 줄이기 위해 {description}과 같이 변수 이름만 적어도 된다. Svelte는 이 경우, {description}이라는 변수를 {description}이라는 props로 자동 매핑한다.
<ContactCard
userName={name}
userImage={image}
jobTitle={title}
{description}
/>
자식 컴포넌트가 부모 컴포넌트에 데이터를 전달할 때는 이벤트 디스패치(dispatch)를 사용한다. 자식 컴포넌트는 이벤트를 발생시키고, 부모 컴포넌트는 그 이벤트를 청취하여 데이터를 처리할 수 있다.
<!-- Parent.svelte -->
<script>
function handleNameChange(event) {
alert(`New name: ${event.detail}`);
}
</script>
<Child on:nameChange={handleNameChange} />
<!-- Child.svelte -->
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function changeName() {
dispatch('nameChange', 'Maximilian'); // 부모 컴포넌트로 'nameChange' 이벤트를 발생시킴
}
</script>
<button on:click={changeName}>Change Name</button>
createEventDispatcher: Svelte는 createEventDispatcher함수를 제공하여 자식 컴포넌트에서 이벤트를 발생시킬 수 있다.dispatch('nameChange', 'Maximilian')에서 nameChange라는 이벤트를 발생시키며, Maximilian이라는 데이터를 부모에게 전달한다.on:nameChange를 통해 이 이벤트를 듣고, handleNameChange 함수를 실행하여 데이터를 처리한다.특정 상황에서는 양방향 데이터 바인딩이 필요할 수 있다. Svelte에서는 양방향 바인딩을 허용하긴 하지만, 기본적으로 권장되지는 않는다! 양방향 바인디응ㄴ 자식 컴포넌트에서 부모 컴포넌트의 상태를 직접 변경할 수 있도록 허용한다.
<!-- Parent.svelte -->
<script>
let name = "Svelte";
</script>
<Child bind:name={name} />
<!-- Child.svelte -->
<script>
export let name;
</script>
<input type="text" bind:value={name} />
부모 컴포넌트는 bind:name={name}을 통해 양방향 바인딩을 적용한다. 이로 인해 자식 컴포넌트에서 name을 수정하면 부모 컴포넌트의 상태도 실시간으로 업데이트된다.
여러 자식 컴포넌트가 동일한 데이터에 접근할 수 있도록 컨텍스트 API을 사용할 수 있다. 이는 여러 계층의 컴포넌트에 걸쳐 데이터를 전달해야 할 때 유용하다.
<!-- Parent.svelte -->
<script>
import { setContext } from 'svelte';
const user = { name: "Svelte User", age: 30 };
setContext('user', user); // 자식 컴포넌트에 'user' 데이터를 전달
</script>
<Child />
<!-- Child.svelte -->
<script>
import { getContext } from 'svelte';
const user = getContext('user'); // 'user' 데이터를 받아옴
</script>
<p>{user.name}, Age: {user.age}</p>
setContext: 부모 컴포넌트에서 setContext를 사용해 데이터를 정의하고, 이를 자식 컴포넌트로 전달한다.getContext자식 컴포넌트는 getContext를 사용해 상위 컴포넌트에서 정의된 데이터를 받아 사용할 수 있다.이 방법은 컴포넌트가 깊게 중첩되어 있을 때 유용하며, props를 통해 데이터를 일일히 전달할 필요 없이 여러 자식 컴포넌트에서 동일한 데이터를 공유할 수 있다.
HTML 콘텐츠를 출력할 때는 XSS(크로스 사이트 스크립팅, Cross-Site Scripting) 공격에 취약해질 수 있다.
XSS는 악성 사용자나 공격자가 악성 코드를 웹 페이지에 삽입해, 다른 사용자의 브라우저에서 해당 코드를 실행시키는 공격 방식이다. 이를 통해 공격자는 세션 정보, 쿠키와 같은 데이터를 탈취하거나 웹사이트에 악용할 수 있다.
Svelte는 기본적으로 안전한 문자열을 렌더링하여 XSS공격을 방지한다. 기본적으로 HTML 요소에 값을 삽입할 때, Svelte는 그 값을 이스케이프 처리한다. 즉, 태그나 스크립트가 포함된 문자열을 자동으로 이스케이프 처리하여 악성 스크립트가 실행되지 않도록 보호한다.
<!-- 안전한 출력 -->
<script>
let userInput = "<script>alert('XSS Attack!')</script>";
</script>
<p>{userInput}</p>
Svelte는 기본적으로 {userInput}을 HTML로 출력할 때 스크립트를 실행하지 않고, 이를 문자 그대로 이스케이프 처리한다. 즉, <나 > 같은 특수 문자를 HTML 엔터티로 변환해 브라우저에서 해석되지 않도록 한다.
Svelte에서 {@html}태그를 사용하여 입력을 HTML로 직접 렌더링할 경우, XSS공격에 취약해질 수 있다.
이 태그는 HTML을 문자열로 출력하면서 해당 문자열을 이스케이프 처리하지 않고 그대로 렌더링한다.
<!-- 위험한 경우 -->
<script>
let userInput = "<script>alert('XSS Attack!')</script>";
</script>
<p>{@html userInput}</p> <!-- XSS 취약성 발생 -->
위 코드에서 {@html}을 사용하여 사용자 입력을 HTML로 렌더링하고 있는데, 이 경우 악성 스크립트가 실행될 수 있다.
Svelte에서는 템플릿 내에서 삼항 연산자를 사용해 조건에 따라 CSS클래스를 동적으로 설정할 수 있다.
<!-- 조건에 따른 동적 클래스 설정 -->
<script>
let isActive = true;
</script>
<button class={isActive ? 'active' : 'inactive'}>
{isActive ? 'Active' : 'Inactive'}
</button>
class={isActive ? 'active' : 'inactive'}는 isActive 값에 따라 active 또는 inactive 클래스를 동적으로 적용한다.isActive 상태에 따라 변경된다.객체를 사용하여 여러 클래스를 조건부로 적용할 수 있다.
객체의 키는 클래스 이름, 값은 해당 클래스가 적용될 조건을 나타낸다.
<!-- 객체 기반의 동적 클래스 바인딩 -->
<script>
let isActive = true;
let isDisabled = false;
</script>
<button class:active={isActive} class:disabled={isDisabled}>
Dynamic Button
</button>
class:active = {isActive}는 isActice가 true일 때만 active클래스를 추가한다.class:disabled={isDisabled}는 isDisabled가 true일 때만 disabled클래스를 추가한다.여러 클래스를 조건부로 적용하려면, 객체를 사용하여 클래스를 동적으로 적용할 수 있다.
객체의 키는 클래스 이름이고, 값은 그 클래스가 적용될 조건이다.
<!-- 여러 클래스 동적 적용 -->
<script>
let isActive = true;
let isDisabled = false;
</script>
<button class={ { active: isActive, disabled: isDisabled } }>
Multiple Dynamic Classes
</button>
active와 disabled클래스가 각각 isActive와 isDisabled값에 따라 동적으로 적용된다.여러 클래스를 문자열로 결합하여 동적으로 설정할 수 있다.
<!-- 여러 클래스 문자열 결합 -->
<script>
let isPrimary = true;
let isLarge = false;
</script>
<button class={`btn ${isPrimary ? 'btn-primary' : ''} ${isLarge ? 'btn-large' : ''}`}>
Dynamic Classes
</button>
btn클래스는 항상 적용되고, btn-primary와 btn-large는 조건에 따라 추가된다.<!-- 동적 스타일 적용 -->
<script>
let color = 'red';
let fontSize = 20;
</script>
<button style="color: {color}; font-size: {fontSize}px;">
Dynamic Styles
</button>
<button on:click={() => color = 'blue'}>
Change Color to Blue
</button>
style속성에서 동적으로 color와 fontSize값을 변경할 수 있다.color값이 바뀌어 버튼의 텍스트 색상이 변경된다.<script>
export let userName;
export let userImage;
export let jobTitle;
export let description;
</script>
<style>
.contact-card {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
max-width: 30rem;
border-radius: 5px;
margin: 1rem 0;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
height: 7rem;
}
.thumb {
width: 33%;
height: 100%;
}
.thumb-placeholder {
background: #cccccc;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.user-data {
width: 67%;
display: flex;
flex-direction: column;
justify-content: center;
padding-left: 1rem;
}
h1 {
font-size: 1.25rem;
font-family: "Roboto Slab", sans-serif;
margin: 0.5rem 0;
}
h2 {
font-size: 1rem;
font-weight: normal;
color: #5a5a5a;
margin: 0;
margin-bottom: 0.5rem;
}
.description {
border-top: 1px solid #ccc;
padding: 1rem;
}
</style>
<div class="contact-card">
<header>
<div class={userImage ? 'thumb' : 'thumb thumb-placeholder'}>
<img src={userImage} alt="sss" />
</div>
<div class="user-data">
<h1>{userName}</h1>
<h2>{jobTitle}</h2>
</div>
</header>
<div class="description">
<p>{description}</p>
</div>
</div>
userImage를 확인해, 만약 값이 없으면 thumb-placeholder를 적용한다.

