$derived — 파생 상태
$derived — 파생 상태
파생된 상태(derived state)는 $derived 런(rune)로 선언해요. 다른 상태 변수에 의존하는 값을 계산할 때 쓰는 런이죠. $derived(...) 안의 표현식은 부작용(side effect)이 없어야 하고, Svelte는 파생 표현식 안에서 상태 변경(예: count++)을 허용하지 않아요.
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>
<button onclick={() => count++}>
{doubled}
</button>
<p>{count} doubled is {doubled}</p>
$state와 마찬가지로 클래스 필드도 $derived로 표시할 수 있어요. Svelte 컴포넌트의 코드는 생성 시 한 번만 실행되므로, $derived 런이 없으면 doubled은 count가 변해도 원래 값을 유지해요.
$derived.by
짧은 표현식에 들어맞지 않는 복잡한 파생이 필요할 때는 함수를 인자로 받는 $derived.by를 쓸 수 있어요.
<script>
let numbers = $state([1, 2, 3]);
let total = $derived.by(() => {
let total = 0;
for (const n of numbers) {
total += n;
}
return total;
});
</script>
<button onclick={() => numbers.push(numbers.length + 1)}>
{numbers.join(' + ')} = {total}
</button>
본질적으로 $derived(expression)은 $derived.by(() => expression)과 같아요.
의존성 이해하기
$derived 표현식(또는 $derived.by 함수 본문) 안에서 동기적으로 읽히는 것은 모두 파생 상태의 의존성으로 간주돼요. 상태가 바뀌면 파생 상태는 "dirty"로 표시되고, 다음에 읽힐 때 다시 계산돼요.
표현식에 await가 있으면 Svelte는 변환을 거쳐 await 이후의 상태도 추적해요. $derived(a + await b) 같은 경우 a와 b가 둘 다 추적돼요. 특정 상태를 의존성에서 제외하고 싶다면 untrack을 쓰세요.
파생 값 덮어쓰기
파생 표현식은 의존성이 바뀌면 다시 계산되지만, const로 선언하지 않았다면 재할당으로 값을 임시로 덮어쓸 수 있어요. 낙관적 UI(optimistic UI) 같은 데 유용해요. 서버 데이터에서 파생된 값을 표시하면서 사용자에게 즉각적인 피드백을 주고, 실패하면 되돌리는 방식이죠. (Svelte 5.25부터 파생 값이 쓰기 가능해졌어요.)
반응성
$state는 객체·배열을 깊은 반응형 프록시로 바꾸지만, $derived 값은 그대로 남겨둬요. 파생 상태를 구조 분해(destructuring)하면 결과 변수들이 모두 반응형이 돼요. 예를 들어 배열 인덱스에서 파생된 객체를 bind:로 바꾸면 원래 배열에 영향을 줄 수 있어요.
업데이트 전파
Svelte는 push-pull 반응성이라는 방식을 써요. 상태가 갱신되면 그 상태에 의존하는 모든 것이 즉시 알림을 받지만(push), 파생 값은 실제로 읽힐 때까지 다시 계산되지 않아요(pull). 파생 값의 새 값이 이전 값과 참조상 동일하면 하위 업데이트가 건너뛰어져요. 그래서 large가 참조적으로 동일하면 count가 변해도 버튼 텍스트는 갱신되지 않아요.
더 알아보기
- 반응형 상태
$state: https://svelte.dev/docs/svelte/$state - 효과
$effect: https://svelte.dev/docs/svelte/$effect - 런이란 무엇인가(What are runes?): https://svelte.dev/docs/svelte/what-are-runes