Runes 심화 — .raw, , 그리고 세밀한 반응성
$state만으로는 부족한 순간이 옵니다 — 대량 데이터, 디버깅, 성능 튜닝에 필요한 심화 Runes를 알아봅시다.
$state.raw — 얕은 반응성
기본 $state는 객체 내부까지 깊은 반응성을 제공합니다. $state.raw는 재할당만 감지합니다.
<script>
// 깊은 반응성 — 내부 속성 변경도 감지
let deep = $state({ items: [1, 2, 3] });
deep.items.push(4); // UI 업데이트됨
// 얕은 반응성 — 재할당만 감지
let raw = $state.raw({ items: [1, 2, 3] });
raw.items.push(4); // UI 업데이트 안 됨!
raw = { items: [...raw.items, 4] }; // 이렇게 해야 됨
</script>
언제 쓰나요?: 수천 개의 항목이 있는 배열이나, 외부 라이브러리에서 관리하는 객체처럼 깊은 프록시가 성능을 떨어뜨리는 경우에 사용합니다.
<script>
// 대량 데이터에는 $state.raw가 유리
let largeDataset = $state.raw(
Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `항목 ${i}`,
value: Math.random(),
}))
);
function updateItem(id, newValue) {
// 전체 재할당 (불변 업데이트)
largeDataset = largeDataset.map(item =>
item.id === id ? { ...item, value: newValue } : item
);
}
</script>
$state.snapshot — 반응형 프록시의 순수 복사본
<script>
let user = $state({ name: '홍길동', scores: [90, 85, 92] });
function logUser() {
// $state 객체를 console.log하면 Proxy가 보임
console.log(user); // Proxy { ... }
// snapshot으로 순수 객체 얻기
console.log($state.snapshot(user)); // { name: '홍길동', scores: [90, 85, 92] }
}
function sendToAPI() {
// API에 보낼 때도 snapshot 사용
fetch('/api/user', {
method: 'POST',
body: JSON.stringify($state.snapshot(user)),
});
}
</script>
$inspect — 개발 모드 디버깅
<script>
let count = $state(0);
let user = $state({ name: '홍길동' });
// count나 user가 변경될 때마다 콘솔에 출력
$inspect(count);
$inspect(user);
// 커스텀 콜백
$inspect(count).with((type, value) => {
// type: 'init' | 'update'
if (type === 'update') {
console.log('카운트 변경됨:', value);
}
});
// 디버거 중단점 설정
$inspect(count).with((type, value) => {
if (value > 10) debugger;
});
</script>
** 주의 **: $inspect는 개발 모드에서만 동작합니다. 프로덕션 빌드에서는 자동으로 제거됩니다.
untrack — 의존성 추적 비활성화
<script>
import { untrack } from 'svelte';
let count = $state(0);
let logCount = $state(0);
$effect(() => {
// count 변경 시 실행되지만,
// logCount 변경은 추적하지 않음
console.log(`count: ${count}, logCount: ${untrack(() => logCount)}`);
});
</script>
$effect와 세밀한 제어
<script>
import { untrack } from 'svelte';
let searchQuery = $state('');
let searchResults = $state([]);
let isLoading = $state(false);
// searchQuery만 추적, isLoading과 searchResults는 추적 안 함
$effect(() => {
const query = searchQuery; // 이 줄에서 의존성 등록
if (!query) {
untrack(() => {
searchResults = [];
isLoading = false;
});
return;
}
untrack(() => { isLoading = true; });
const timeout = setTimeout(async () => {
const res = await fetch(`/api/search?q=${query}`);
const data = await res.json();
untrack(() => {
searchResults = data;
isLoading = false;
});
}, 300);
return () => clearTimeout(timeout);
});
</script>
클래스에서 Runes 사용
<script>
class Counter {
count = $state(0);
doubled = $derived(this.count * 2);
increment() {
this.count++;
}
reset() {
this.count = 0;
}
}
class TodoList {
items = $state([]);
filter = $state('all');
filtered = $derived.by(() => {
switch (this.filter) {
case 'active': return this.items.filter(i => !i.done);
case 'done': return this.items.filter(i => i.done);
default: return this.items;
}
});
add(text) {
this.items.push({ id: Date.now(), text, done: false });
}
toggle(id) {
const item = this.items.find(i => i.id === id);
if (item) item.done = !item.done;
}
}
const counter = new Counter();
const todos = new TodoList();
</script>
<button onclick={() => counter.increment()}>
{counter.count} (x2 = {counter.doubled})
</button>
면접 포인트
- "$state와 $state.raw의 성능 차이는?":
$state는 Proxy를 사용해 깊은 반응성을 제공하므로, 대량 데이터에서는 프록시 생성과 속성 접근에 오버헤드가 있습니다.$state.raw는 프록시를 만들지 않아 메모리와 CPU 사용이 적지만, 불변 업데이트가 필요합니다. - "untrack은 왜 필요한가요?": $effect 안에서 특정 반응형 값을 읽되 의존성으로 등록하고 싶지 않을 때 사용합니다. 무한 루프 방지나 불필요한 재실행 방지에 필수적입니다.
정리
Runes 심화 기능은 "대부분의 경우는 기본 $state로 충분하지만, 특수한 상황에서 세밀한 제어가 필요할 때" 사용합니다. $state.raw로 성능 최적화, $inspect로 디버깅, untrack으로 의존성 제어 — 이 도구들을 알면 복잡한 반응성 시나리오도 깔끔하게 처리할 수 있습니다.
댓글 로딩 중...