React에서의 Ref와 컴포넌트 생명 주기



1. 컴포넌트 생명 주기(Lifecycle)란?

컴포넌트 생명 주기(Lifecycle)는 React 컴포넌트가 생성되고, 업데이트되고, 제거되는 과정에서 발생하는 단계를 의미합니다. 각 단계마다 특정 메서드나 훅을 통해 작업을 수행할 수 있습니다.

  • 마운트(Mount): 컴포넌트가 처음으로 DOM(Document Object Model)에 삽입될 때 발생합니다. 여기서 DOM은 브라우저가 웹 페이지를 이해하고 구조화하기 위해 사용하는 문서 구조입니다. 컴포넌트가 마운트된다는 것은, DOM 트리에 컴포넌트가 처음으로 추가되어 화면에 보이게 됩니다.

  • 업데이트(Update): 컴포넌트가 리랜더링될 때 발생합니다. 리랜더링은 컴포넌트의 상태(state)나 부모로부터 전달받은 속성(props)이 변경될 때 일어나며, 이를 통해 화면에 보여지는 내용이 새롭게 갱신됩니다.

  • 언마운트(Unmount): 컴포넌트가 DOM에서 제거될 때 발생합니다. 이는 컴포넌트가 더 이상 필요하지 않아 브라우저 화면에서 사라지는 시점을 의미합니다. 이때, 컴포넌트가 사용했던 자원을 해제하거나, 타이머나 네트워크 요청을 정리하는 작업이 필요할 수 있습니다.

이러한 단계에서 원하는 작업을 수행하려면

  • 클래스형 컴포넌트에서는 생명 주기 메서드를
  • 함수형 컴포넌트에서는 useEffect 훅을 사용합니다.


2. 단축 평가(Short-circuit Evaluation)와 조건부 랜더링

단축 평가는 논리 연산에서 좌항의 값에 따라 우항의 평가를 건너뛸 수 있는 특성을 의미합니다.

  • 논리곱(&&): 좌항이 false이면, 전체 표현식이 false가 되기 때문에 우항은 평가되지 않습니다. 그러나 좌항이 true이면, 표현식의 결과는 우항의 값에 따라 달라지므로 우항이 평가되고 그 결과가 반환됩니다.

    • true && "Hello"의 경우, 좌항이 true이므로 우항 "Hello"가 평가되고, 이 값이 반환됩니다.
    • false && "Hello"의 경우, 좌항이 false이므로 우항은 평가되지 않고, false가 반환됩니다.

    • && 연산자는 두 값이 모두 true일 때만 true를 반환합니다. 따라서 좌항이 false일 경우, 더 이상 결과가 true가 될 수 없으므로 우항을 평가할 필요가 없어서 건너뛰고, 좌항인 false를 그대로 반환합니다. 반면, 좌항이 true일 경우, 결과는 우항에 의해 결정되므로 우항을 평가하고, 그 결과가 반환됩니다.
  • 논리합(||): 좌항이 true이면, 전체 표현식이 true가 되기 때문에 우항은 평가되지 않습니다. 좌항이 false이면, 표현식의 결과는 우항의 값에 따라 달라지므로 우항이 평가되고 그 결과가 반환됩니다.

    • true || "Hello"의 경우, 좌항이 true이므로 우항은 평가되지 않고, true가 반환됩니다.
    • false || "Hello"의 경우, 좌항이 false이므로 우항 "Hello"가 평가되고, 이 값이 반환됩니다.

    • || 연산자는 두 값 중 하나만 true이면 true를 반환합니다. 따라서 좌항이 true일 경우, 전체 표현식이 true가 되므로 우항을 평가할 필요가 없고, 좌항인 true가 그대로 반환됩니다. 반면, 좌항이 false일 경우, 결과는 우항에 의해 결정되므로 우항을 평가하고, 그 결과가 반환됩니다.


3. 클래스형 컴포넌트에서의 생명 주기와 조건부 랜더링

1) LifeCycleClass 컴포넌트

import React, { Component } from 'react';
import LifeCycleClassChild from './LifeCycleClassChild';

export default class LifeCycleClass extends Component {
    constructor(props) {
        super(props);
        this.state = {
            number: 0,
            visible: true, // 자식 컴포넌트의 표시 여부를 제어
        };
    }

    // number 상태를 1 증가시키는 함수
    changeNumberState = () => this.setState({ number: this.state.number + 1 });

    // 자식 컴포넌트의 표시 여부를 토글하는 함수
    changeVisibleState = () => this.setState({ visible: !this.state.visible });

    render() {
        return (
            <>
                <button onClick={this.changeNumberState}>Plus</button>
                <button onClick={this.changeVisibleState}>On / Off</button>

                {/* visible 상태에 따라 자식 컴포넌트를 랜더링 */}
                {this.state.visible && (
                    <LifeCycleClassChild number={this.state.number} />
                )}
            </>
        );
    }
}
  • Plus 버튼: number 상태를 1씩 증가시킵니다.
  • On / Off 버튼: 자식 컴포넌트의 표시 여부를 제어합니다. 자식 컴포넌트가 화면에 나타나거나 사라지도록 하는 것을 의미합니다. visible이라는 상태를 통해 이루어지며, visibletrue일 때는 자식 컴포넌트가 화면에 보이고, false일 때는 자식 컴포넌트가 화면에서 사라지게 됩니다.


React에서의 조건부 랜더링(단축평가 사용)

클래스형 컴포넌트에서 && 연산자를 사용한 조건부 랜더링을 사용합니다. {this.state.visible && <LifeCycleClassChild number={this.state.number} />}의 경우, this.state.visibletrue일 때만 <LifeCycleClassChild /> 컴포넌트가 랜더링됩니다.

this.state.visibletrue이면, && 연산자의 특성에 따라 우항의 컴포넌트가 평가됩니다. 이때 평가된 컴포넌트는 랜더링 결과로 반환됩니다. 만약 this.state.visiblefalse라면, && 연산자의 좌항이 false이기 때문에 우항의 컴포넌트는 평가되지 않고, 전체 표현식은 false로 평가되어 아무것도 랜더링되지 않습니다.


2) LifeCycleClassChild 컴포넌트

import React, { Component } from 'react';

export default class LifeCycleClassChild extends Component {

    // 컴포넌트가 처음 DOM에 삽입될 때 실행
    componentDidMount() {
        console.log('컴포넌트 마운트!! 🔵');
    }

    // 컴포넌트가 업데이트될 때 실행
    componentDidUpdate() {
        console.log('컴포넌트 업데이트!! 🟢');
    }

    // 컴포넌트가 DOM에서 제거될 때 실행
    componentWillUnmount() {
        console.log('컴포넌트 언마운트!! 🔴');
    }

    render() {
        return (
            <div>
                자식 컴포넌트
                <p>현재 Number 

 {this.props.number} 입니다.</p>
            </div>
        );
    }
}
  • componentDidMount: 컴포넌트가 처음으로 마운트(Mount)될 때, 즉 DOM에 삽입될 때 호출됩니다. 이 시점에서 초기 데이터 로드, 외부 API 호출 등의 작업을 할 수 있습니다.

  • componentDidUpdate: 컴포넌트가 업데이트(Update)될 때, 즉 상태나 props가 변경되어 리랜더링될 때 호출됩니다. 이 메서드는 이전 상태 또는 props와 비교하여 조건부로 작업을 수행할 때 유용합니다.

  • componentWillUnmount: 컴포넌트가 언마운트(Unmount)될 때, 즉 DOM에서 제거될 때 호출됩니다. 이 시점에서는 타이머 정리, 네트워크 요청 취소 등의 정리 작업을 수행할 수 있습니다.


4. 함수형 컴포넌트에서의 생명 주기와 조건부 랜더링

1) LifeCycleFunc 컴포넌트

import React, { useState } from 'react';
import LifeCycleFuncChild from './LifeCycleFuncChild';

export default function LifeCycleFunc() {
    const [number, setNumber] = useState(0);
    const [visible, setVisible] = useState(true); // 자식 컴포넌트의 표시 여부를 제어

    // number 상태를 1 증가시키는 함수
    const changeNumber = () => {
        setNumber(number + 1);
    };

    // 자식 컴포넌트의 표시 여부를 토글하는 함수
    const changeVisible = () => {
        setVisible(!visible);
    };

    return (
        <div style=>
            <button onClick={changeNumber}>Plus</button>
            <button onClick={changeVisible}>On / Off</button>

            {/* visible 상태에 따라 자식 컴포넌트를 랜더링 */}
            {visible && <LifeCycleFuncChild number={number} />}
        </div>
    );
}
  • Plus 버튼: number 상태를 1씩 증가시킵니다.
  • On / Off 버튼: visible 상태가 true일 때만 자식 컴포넌트가 랜더링되고, false일 때는 사라집니다.


React에서의 조건부 랜더링(단축평가 사용)

함수형 컴포넌트에서도 && 연산자를 사용한 조건부 랜더링을 자주 사용합니다. {visible && <LifeCycleFuncChild number={number} />}의 경우, visibletrue일 때만 <LifeCycleFuncChild /> 컴포넌트가 랜더링됩니다.

visibletrue이면, && 연산자의 특성에 따라 우항의 컴포넌트가 평가됩니다. 이때 평가된 컴포넌트는 랜더링 결과로 반환됩니다. 만약 visiblefalse라면, && 연산자의 좌항이 false이기 때문에 우항의 컴포넌트는 평가되지 않고, 전체 표현식은 false로 평가되어 아무것도 랜더링되지 않습니다.


2) LifeCycleFuncChild 컴포넌트

import React, { useEffect, useState } from 'react';

export default function LifeCycleFuncChild({ number }) {
    const [input, setInput] = useState('');

    // 컴포넌트가 처음 DOM에 삽입될 때 실행
    useEffect(() => {
        console.log('컴포넌트 마운트!! 🔵');
    }, []);

    // 컴포넌트가 마운트될 때와 언마운트될 때 실행
    useEffect(() => {
        console.log('컴포넌트 마운트!! 🔵');
        return () => {
            console.log('컴포넌트 언마운트!! 🔴');
        };
    }, []);

    // 상태나 props가 변경될 때마다 실행
    useEffect(() => {
        console.log('컴포넌트 마운트!! 🔵 or 업데이트  🟢');
    });

    // 특정 상태(input)가 변경될 때 실행
    useEffect(() => {
        console.log(
            '마운트 🔵 or input 상태가 변경됨에 따라 컴포넌트 업데이트 🟠'
        );
    }, [input]);

    return (
        <div style=>
            자식 컴포넌트
            <div>현재 number 값은 {number} 입니다.</div>
            <input
                type="text"
                value={input}
                onChange={(e) => setInput(e.target.value)}
            />
        </div>
    );
}

(1) useState

  • useState는 React에서 함수형 컴포넌트의 상태를 관리하는 훅입니다. 여기서는 input이라는 상태를 선언하고, 이 상태는 사용자가 입력 필드에 입력한 값을 저장합니다. useState는 초기값으로 빈 문자열 ''을 설정하고, 상태를 업데이트하는 함수인 setInput을 반환합니다. 이 함수는 사용자가 입력 필드에 값을 입력할 때 호출되어 상태를 업데이트합니다.


(2) useEffect

  • useEffect는 함수형 컴포넌트에서 사이드 이펙트를 처리하는 훅입니다. 여기서는 네 가지 useEffect 훅이 사용되고 있으며, 각각 컴포넌트의 생명 주기 동안 특정 시점에 실행됩니다.

    • 빈 배열을 의존성으로 가지는 useEffect: 컴포넌트가 처음 마운트될 때 한 번 실행됩니다. 이때 "컴포넌트 마운트!! 🔵"라는 메시지를 콘솔에 출력합니다.

    • 언마운트 시 실행되는 useEffect: 컴포넌트가 마운트될 때 실행되며, 반환하는 함수는 컴포넌트가 언마운트될 때 호출됩니다. 이 훅은 "컴포넌트 마운트!! 🔵""컴포넌트 언마운트!! 🔴" 메시지를 각각 출력합니다.

    • 의존성 배열이 없는 useEffect: 의존성 배열이 없기 때문에 컴포넌트가 리랜더링될 때마다 실행됩니다. "컴포넌트 마운트!! 🔵 or 업데이트 🟢"라는 메시지를 콘솔에 출력합니다.

    • 특정 상태를 의존성으로 가지는 useEffect: input 상태가 변경될 때마다 실행됩니다. "마운트 🔵 or input 상태가 변경됨에 따라 컴포넌트 업데이트 🟠"라는 메시지를 콘솔에 출력합니다.


3) 의존성 배열과 useEffect

useEffect 훅은 React 함수형 컴포넌트에서 생명 주기 메서드의 역할을 합니다. 이 훅은 컴포넌트가 랜더링될 때마다 특정 작업을 수행할 수 있게 해주며, 이 작업이 언제 실행될지를 제어하기 위해 의존성 배열(Dependency Array)을 사용합니다.

(1) 의존성 배열의 역할

  • 빈 배열 []: 의존성 배열이 빈 배열이면 useEffect는 컴포넌트가 처음 마운트될 때 한 번만 실행됩니다. 마운트 이후에는 다시 실행되지 않습니다. 이 방법은 컴포넌트가 처음 생성될 때 한 번만 실행하고자 하는 초기화 작업에 유용합니다.

  • 의존성 배열 생략: 의존성 배열을 생략하면, useEffect는 컴포넌트가 리랜더링될 때마다 실행됩니다. 이는 상태나 props가 변경될 때마다 특정 작업을 수행해야 할 때 사용됩니다.

  • 특정 값이 포함된 배열: 특정 상태나 props를 의존성 배열에 포함시키면, 해당 값이 변경될 때마다 useEffect가 실행됩니다. 예를 들어, 배열에 [count]가 있다면 count 값이 변경될 때마다 useEffect가 실행됩니다.


(2) 빈 배열일 때 useEffect가 한 번만 실행되는 이유 (특징)

빈 배열이 의존성 배열로 전달되면 useEffect 훅은 컴포넌트가 마운트될 때 한 번만 실행되고, 이후에는 다시 실행되지 않습니다. 이는 React가 의존성 배열을 통해 useEffect의 실행 여부를 결정하기 때문입니다.

  • 컴포넌트 마운트 시점: 컴포넌트가 처음 DOM에 삽입될 때, React는 useEffect를 실행합니다. 이 시점에서 의존성 배열이 빈 배열인 경우, React는 의존할 값이 없다고 판단합니다.

  • 의존성 배열 검사: 의존성 배열이 비어 있으므로, React는 더 이상 추적할 상태나 props가 없다고 인식합니다. 따라서 이후의 업데이트나 리랜더링 시점에서는 useEffect가 실행되지 않습니다.

  • 업데이트 시점: 컴포넌트가 리랜더링될 때, React는 의존성 배열에 변화가 없음을 확인하고 useEffect를 다시 실행하지 않습니다.