라이프사이클 훅: 컴포넌트의 생성과 파괴
라이프사이클 훅: 컴포넌트의 생성과 파괴
컴포넌트가 화면에 붙을 때 시작하고, 사라질 때 정리해야 하는 작업이 있어요. 예를 들어 타이머를 걸었다가 컴포넌트가 없어질 때 해제하거나, 마운트된 시점에 한 번 로그를 남기는 식이죠. 이렇게 컴포넌트의 생명주기(lifecycle) 에 맞춰 코드를 실행하는 게 라이프사이클 훅이에요.
Svelte 5에서 컴포넌트 라이프사이클은 생성과 파괴, 두 부분으로만 이뤄져요. 그 사이의 '이 상태가 바뀔 때' 같은 일은 컴포넌트 전체가 아니라, 그 상태에 반응해야 하는 부분만 다뤄요. 그래서 React의 'before update/after update' 같은 훅은 없어요.
onMount
onMount 함수는 컴포넌트가 DOM에 마운트되자마자 콜백을 실행하도록 예약해요. 컴포넌트 초기화 중에 호출해야 하며, 꼭 컴포넌트 안에 있을 필요는 없어요(외부 모듈에서 호출할 수도 있어요). 서버에서 렌더링되는 컴포넌트 안에서는 실행되지 않아요.
<script>
import { onMount } from 'svelte';
onMount(() => {
console.log('the component has mounted');
});
</script>
onMount 에서 함수를 반환하면, 그 함수는 컴포넌트가 unmount 될 때 호출돼요.
<script>
import { onMount } from 'svelte';
onMount(() => {
const interval = setInterval(() => {
console.log('beep');
}, 1000);
return () => clearInterval(interval);
});
</script>
⚠️ 이 동작은
onMount에 전달한 함수가 동기일 때만 동작해요.async함수는 항상Promise를 반환하니까 주의하세요.
onDestroy
컴포넌트가 unmount 되기 직전에 콜백을 실행하도록 예약해요. onMount/beforeUpdate/afterUpdate/onDestroy 중에서 서버 사이드 컴포넌트 안에서 유일하게 실행되는 훅이에요.
<script>
import { onDestroy } from 'svelte';
onDestroy(() => {
console.log('the component is being destroyed');
});
</script>
tick
'after update' 훅은 없지만, tick 을 쓰면 계속 진행하기 전에 UI가 갱신되도록 보장할 수 있어요. tick 은 보류 중인 상태 변경이 모두 적용되면(없다면 다음 microtask에서) resolve 되는 promise를 반환해요.
<script>
import { tick } from 'svelte';
$effect.pre(() => {
console.log('the component is about to update');
tick().then(() => {
console.log('the component just updated');
});
});
</script>
(deprecated) beforeUpdate / afterUpdate
Svelte 4에는 컴포넌트 전체가 갱신되기 전·후에 실행되는 훅이 있었어요. Svelte 5에서도 하위 호환을 위해 shim으로 남아 있지만, 룬을 쓰는 컴포넌트 안에서는 사용할 수 없어요.
<script>
import { beforeUpdate, afterUpdate } from 'svelte';
beforeUpdate(() => {
console.log('the component is about to update');
});
afterUpdate(() => {
console.log('the component just updated');
});
</script>
beforeUpdate 대신 $effect.pre 를, afterUpdate 대신 $effect 를 써요. 이 룬들은 더 세밀하게 제어할 수 있고, 실제 관심 있는 변화에만 반응해요.
채팅 창 예시
새 메시지가 오면 자동으로 맨 아래로 스크롤되는 채팅 창을 만든다고 해볼게요. (단, 이미 맨 아래에 있을 때만) 그러려면 DOM을 갱신하기 전에 측정해야 해요.
Svelte 4에서는 beforeUpdate 로 했지만, 이 방식은 관련이 있든 없든 매 업데이트마다 실행돼서 결함이 있어요. 아래 예시처럼 updatingMessages 같은 체크를 넣어야 다크 모드를 토글할 때 스크롤 위치를 건드리지 않을 수 있었죠.
"before update"/"after update"는 Svelte 5에서 deprecated 처리됐어요. 룬에서는 $effect.pre 를 쓸 수 있는데, $effect 와 동작은 같지만 그 뒤에 예약된 DOM 업데이트보다 먼저 실행돼요. 이펙트 본문 안에서 messages 를 명시적으로 참조하기만 하면, theme 이 아닌 messages 가 바뀔 때만 실행돼요.
<script>
import { tick } from 'svelte';
let theme = $state('dark');
let messages = $state([]);
let viewport;
$effect.pre(() => {
messages;
const autoscroll = viewport && viewport.offsetHeight + viewport.scrollTop > viewport.scrollHeight - 50;
if (autoscroll) {
tick().then(() => {
viewport.scrollTo(0, viewport.scrollHeight);
});
}
});
function handleKeydown(event) {
if (event.key === 'Enter') {
const text = event.target.value;
if (!text) return;
messages = [...messages, text];
event.target.value = '';
}
}
function toggle() {
theme = theme === 'dark' ? 'light' : 'dark';
}
</script>
<div class:dark={theme === 'dark'}>
<div bind:this={viewport}>
{#each messages as message}
<p>{message}</p>
{/each}
</div>
<input onkeydown={handleKeydown} />
<button onclick={toggle}> Toggle dark mode </button>
</div>
더 알아보기
- 공식 문서 (1차): Lifecycle hooks
- 관련 룬: $effect
- 이어지는 개념: 반응형 상태($state) · 컴포넌트 props($props)