091

[JS/React] JSX(4): Lifecycle, Hook 본문

Programming Language/JavaScript&TypeScript

[JS/React] JSX(4): Lifecycle, Hook

공구일 2026. 4. 12. 04:02
728x90

5. Lifecycle(생명주기) & Hook(훅)
- 생명주기(Lifecycle)이란 리액트 컴포넌트가 생성되고 사라지는 전 과정을 의미합니다.

 

- 훅(Hook)은 함수 컴포넌트에서 React State와 생명주기 기능을 연동할 수 있게 해주는 함수입니다.

-> 훅의 이름은 모두 use로 시작해야하며, 원하는 시점에 함수가 실행되도록 만드는 함수가 훅입니다.

const [변수명, set함수명] = useState(초기값);
const [count, setCount] = useState(0);

useState(상태관리): 컴포넌트 내에서 변하는 데이터를 관리할 때 사용 

-> 상태가 업데이트되면 리액트는 해당 컴포넌트를 다시 렌더링하여 화면에 갱신하며, set 함수가 비동기적으로 동작합니다. 

useEffect(이펙트 함수, 의존성 배열);
useEffect(() => { console.log("마운트됨"); }, []);

useEffect(사이드 이펙트 처리): 서버 데이터 수신, DOM 직접 변경 등 렌더링 이후에 실행되어야하는 작업에 사용

-> API 호출, 타이머 설정, DOM 직접 조작 등 '외부 시스템'과 통신할 때 주로 사용합니다. 이전에 useState의 set함수의 비동기 방식 때문에 console.log(...) 호출에서 이전 값이 나온 것을 이 useEffect와 함께 사용하면 렌더링 완료 후 최신 값을 확인할 수 있습니다.

 import {useState,useEffect} from "react";
 useEffect(()=>{ console.log(count); },[count]);

-> Counter 컴포넌트에 이렇게 두 줄을 추가하고 나면 최신값으로 console.log가 출력되는 것을 알 수 있습니다.

내부 출력중이던 console.log(...)는 주석처리하고 난뒤의 결과입니다.

-> 클래스 컴포넌트에서 제공하는 생명주기 함수인 componentDidMount(), componentDidUpdate(), componentWillUnmount()와 동일한 기능을 하나로 통합해서 제공합니다.

-> 뒷정리(Cleanup) 함수란 useEffect 안에서 return으로 반환하는 함수입니다. 타이머, 이벤트 리스너 등을 해제해 메모리 누수를 방지합니다.

 

+) 자동 카운터 실습

import { useState, useEffect } from "react";

export default function AutoCounter(props) {
    const [count, setCount] = useState(0);
    const [isRunning, setIsRunning] = useState(false);

    // useEffect(() => {
    //     let timer;
    //     if (isRunning) {
    //         timer = setTimeout(function () {
    //             setCount(count + 1);
    //         }, 1000);
    //     }
    //     return () => clearTimeout(timer);
    // }, [count, isRunning]);

    useEffect(() => {
        let timer;
        if (isRunning) {
            timer = setInterval(() => {
                setCount(prev=>prev+1);
            }, 1000);
        }
        return () => clearInterval(timer);
    }, [isRunning]);


    return (
        <div>
            <p>{count}</p>
            <button onClick={() => setIsRunning(!isRunning)}>
                {isRunning ? "STOP" : "START"}
            </button>
        </div>
    )
}

-> setInterval(...)과 setTimeout(...): setInterval은 지정된 시간 간격마다 코드를 무한히 반복해서 실행합니다. 그에 반해 setTimeout은 지정된 시간이 지난 후에 코드를 단 한번만 실행하고 종료합니다. 그래서 setTimeout을 쓸 때는 의존성배열에 count를 넣어 변화할 때마다 실행될 수 있게 해줘야합니다. 그리고 setTimeout은 보통의 경우 한번 실행되니까 clearTimeout를 쓰는 것은 특정 상황에서의 선택이지만, setInterval은 쓰면 반드시 clearInterval을 통해 메모리 누수를 방지해야합니다.

-> function(){}과 ()=>{}: 두개는 this 바인딩에서 가장 큰 차이를 가집니다. function(){}은 자기 자신만의 this를 가지지만, 화살표 함수는 this가 없습니다. 이러한 this문제로부터 자유롭기 위해 React에서는 화살표 함수를 사용합니다.

-> setCount(prev=>prev+1): set 함수 안에 값이 아닌 함수가 들어오면 무조건 그 함수의 첫번째 매개변수에 리액트 앤진 내부 메모리가 기억하고 있는 가장 최신 state 값을 꽂아주도록 프로그래밍되어있기 떄문에 prev를 넣어도 count 값 연산이 됩니다.

const memoizedValue = useMemo(() => {
  return computeExpensiveValue(a, b);
}, [a,b);

useMemo(연산 결과 재사용): 비용이 많이 드는 복잡한 계산값을 기억해두었다가 재사용

-> 의존성 배열의 값이 변하지 않으면 이전에 계산한 값을 그대로 반환하여 성능을 최적화합니다. 이러한 성능 최적화 기법을 메모이제이션(Memoization)이라고 합니다.

+) 느리게 작동하는 숫자 제곱 함수

import { useState, useMemo } from 'react';

function BigSquare() {
    const [number, setNumber] = useState(0); // 제곱 계산 입력값
    const [count, setCount] = useState(0); // “+1”버튼 클릭시 증가값

    const slowSquare = (num) => { // 느리게 계산되는 제곱 함수 (예제용으로 일부러 지연)
        console.log('계산 중...');
        let result = 0;
        for (let i = 0; i < 2500000000; i++) {
            result = num * num;
        }
        return result;
    };

    // useMemo로 계산 결과를 메모이제이션
    const squared = useMemo(() => slowSquare(number), [number]);
    
    return (
        <div>
            <h2>느린 제곱 계산기</h2>
            <input
                type="number"
                value={number}
                onChange={(e) => setNumber(Number(e.target.value))}
            />
            <p>제곱 결과: {squared}</p>
            <p>{count}</p>
            <button onClick={() => setCount(count + 1)}>+1</button>
        </div>
    );
}

export default BigSquare;

-> 느리게 작동하는 제곱함수와 카운터를 동시에 보유하고 있습니다. +1 버튼을 누를 때는 useMemo로 제곱 계산이 수행되지 않아 불필요한 재렌더링 작업이 발생하지않아 지연 시간이 없습니다. 

const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

useCallback(함수 자체를 재사용): 함수를 메모이제이션하는 훅으로, useMemo가 값을 저장한다면, useCallback은 함수 자체를 저장합니다. 의존성 배열의 값이 바뀔 때만 함수를 새로 만들고 그렇지 않으면 기존 함수를 재사용합니다.

-> useEffect 의존성 배열에 함수를 넣어야할 때는 반드시 useCallback으로 감싸야합니다. 왜냐면 자바스크립트는 객체나 함수는 내용이 완벽이 같아도 새로 만들게 되면 메모리 주소의 차이로 인해 다르게 인식하기 때문에 다시 컴포넌트를 그리는 리렌더링을 할 때 같은 함수(원래의 A와 새로 만들어진 B)가 새로운 메모리 주소에 쓰여져서 A에서 B로 바뀌었다고 생각되어 API 서버 호출을 다시 실행하며 서버가 공격당하게 됩니다. 

=> 이때 useCallback을 사용하면, 의존성 배열을 확인 후 키워드의 변경이 없다면 B를 만들지 말고 A를 그대로 재사용하라고 합니다.

+) 검색결과

const fetchData = useCallback(() => {
    console.log("데이터 불러오는 중...");
    setTimeout(() => {
        setData(`”${keyword}”에 대한 검색 결과`);
    }, 1000);
}, [keyword]); // keyword가 바뀔 때만 함수 재생성

// keyword 변경 시 자동 호출
useEffect(() => {
    console.log("useEffect()");
    if (keyword) fetchData();
}, [fetchData]);

-> fetchData() 함수는 useCallback 훅을 통해 함수 참조가 고정되었으며, keyword 변경 시에만 새로운 함수를 생성합니다.

 

useRef(DOM 접근 및 값 유지): 렌더링 없이 값을 저장하는 훅으로, 렌더링을 유발하지 않는 변수 저장소이자, DOM 요소에 직접 접근할 때 사용합니다. 브라우저의 DOM을 가르키는 기능과 값을 바꿔도 화면을 다시 안 그리는 두가지의 기능이 메인입니다.

-> useRef을 통해 참조하고 있는 값에 접근하기위해서는 .current를 사용해줘야하는데 그 이유는 항상 { current: 저장값 }의 형태로 useRef가 제공하기 때문입니다. 

+) input 태그에 포커싱

import { useState, useRef } from "react";

export default function TextInputWithFocusButton(props) {
    const inputElem = useRef(null);
    const onButtonClick = () => {
        inputElem.current.focus();
    }
    return (
        <div>
            <input ref={inputElem}></input>
            <button onClick={onButtonClick}>
                Focus the input
            </button>
        </div>
    );
}

 

- 만약에 직접 훅을 만들어서 사용하고 싶다면, 커스텀 훅으로 use를 시작으로 만들어 사용할 수 있습니다.

import { useState } from "react";

function useCounter(initialValue){
    const [count, setCount] = useState(initialValue);

    const increaseCount = () => setCount((count) => count+1);
    const decreaseCount = () => setCount((count) => count-1);

    return [count, increaseCount, decreaseCount];
}

export default useCounter;
import { useState, useEffect } from "react";
import useCounter from "../hooks/useCounter";

const MAX_CAPACITY = 10;

function Accomodate(props){
    const [isFull, setIsFull] = useState(false);
    const [count,increaseCount,decreseCount] = useCounter(0);

    useEffect(()=>{
        console.log("----------------");
        console.log("useEffect()가 호출되었습니다.");
        console.log(`isFull: ${isFull}`);
    });

    useEffect(()=>{
        setIsFull(count >= MAX_CAPACITY);
        console.log(`현재 인원 수: ${count}명`);
    },[count]);

    return(
        <div>
            <h3>총 {count}명 수용했습니다.</h3>
            
            {count < MAX_CAPACITY &&
            <button onClick={increaseCount}>입장</button>}
            {count > 0 &&
            <button onClick={decreseCount}>퇴장</button>}

            {isFull && <p style={{color:'red'}}>정원이 꽉찼습니다.</p>}
        </div>
    )

}

export default Accomodate;

-> JSX 태그는 자바스크립트에서 언제나 true로 취급하기 때문에 반대쪽 조건이 사실이면 반환됩니다.

728x90