Stores — 컴포넌트를 넘어서는 전역 상태 관리
컴포넌트 트리 깊숙이 데이터를 전달해야 할 때, Props를 10단계나 내려보내는 건 고통입니다 — Store가 그 문제를 해결합니다.
개념 정의
Store 는 컴포넌트 외부에서 반응형 상태를 관리하는 메커니즘입니다. 여러 컴포넌트에서 공유해야 하는 데이터를 중앙에서 관리하며, 값이 변경되면 구독 중인 모든 컴포넌트가 자동 업데이트됩니다.
writable — 읽기/쓰기 스토어
// stores.js
import { writable } from 'svelte/store';
// 초기값을 인자로 전달
export const count = writable(0);
export const user = writable({ name: '', loggedIn: false });
export const theme = writable('light');
<!-- Counter.svelte -->
<script>
import { count } from './stores.js';
</script>
<!-- $ 접두사로 자동 구독/해제 -->
<p>카운트: {$count}</p>
<button onclick={() => $count++}>증가</button>
<button onclick={() => count.set(0)}>초기화</button>
<button onclick={() => count.update(n => n + 10)}>+10</button>
Store API
const myStore = writable(initialValue);
// set — 값 교체
myStore.set(newValue);
// update — 현재 값을 기반으로 업데이트
myStore.update(currentValue => currentValue + 1);
// subscribe — 값 변경 구독 ($ 접두사 대신 수동 구독)
const unsubscribe = myStore.subscribe(value => {
console.log('값 변경:', value);
});
// 구독 해제
unsubscribe();
readable — 읽기 전용 스토어
외부에서 값을 설정할 수 없고, 스토어 내부에서만 값을 변경할 수 있습니다.
// stores.js
import { readable } from 'svelte/store';
// 현재 시간을 1초마다 업데이트
export const time = readable(new Date(), (set) => {
const interval = setInterval(() => {
set(new Date());
}, 1000);
// 정리 함수 — 마지막 구독자가 해제될 때 호출
return () => clearInterval(interval);
});
// 마우스 위치 추적
export const mousePosition = readable({ x: 0, y: 0 }, (set) => {
function handleMove(e) {
set({ x: e.clientX, y: e.clientY });
}
window.addEventListener('mousemove', handleMove);
return () => window.removeEventListener('mousemove', handleMove);
});
<script>
import { time, mousePosition } from './stores.js';
</script>
<p>현재 시간: {$time.toLocaleTimeString()}</p>
<p>마우스: ({$mousePosition.x}, {$mousePosition.y})</p>
derived — 파생 스토어
다른 스토어로부터 계산된 값을 제공합니다.
// stores.js
import { writable, derived } from 'svelte/store';
export const items = writable([
{ name: '사과', price: 1000, quantity: 3 },
{ name: '바나나', price: 500, quantity: 5 },
]);
// items가 변경되면 자동 재계산
export const totalPrice = derived(items, ($items) =>
$items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
export const itemCount = derived(items, ($items) => $items.length);
// 여러 스토어 조합
export const firstName = writable('길동');
export const lastName = writable('홍');
export const fullName = derived(
[firstName, lastName],
([$first, $last]) => `${$last}${$first}`
);
$ 자동 구독의 원리
Svelte 컴파일러는 $ 접두사가 붙은 스토어 변수를 자동으로 구독/해제 코드로 변환합니다.
<script>
import { count } from './stores.js';
// 컴파일러가 내부적으로 이렇게 변환:
// let $count;
// const unsubscribe = count.subscribe(v => $count = v);
// onDestroy(unsubscribe);
</script>
<!-- $count를 쓰면 자동 구독됨 -->
<p>{$count}</p>
**주의 **: $ 접두사는 .svelte 파일 안에서만 동작합니다. 일반 .js 파일에서는 수동으로 subscribe()를 호출해야 합니다.
Svelte 5에서의 Store
Svelte 5에서는 $state를 .svelte.js 파일에서 사용할 수 있어, 전통적인 Store 대신 이 방식을 쓸 수도 있습니다.
// counter.svelte.js
let count = $state(0);
export function getCount() {
return count;
}
export function increment() {
count++;
}
export function reset() {
count = 0;
}
<!-- App.svelte -->
<script>
import { getCount, increment, reset } from './counter.svelte.js';
</script>
<p>{getCount()}</p>
<button onclick={increment}>증가</button>
<button onclick={reset}>초기화</button>
실전 패턴 — 인증 상태 관리
// authStore.js
import { writable, derived } from 'svelte/store';
function createAuthStore() {
const { subscribe, set, update } = writable({
user: null,
token: null,
loading: false,
});
return {
subscribe,
login: async (email, password) => {
update(s => ({ ...s, loading: true }));
try {
const res = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
headers: { 'Content-Type': 'application/json' },
});
const data = await res.json();
set({ user: data.user, token: data.token, loading: false });
} catch (e) {
update(s => ({ ...s, loading: false }));
throw e;
}
},
logout: () => {
set({ user: null, token: null, loading: false });
},
};
}
export const auth = createAuthStore();
export const isLoggedIn = derived(auth, ($auth) => !!$auth.user);
export const userName = derived(auth, ($auth) => $auth.user?.name ?? '게스트');
면접 포인트
- "Store와 Context API의 차이는?": Store는 컴포넌트 트리와 무관하게 어디서든 import해서 사용할 수 있습니다. Context API는 컴포넌트 트리 내에서만 접근 가능하며, 서로 다른 트리에서 독립적인 인스턴스를 만들 수 있습니다.
- "Svelte 5에서 Store가 여전히 필요한가요?":
.svelte.js파일에서$state를 사용하면 Store 없이도 전역 상태를 관리할 수 있습니다. 하지만 Store는subscribe프로토콜을 제공해 외부 라이브러리와 호환성이 좋고,$자동 구독이 편리합니다.
정리
Store는 Svelte에서 컴포넌트를 넘어서는 상태를 관리하는 핵심 도구입니다. writable로 읽기/쓰기, readable로 읽기 전용, derived로 파생 값 — 이 세 가지를 조합하면 대부분의 상태 관리 시나리오를 커버할 수 있습니다.
댓글 로딩 중...