본문 바로가기
Web과 프로그래밍 언어/JavaScript

[SVELTE]Lifecycle - onMount, onDestory, beforeUpdate and afterUpdate, tick

by cosmicgy 2023. 3. 8.

 

Lifecycle

 

lifecycle 즉, 생명주기!
라이프사이클 함수는 컴포넌트가 화면에 마운트 되기 전과 되고 난 후의 상태에 어떤 작업을 처리할 때 사용하는 함수

onMount : 컴포넌트가 돔에 마운트 되면 실행
onDestroy : 컴포넌트가 해제된 후 실행
berforeUpdate : 컴포넌트가 마운트 되기 전 실행
afterUpdate: 컴포넌트가 마운트 된 후 실행
tick : 컴포넌트 변경이 완료되면 실행 (라이프사이클과 성격이 조금 다르다)

  1. 순서 : beforeUpdate -> onMount -> afterUpdate -> onDestroy
  • 부모와 자식 컴포넌트 사이에 라이프사이클의 경우 는 부모의 beforeUpdate 이후 자식 컴포넌트의 모든 라이프사이클 즉 afterUpdate 이후에 부모의 onMount 가 발생한다
  • import { 라이프 사이클 함수 이름 } from 'svelte'로 라이프 사이클 함수를 가져올 수 있다.

 

onMount

: 컴포넌트가 처음으로 DOM에 렌더링 될 때 실행되는 함수로 네트워크를 통해 데이터를 가져와야 할 경우 onMount를 사용하는 것이 좋다.

 

<script>
    import { onMount } from 'svelte';

    let photos = [];

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

<h1>Photo album</h1>

<div class="photos">
    {#each photos as photo}
        <figure>
            <img src={photo.thumbnailUrl} alt={photo.title}>
            <figcaption>{photo.title}</figcaption>
        </figure>
    {:else}
        <!-- this block renders when photos.length === 0 -->
        <p>loading...</p>
    {/each}
</div>

<style>
    .photos {
        width: 100%;
        display: grid;
        grid-template-columns: repeat(5, 1fr);
        grid-gap: 8px;
    }

    figure, img {
        width: 100%;
        margin: 0;
    }
</style>

 

onDestroy

onDestroy는 컴포넌트가 제거되었을 때 호출된다. 컴포넌트가 할당받은 자원을 해제할 때 사용되는 라이프 사이클 함수이다.

<!-- App.svelte -->
<script>
    import Timer from './Timer.svelte';

    let open = true;
    let seconds = 0;

    const toggle = () => (open = !open);
    const handleTick = () => (seconds += 1);
</script>

<div>
    <button on:click={toggle}>{open ? 'Close' : 'Open'} Timer</button>
    <p>
        The Timer component has been open for
        {seconds} {seconds === 1 ? 'second' : 'seconds'}
    </p>
    {#if open}
    <Timer callback={handleTick} />
    {/if}
</div>

<!-- Timer.svelte -->
<script>
    import { onInterval } from './utils.js';

    export let callback;
    export let interval = 1000;

    onInterval(callback, interval);
</script>

<p>
    This component executes a callback every
    {interval} millisecond{interval === 1 ? '' : 's'}
</p>

<style>
    p {
        border: 1px solid blue;
        padding: 5px;
    }
</style>

 

// utils.js
import { onDestroy } from 'svelte';

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

    onDestroy(() => {
        clearInterval(interval);
    });
}
  • 라이프 사이클 모듈화

svelte의 라이프사이클 함수는 어디에서 사용할 지는 중요하지 않다. 왜냐하면 다른 파일에 작성하고 컴포넌트에 import하여 사용할 수 있기 대문에 기능별로 모듈화 하기가 쉽다.

 

beforeUpdate & afterUpdate

beforeUpdate는 DOM이 업데이트되기 직전에 호출되는 라이프 사이클 함수이다.
afterUpdate는 DOM이 업데이트된 직후에 호출되는 라이프 사이클 함수이다.

 

<script>
    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>

<style>
    .chat {
        display: flex;
        flex-direction: column;
        height: 100%;
        max-width: 320px;
    }

    .scrollable {
        flex: 1 1 auto;
        border-top: 1px solid #eee;
        margin: 0 0 0.5em 0;
        overflow-y: auto;
    }

    article {
        margin: 0.5em 0;
    }

    .user {
        text-align: right;
    }

    span {
        padding: 0.5em 1em;
        display: inline-block;
    }

    .eliza span {
        background-color: #eee;
        border-radius: 1em 1em 1em 0;
    }

    .user span {
        background-color: #0074D9;
        color: white;
        border-radius: 1em 1em 0 1em;
        word-break: break-all;
    }
</style>

<div class="chat">
    <h1>Eliza</h1>

    <div class="scrollable" bind:this={div}>
        {#each comments as comment}
            <article class={comment.author}>
                <span>{comment.text}</span>
            </article>
        {/each}
    </div>

    <input on:keydown={handleKeydown}>
</div>

 

beforeUpdate 라이프 사이클 함수는 마운트 되기 전에도 호출되기 때문에 DOM에 접근할 때 DOM이 존재하는지 체크하는 로직이 필요하다. beforeUpdate에서 div를 사용하기 전에 div && ...으로 유효성 체크한다.

 

tick

tick은 다른 라이프 사이클 함수들과 다르게 언제든 사용할 수 있다. 언제든 사용할 수 있다는 뜻은 마운트 된 후에만 호출되는 onMount, 제거된 후에만 호출되는 onDestroy 와는 달리 언제든 호출된다는 뜻이다. tick 함수는 변경된 내용이 있다면 변경된 내용이 DOM에 반영된 직후에, 변경된 내용이 없다면 바로 호출된다.

Svelte는 상태가 변경되면 즉시 업데이트되지 않는다. 정해진 시간 동안에 변경된 내용들을 한꺼번에 업데이트하는데, 이런 동작은 불필요한 작업을 피할 수 있으며 브라우저가 효율적으로 작업을 일괄 처리할 수 있게 해준다.

 

<script>
    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)
        );

        // this has no effect, because the DOM hasn't updated yet
        this.selectionStart = selectionStart;
        this.selectionEnd = selectionEnd;
    }
</script>

<style>
    textarea {
        width: 100%;
        height: 200px;
    }
</style>

<textarea value={text} on:keydown={handleKeydown}></textarea>

<textarea> 의 값이 변하기 때문에 커서가 끝으로 가고 선택 영역이 지워지는 문제가 발생한다.

 

<script>
    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>