Svelte ] 스벨트의 Lifecycle에 대해 알아보자

히징·2023년 2월 9일
0

Lifecycle

1. onMount

컴포넌트가 처음 DOM에 렌더링 된 후 실행되는 함수입니다.

import { onMount } from 'svelte';

	let photos = [];

	onMount(async () => {
		const res = await fetch(`/tutorial/api/album`);
		photos = await res.json();
	});

fetch를 함수 안에 넣는 것을 추천합니다.

onDestroy를 제외하고 생명 주기 함수는 SSR에서 작동하지 않기 때문에fetch를 onMout함수 안에 넣게 되면 컴포넌트에 fetching 된 데이터가 느리게 로딩 되는 것을 피할 수 있습니다.

💡 onMount 에 return값을 반환하게 되면, 컴포넌트가 소멸되기 직전에 실행됩니다. = onDestroy

2. onDestroy

컴포넌트가 소멸될 때 onDestroy를 사용합니다.

예를 들어 setInterval 함수를 컴포넌트가 초기화 될 때 추가하고, 더 이상 관련이 없을 때 정리할 수 있습니다. 이렇게 하면 메모리 누수를 방지할 수 있습니다.

import { onDestroy } from 'svelte';

let counter = 0;
const interval = setInterval(() => counter += 1, 1000);

onDestroy(() => clearInterval(interval));
</script>

컴포넌트를 초기화하는 동안 lifecycle 함수를 호출하는 것은 중요하지만 호출하는 위치는 중요하지 않습니다. 따라서 원하는 경우 interval 로직을 utils.js에 넣고 컴포넌트에 import하여 사용할 수 있습니다.

import { onDestroy } from 'svelte';

export function onInterval(callback, milliseconds) {
	const interval = setInterval(callback, milliseconds);

	onDestroy(() => {
		clearInterval(interval);
	});
}
import { onInterval } from './utils.js';

let counter = 0;
onInterval(() => counter += 1, 1000);
</script>

2.1. onMount & onDestroy

import Something from './Something.svelte'

let toggle = false
</script>

<button on:click={()=>{toggle = !toggle}}>
Toggle
</button>

{#if toggle}
<Something />
{/if}
import { onMount, onDestroy } from 'svelte'

onMount(()=>{
	console.log('Mounted!')
	return()=>{
		console.log('Destroy in mount')
	} 
//onMount의 return문은 onDestroy와 동일한 역할을 한다 
//onMount에서 return을 사용해 onDestroy를 구현할때는 비동기가 없는 상황에서만 가능하다 
})
onDestroy(()=>{
	console.log('destroy~')
})
</script>

<h1>Something...</h1>

onMount는 something 컴포넌트가 나타난 직후 바로 실행되는 것을 볼 수 있으며 onDestroy는 컴포넌트가 사라지기 직전에 실행되는 것을 볼 수 있습니다.

3. BeforeUpdate & AfterUpdate

  • 반응성을 가지는 데이터가 할당을 통해서 갱신이 되기 전,후에 실행됩니다.
  • 컴포넌트가 연결될 때 또한 실행 됩니다.
  • beforeUpdate 함수는 DOM이 업데이트 되기 직전, 반응성을 가지는 데이터가 갱신이 되기 직전에 작업이 수행됩니다.
  • afterUpdate 함수는 DOM이 데이터와 동기화 된 후(업데이트 직후), 반응성을 가지는 데이터가 갱신이 된 직후에 작업이 수행됩니다.
  • 반응성을 가지는 데이터는 beforeUpdate, afterUpdate 안에 있게 되면 무한 루프에 빠질 수 있기 때문에 내부에 넣지 않는 것이 좋습니다.

이 두 함수를 함께 사용하면 요소의 스크롤 위치를 업데이트하는 동작과 같이 단순히 상태 기반으로 작업하기 어려운 작업을 수행하는데 유용합니다.

공식 문서 예제)

import Eliza from 'elizabot';
import { beforeUpdate, afterUpdate } from 'svelte';

let div;
let autoscroll;

beforeUpdate(() => {
		autoscroll = div && (div.offsetHeight + div.scrollTop) > (div.scrollHeight - 20);
});

afterUpdate(() => {
		if (autoscroll) div.scrollTo(0, div.scrollHeight);
});

const eliza = new Eliza();

let comments = [
	{ author: 'eliza', text: eliza.getInitial() }
];

function handleKeydown(event) {
	if (event.key === 'Enter') {
		const text = event.target.value;
		if (!text) return;

		comments = comments.concat({
			author: 'user',
			text
		});

		event.target.value = '';

		const reply = eliza.transform(text);

		setTimeout(() => {
			comments = comments.concat({
				author: 'eliza',
				text: '...',
				placeholder: true
			});

			setTimeout(() => {
				comments = comments.filter(comment => !comment.placeholder).concat({
					author: 'eliza',
					text: reply
				});
			}, 500 + Math.random() * 500);
		}, 200 + Math.random() * 200);
		}
	}
</script>

beforeUpdate가 컴포넌트가 마운트 되기 전에 먼저 실행되므로 div가 해당 속성을 읽어오기 전에 존재 여부를 체크해야 합니다.

간단한 예제)

import Something from './Something.svelte'

let toggle = false
</script>

<button on:click={()=>{toggle = !toggle}}>
Toggle
</button>

{#if toggle}
<Something />
{/if}
import { onMount, onDestroy, beforeUpdate, afterUpdate } from 'svelte'

let name = 'Something..'
let h1

function moreDot() {
name += '.'
}

beforeUpdate(()=>{
	console.log('Before update!')
	console.log(h1 && h1.innerText)
})

afterUpdate(()=>{
	console.log('After update!')
	console.log(h1 && h1.innerText)
})

onMount(()=>{
	console.log('Mounted!')
	h1 = document.querySelector('h1')
})

onDestroy(()=>{
	console.log('Before destroy~')
})

</script>

<h1 on:click={moreDot}>Something...</h1>

위 예제를 실행해보면 .이 추가되기 직전과 something 컴포넌트가 나타나기 직전 beforeUpdate 실행되고, .이 추가된 직후와 something 컴포넌트가 나타난 후 beforeUpdate가 실행된다.

toggle 버튼을 눌렀을 때 실행 순서

Before Update → Mounted → After update

4. Tick

tick 함수는 promise를 반환하는 비동기 함수이며 데이터가 갱신 되고 나서 화면이 바뀌는 반응성을 가질 때까지 기다려줍니다.

컴포넌트가 처음 초기화 될 때만 아니라 언제든지 호출할 수 있다는 점에서 다른 lifecycle함수와 다릅니다.

pending 중인 상태가 변경되어 DOM에 반영되는 즉시 ( 또는 pending 되지 않은 상태가 변경되는 즉시 ) promise를 반환합니다.

svelte에서 컴포넌트의 상태를 업데이트하면 즉시 DOM에 업데이트 되지는 않습니다. 대신 브라우저가 불필요한 작업을 피하고, 더 효과적으로 일괄 처리하기 위하여 다른 컴포넌트를 포함해서 다른 변경 사항의 적용이 필요한지 확인하기 위하여 microtask 작업까지 기다립니다.

import { tick } from 'svelte';

await tick();
this.selectionStart = selectionStart;
this.selectionEnd = selectionEnd;
  • 전체 예제 보기
    
    	import { tick } from 'svelte';
    
    	let text = `Select some text and hit the tab key to toggle uppercase`;
    
    	async function handleKeydown(event) {
    		if (event.key !== 'Tab') return;
    
    		event.preventDefault();
    
    		const { selectionStart, selectionEnd, value } = this;
    		const selection = value.slice(selectionStart, selectionEnd);
    
    		const replacement = /[a-z]/.test(selection)
    			? selection.toUpperCase()
    			: selection.toLowerCase();
    
    		text = (
    			value.slice(0, selectionStart) +
    			replacement +
    			value.slice(selectionEnd)
    		);
    
    		await tick();
    		this.selectionStart = selectionStart;
    		this.selectionEnd = selectionEnd;
    	}
    </script>
    
    <style>
    	textarea {
    		width: 100%;
    		height: 200px;
    	}
    </style>
    
    <textarea value={text} on:keydown={handleKeydown}></textarea>

간단한 예제)

import {tick} from 'svelte'

let name = 'world'

async function handler(){
	name = 'heejin'
	await tick()
	const h1 = document.querySelector('h1')
	console.log(h1.innerText)
}
</script>

<h1 on:click={handler}>Hello {name}! </h1>

tick()을 사용하지 않았을 경우 handler 함수 안에서 name의 값을 할당해주었어도 함수 내에서 바로 변경되지 않기 때문에 즉각적으로 변경되지 않습니다.. 그러나 tick을 사용하게 되면 name을 할당된 값으로 변경될 때까지 기다려 준 후 다음 코드를 수행하게 됩니다.

tick은 promise를 반환하는 비동기 함수이기 때문에 async await과 함께 사용합니다.

5. Lifecycle의 모듈화

import { lifecycle, delayRender } from './lifecycle.js'

let done = delayRender()
lifecycle()
</script>

{#if $done}
<h1>Hello Lifecycle!</h1>
{/if}
import { onMount, onDestroy, beforeUpdate, afterUpdate } from 'svelte'
import { writable } from 'svelte/store'

export function lifecycle(){
	beforeUpdate(()=>{
		console.log('Before update!')
	})
	
	afterUpdate(()=>{
		console.log('After update!')
	})
	
	onMount(()=>{
		console.log('Mounted!')
	})
	
	onDestroy(()=>{
		console.log('Before destroy!')
	})
}

export function delayRender(delay = 3000){ //인수가 없으면 3000으로 세팅
let render = writable(false)
	onMount(()=>{
		setTimeout(() => {
			console.log(render)
			render.set(true)
		},delay)
	})
}

lifecylce을 다른 js파일로 분리하여 import 해서 사용할 수 있습니다.

profile
FE DEVELOPER 👩🏻‍💻🤍

0개의 댓글