Frontend/React.js

[React] useEffect, useLayoutEffect - sideEffect 훅

okojin 2024. 8. 13. 15:58

💡 SideEffect란?

컴포넌트 그리기와는 직접적인 관계가 없는 처리
ex) 화면을 그린 DOM 수동 변경, 로그 출력, 타이머 설정, 데이터 취득 등

useEffect

  • sideEffect를 실행하기 위해 사용하는 훅
  • useEffect()를 사용하면, props나 state가 업데이트되고, 다시 그리기가 완료된 후 처리가 실행
  • 의존성 배열을 지정해서, 특정 데이터가 변화할 때만 처리하도록 설정 가능

다음 코드는 useEffect를 사용한 예제입니다.
Clock 컴포넌트는 현재 시각을 표시하고, 1초마다 시간이 업데이트되며, 드롭다운 메뉴를 선택해서 시각 표기를 변경할 수 있습니다. 시각 표기 설정은 로컬 스토리지에 저장됩니다. 리로드 뒤에는 로컬 스토리지에 저장된 데이터를 읽어, 가장 마지막에 선택한 값을 표시합니다.

    import { useState, useEffect } from 'react';

    // 타이머가 호출되는 주기를 1초로 한다
    const UPDATE_CYCLE = 1000

    // 로컬 스토리지에서 사용하는 키
    const KEY_VALUE = 'KEY_LOCALE';

    enum Locale {
        US = 'en-US',
        KR = 'ko-KR',
    }

    const getLocaleFromString = (text: string) => {
        switch (text) {
            case Locale.US:
                return Locale.US;
            case Locale.KR:
                return Locale.KR;
            default:
                return Locale.US;
        }
    }

    const Clock = () => {
        const [timestamp, setTimestamp] = useState(new Date());
        const [locale, setLocale] = useState(Locale.US);

        // 타이머를 설정하기 위한 sideEffect
        useEffect(() => {
            // 타이머 셋
            const timer = setInterval(() => {
                setTimestamp(new Date());
            }, UPDATE_CYCLE);

            // 클린업 함수를 전달하고, 언마운트 시에 타이머를 해제한다.
            return () => {
                clearInterval(timer)
            }
            // 초기 그리기 시에만 실행한다.
        }, []);

        // 로컬 스토리지에서 값을 로딩
        useEffect(() => {
            const savedLocale = localStorage.getItem(KEY_VALUE);
            if (savedLocale !== null) {
                setLocale(getLocaleFromString(savedLocale));
            }
        }, []);

        // 로케일이 바뀌었을 때 로컬 스토리지에 값을 저장
        useEffect(() => {
            localStorage.setItem(KEY_LOCALE, locale);
            // 의존 배열에 로케일을 전달하고, 로케일이 변할 때마다 실행
        }, [locale]);

        return (
            <div>
                <p>
                    <span id="current-time-label">현재 시각</span>
                    <span>:{timestamp.toLocaleString(locale)</span>
                    <select
                        value={locale}
                        onChange={(e) => setLocale(getLocaleFromString(e.target.value))}>
                        <option vlaue="en-US">en-US</option>
                        <option vlaue="ko-KR">ko-KR</option>
                    </select>
                </p>
            </div>
        );
    }

useLayoutEffect

  • useEffect와 마찬가지로 sideEffect를 실행하기 위한 훅이지만, 실행 시점이 다름
  • useEffect와 달리 DOM이 업데이트된 후, 화면에 실제로 그려지기 전에 실행

다음 코드는 useLatyoutEffect를 사용한 예제입니다.
위에서 사용한 useEffect의 예제에서 2번째 useEffect를 수정하였습니다.

import { useState, useEffect } from 'react';

// 타이머가 호출되는 주기를 1초로 한다
const UPDATE_CYCLE = 1000

// 로컬 스토리지에서 사용하는 키
const KEY_VALUE = 'KEY_LOCALE';

enum Locale {
    US = 'en-US',
    KR = 'ko-KR',
}

const getLocaleFromString = (text: string) => {
    switch (text) {
        case Locale.US:
            return Locale.US;
        case Locale.KR:
            return Locale.KR;
        default:
            return Locale.US;
    }
}

const Clock = () => {
    const [timestamp, setTimestamp] = useState(new Date());
    const [locale, setLocale] = useState(Locale.US);

    // 타이머를 설정하기 위한 sideEffect
    useEffect(() => {
        // 타이머 셋
        const timer = setInterval(() => {
            setTimestamp(new Date());
        }, UPDATE_CYCLE);

        // 클린업 함수를 전달하고, 언마운트 시에 타이머를 해제한다.
        return () => {
            clearInterval(timer)
        }
        // 초기 그리기 시에만 실행한다.
    }, []);

    // 리렌더링 할 때마다 localstorage의 값을 읽기 전에 초기값인 US가 표시되어 살짝
    // 이상하게 보임,
    // useLayoutEffect를 사용하면 초기 화면에 반영되기 전에 localstorage로부터
    // 데이터를 읽으므로 이런 현상을 없앨 수 있음
    // 하지만 ㅕuseLayoutEffect로 실행하는 처리는 동기적으로 실행되므로, 무거운 처리를
    // 실행하면 화면 그리기가 지연되므로 주의
    useLayoutEffect(() => {
        const savedLocale = localStorage.getItem(KEY_VALUE);
        if (savedLocale !== null) {
            setLocale(getLocaleFromString(savedLocale));
        }
    }, []);

    // 로케일이 바뀌었을 때 로컬 스토리지에 값을 저장
    useEffect(() => {
        localStorage.setItem(KEY_LOCALE, locale);
        // 의존 배열에 로케일을 전달하고, 로케일이 변할 때마다 실행
    }, [locale]);

    return (
        <div>
            <p>
                <span id="current-time-label">현재 시각</span>
                <span>:{timestamp.toLocaleString(locale)</span>
                <select
                    value={locale}
                    onChange={(e) => setLocale(getLocaleFromString(e.target.value))}>
                    <option vlaue="en-US">en-US</option>
                    <option vlaue="ko-KR">ko-KR</option>
                </select>
            </p>
        </div>
    );
}

참고: 타입스크립트, 리액트, Next.js로 배우는 실전 웹 애플리케이션 개발