라이프사이클 훅: 컴포넌트의 생성과 파괴

라이프사이클 훅: 컴포넌트의 생성과 파괴

컴포넌트가 화면에 붙을 때 시작하고, 사라질 때 정리해야 하는 작업이 있어요. 예를 들어 타이머를 걸었다가 컴포넌트가 없어질 때 해제하거나, 마운트된 시점에 한 번 로그를 남기는 식이죠. 이렇게 컴포넌트의 생명주기(lifecycle) 에 맞춰 코드를 실행하는 게 라이프사이클 훅이에요.

Svelte 5에서 컴포넌트 라이프사이클은 생성과 파괴, 두 부분으로만 이뤄져요. 그 사이의 '이 상태가 바뀔 때' 같은 일은 컴포넌트 전체가 아니라, 그 상태에 반응해야 하는 부분만 다뤄요. 그래서 React의 'before update/after update' 같은 훅은 없어요.

출처: Svelte 공식 문서 — Lifecycle hooks

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>

더 알아보기