본문 바로가기
개발 기록/front - react

[React] React 컴포넌트 생명주기, 생명주기 메소드

by jeong11 2024. 11. 26.
반응형

※ 체크 리스트  
① React 컴포넌트의 생명주기와 각 단계의 특징을 이해하기  
② 클래스형 컴포넌트의 생명주기 메소드(Lifecycle Method)를 학습하기
③ 함수형 컴포넌트에서 생명주기를 관리하는 방법을 이해하기
 

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

컴포넌트가 생성되고 업데이트되며, 제거되는 과정을 단계별로 설명하는 것을 말한다 
생명주기 메소드는 컴포넌트의 특정 시점에서 실행되는 메소드를 말한다 

 
생성 → 업데이트 → 소멸의 과정
 

1-1. 생명주기 단계의 상태

1) 마운드(Mount)
● 상태(Phase)를 나타냄 
● React 컴포넌트가 생성되어 처음 DOM에 추가되는 과정과 그 시점 전 후의 상태를 통칭함 
● 예시) 컴포넌트가 Mount 되었다
☞ 컴포넌트가 이미 DOM에 렌더링 되었다 
● 주요 메소드 
→ constructor : 컴포넌트의 초기상태를 설정하고 props를 초기화하는 메소드, 컴포넌트가 생성될 때 가장 먼저 호출됨  
→ render : 컴포넌트의 UI를 생성하는 메소드, JSX를 반환함  
→ componentDidMount : 컴포넌트가 DOM에 렌더링된 후 실행되는 메소드로 DOM 조작(스크롤 위치 설정, 타이머 시작), 이벤트 리스너 등록 등에 사용한다 
 
2) 업데이트(Update)
● 컴포넌트의 상태(State)나 속성(Props)이 변경되어 다시 렌더링되는 과정 
● 주요 메소드 
→ shouldComponentUpdate : 성능 최적화를 위해 다시 렌더링할 지 여부를 결정
→ render : UI를 다시 생성
→ componentDidUpdate : 업데이트가 DOM에 반영된 후 호출  
 
3) 언마운트(Unmount)
● 컴포넌트가 DOM에서 제거되는 과정 
● 주요 메소드 
→ componentWillUnmount : 리소스 정리 (타이머 정지, 이벤트 핸들러 제거 등)
 
☆ 짧게 정리 
마운트 : 처음 화면에 나타날 때
업데이트 : 상태/Props 변경으로 UI 갱신
언마운트 : 화면에서 사라질 때 
 
 

Mounting과 Mount의 차이는 뭐지? 

상태와 과정의 표현 차이라고 할 수 있다 
같은 개념을 나타내지만 문맥에 따라 다르게 사용된다 
 
Mounting은 동작/과정을 나타냄 ☞ DOM에 추가되는 행위 
Mount는 결과 또는 상태를 지칭 ☞ 컴포넌트가 DOM에 존재하는 상태 
 
4) Mounting 
● 동작(과정)을 나타냄
●  컴포넌트가 DOM에 추가되기 위해 진행 중인 과정을 강조할 때 사용된다 
● 예시) React가 constructor → render → componentDidMount 메소드를 호출하는 일련의 단계를 의미
 
 

2. 클래스형 컴포넌트에서 생명주기 메소드(Lifecycle Method) 사용법 

Mounting → Updating → Unmounting 단계로 진행

2-1. Mounting 단계 

1) constructor
▷ 컴포넌트를 초기화하는 단계에서 호출한다 
▷ State와 Props를 초기화 
▷ React 클래스형 컴포넌트에서 컴포넌트를 초기화할 때 사용되는 생성자 메소드 예시 

constructor(props) {
	super(props);		// 부모 클래스 초기화 + this 사용 가능
    this.state = { count: 0 };	// 컴포넌트의 초기 상태 설정
}

→ super(props)는 React.Component의 생성자 호출 및 this 초기화 및 활성화 
→ this.state는 컴포넌트의 초기 상태(state)를 정의해주는데 클래스형 컴포넌트는 constructor 내부에 this.state를 직접 설정해줘야 한다 
→ 이 코드로 컴포넌트는 상태를 가지고 부모로부터 전달받은 props에 접근할 수 있게 된다 
 
render  : 준비된 데이터를 기반으로 화면에 그리는 단계 (컴포넌트를 화면에 렌더링)
역할 : UI를 정의하며 JSX를 반환하는 메소드 
☞ render 메소드에서 반환된 내용을 기반으로 React가 DOM을 그린다 
특징 : 반드시 순수함수여야 한다 
☞ 상태(state)와 속성(Props)를 기반으로 UI를 계산하지만, 내부적으로 상태를 변경하거나 부수효과를 발생시키면 안된다 
→ 호출 시점 : 컴포넌트가 렌더링될 때 constructor 호출 후 실행된다  
→ 예시 코드)

render() {
	return <h1>Hello, React!</h1>
}

React는 render() 메소드에서 반환된 JSX를 기반으로 Virtual DOM을 생성하고 실제 DOM에 반영한다 
위 코드의 반환 값은 <h1> 태그 안 쪽의 내용으로 DOM 요소의 구조와 내용을 표현한다 
JSX는 실제 HTML이 아니라 JavaScript 코드로 변환되는데 React가 이것을 읽어서 DOM으로 변환하여 브라우저에 표시한다

 

// return <h1>Hello, React!</h1>는 
// 내부적으로 다음과 같은 JavaScript 코드로 변환한다

return React.createElement('h1', null, 'Hello, React!');

코드를 실행하면 컴포넌트가 렌더링되고 브라우저 DOM에 HTML이 추가된다

<h1>Hello, React!</h1>

 
 
2) componentDidMount 
▷ React 컴포넌트가 DOM에 추가된 직후에 호출되는 메소드로 Mounting 단계의 마지막 메소드  
데이터 요청(API 호출)이나 이벤트 등록(스크롤, 이벤트 리스너 등록) 등에 사용된다 
▷메소드 구현 : 

componentDidMount() {
	console.log("Component mounted!");
}

해당 메소드는 컴포넌트가 DOM에 완전히 렌더링된 후 자동으로 호출된다 
☞ 컴포넌트가 처음 화면에 표시될 때 한 번 실행된다 
☞ 컴포넌트가 성공적으로 마운트되었을 경우 콘솔에 Component mounted! 메시지를 출력한다 
 
 

2-2. Updating 단계

1) shouldComponentUpdate
▷컴포넌트가 업데이트될지, 렌더링 여부를 결정하는 생명주기 메소드 
▷성능 최적화에 유용함 
▷메소드 구현 : 

shouldComponentUpdate(nextProps. nextState) {
	// 새로운 상태(nextState.count)와 현재 상태(this.state.count)가 다른 경우 렌더링을 허용함
    return nextState.count !== this.state.count;	
}

nextProps, nextState : 컴포넌트가 업데이트 될 때 새로운 props와 state를 인자로 받는다 
새로운 상태(nextState.count)와 현재 상태(this.state.count)를 비교하여 상태가 변경되었는지 확인하고,  
다를 때만 렌더링을 허용한다
→ true 반환하는 경우 : 상태가 변경되었으므로 다시 렌더링
→false 반환하는 경우 :상태가 동일하므로 렌더링을 건너띈다 → 성능 최적화
 
 
2) componentDidUpdate
▷컴포넌트가 업데이트된 후 호출 
▷DOM 작업, API 호출 등에 사용함 
▷메소드 구현 : 

componentDidUpdate(prevProps, prevState) {
	console.log("Component updated!");
}

이 전의 props(prevProps)와 state(prevState)를 비교하거나 후속 작업을 처리하는데 사용된다 
컴포넌트가 렌더링되고 나면 componentDidUpdate가 호출되고,
이 때 이전의 props, state와 비교하거나 추가작업을 할 수가 있다 
 
 

2-3. Unmounting 단계 

1) componentWilUnmount
▷컴포넌트가 DOM에서 제거되기 직전에 호출
▷정리(clean-up) 작업에 사용
▷메소드 구현 

componentWillUnmount() {
	console.log("Component will unmount!");
}

컴포넌트가 DOM에서 제거되기 직전에 호출되는 생명주기 메소드 
 
 

3. 함수형 컴포넌트에서 생명주기(useEffect Hook) 관리 

함수형 컴포넌트는 생명주기 메소드를 사용하지 않고 useEffectHook을 사용한다 

3-1.  useEffect의 역할

① 클래스 컴포넌트에서의 componentDidMount 같은 역할 : 
▷컴포넌트가 처음 화면에 렌더링 될 때 실행된다 
예시) 데이터 가져오기, 구독 시작 등 
▷useEffect에서 구현

useEffect(() => {
	console.log("Component mounted");
    // 데이터 가져오기 예
    fetchData();
    
}, []);	//빈 배열 : 의존성 없음 => 처음 한 번만 실행

→ 두 번째 매개변수로 빈 배열([ ])을 전달하면 componentDidMount 와 같은 동작을 한다 
 
② 클래스 컴포넌트에서의 componentDidUpdate 같은 역할 : 
▷컴포넌트가 업데이트 될 때마다 실행된다 
예시) 특정 상태나 props가 변경될 때 실행할 작업 

useEffect(() => {
	console.log("Component updated");
	// 상태가 변할 때 실행되는 작업 예시
}, [stateVariable]);	// stateVariable 값이 변경될 때 실행

→ 의존성 배열에 특정 상태나 props를 넣으면 해당 값이 변경될 때마다 실행된다 
 
③ 클래스 컴포넌트에서의 componentWillUnmount 같은 역할 : 
▷컴포넌트가 화면에서 사라지기 전에 실행된다 
예시) 구독해제, 리소스 정리 등

useEffect(() => {
	console.log("Component mounted");
	
    return () => {
    	console.log("Component will unmount");
        // 구독 해제 등 정리 작업
        cleanupResources();
    };
}, []);	//빈 배열 : 정리 작업은 컴포넌트가 사라질 때 실행

→ useEffect의 콜백 함수에서 정리(clean-up)함수를 반환하면 
componentWillUnmount와 같은 동작을 한다 
 
 

3-2. useEffect 사용

1) 기본 useEffect 사용

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

function Counter() {
	const [count, setCount] = useState(0);
    
    useEffect(() => {
    	console.log("Component mounted or updated!")
    });
    
    return (
    	<div>
        	<p>Count: {count}</p>
            <button onClick={() => setCount(count+1)}>Increase</button>
        </div>
    );
}

export default Counter;

→ 버튼 클릭 시 상태가 변경되어 리렌더링되어 그 때마다 useEffect가 실행된다 
● count는 상태 변수로 초기값이 0, setCount는 상태를 업데이트하기 위한 함수 
● useEffect(() => { ... }) : 의존성 배열이 없으므로 컴포넌트가 렌더링될 때마다 실행된다 
초기 마운트와 업데이트 모두에서 실행되어 콘솔에 Component mounted or updated! 메시지가 출력된다
● 출력 : 초기 렌더링 시 "Component mounted or updated!" 출력 
버튼 클릭 시 상태가 변경되어 "Component mounted or updated!" 다시 출력 
 
 
2) Clean-up 작업

import React, {useState, useEffect} from "react"

function Counter() {
	useEffect(() => {
    	const interval = setInterval(() => {
        	console.log("Tick");
    	}, 1000);
        
        return () => {
 	    //clean-up 작업 진행 
	    clearInterval(interval);
            console.log("Component will unmount!");
        };
	}, []);
    
}

export default Counter;

→ 컴포넌트가 마운트되며, 타이머를 설정, 언마운트 시 타이머 정리 및 메시지 출력 
● 의존성 배열이 [ ] 빈배열 이므로, useEffect는 초기 마운트 시에만 실행된다 
● setInterval을 사용해 1초마다 "Tick"을 콘솔에 출력한다 
● useEffect 콜백 함수의 반환값으로 정리(clean-up) 함수를 작성한다 
● 정리 함수는 컴포넌트가 언마운트될 때 실행되고 clearInterval(interval)을 호출해 타이머를 정리한다 
 
 

4. 함수형 컴포넌트 실습

4-1. 목표 

▷ componentDidMount를 사용하여 컴포넌트가 로드될 때 콘솔 메시지를 출력하세요 
▷ useEffect를 사용해 타이머를 구현하고 컴포넌트가 제거될 때 타이머를 정리하세요 
 

4-2. 함수형 컴포넌트 코드

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

function Timer() {
	const [seconds, setSeconds] = useState(0);
	
	useEffect(() => {
    	console.log("componentDidMount와 동일한 역할!");
        
		const interval = setInterval(() => {
			setSeconds((prev) => prev + 1);		// 1초마다 seconds 상태 업데이트
		}, 1000);
        
    	return () => clearInterval(interval);	// componentWillUnmount와 동일한 역할
   
   }, []);	//빈 배열로 의존성 없음 => 마운트/언마운트 시 실행
    
	return <p>Seconds : {seconds}</p>;
}

export default Timer;

▷ 코드의 동작 : 
① useState로 상태 관리 : 
- seconds는 타이머가 경과한  시간을 초 단위로 저장한다 
- setSeconds를 사용해 상태를 업데이트한다 
② useEffect의 역할 : 
- 초기 마운트 시 : 
console.log를 찍어 확인하고 setInterval을 사용해 1초마다 seconds 값을 증가한다 
- 언마운트 시 : 
setInterval을 사용해 1초마다 seconds 값을 증가시킨다 
③ 타이머 동작 : 
매 1초마다 setSeconds를 호출해 상태를 갱신하고 화면에 경과 시간을 표시한다 
 
 

4-3. 코드 실행하기

▷ componentDidMount를 사용하여 컴포넌트가 로드될 때 콘솔 메시지를 출력하세요
 
1) react 프로젝트 생성 

npx create-react-app my-timer-app

 
2) src 폴더 안에 Timer.js 파일을 생성
> src/Timer.js

타이머 코드

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

function Timer() {
	const [seconds, setSeconds] = useState(0);
    
    useEffect(() => {
    	console.log("componentDidMount와 동일한 역할!");
    	
        const interval = setInterval(() => {
        	setSeconds((prev) => prev +1);	//1초마다 seconds 상태 업데이트
        }, 1000);
        
        return () => clearInterval(interval);	//componentWillUnmount와 동일한 역할
    }, []);	//빈 배열로 의존성 없음 => 마운트/언마운트 시 실행
    
    return <p>Seconds: {seconds}</p>;
}

export default Timer;

 
3) App.js 수정 
> src/App.js

App 코드

import React from "react";
import Timer from "./Timer";

function App() {
	return (
    	<div className="App">
        	<h1>React Time</h1>
            <Timer />
        </div>
    )
}

export default App;

 
4) 프로젝트 실행 

npm start

 
5) 결과 
콘솔 화면 > 메시지 확인
브라우저 화면 

timer 표시와 콘솔 출력

 
 

4-4. 컴포넌트 제거(언마운트) 실행

▷ useEffect를 사용해 타이머를 구현하고 컴포넌트가 제거될 때 타이머를 정리하세요
react- router를 사용해 페이지간 이동 구현 / 상태를 변경하여 컴포넌트를 조건부 렌더링 중 선택 1
1) React Router 설치 

npm install react-router-dom

 
2) 코드 수정 
① App.js 수정
> src/App.js

App.js 코드를 수정

import React from "react";
import { BrowserRouter as Router, Route, Link, Routes } from "react-router-dom";
import Timer from "./Timer";

// 타이머 정리가 제대로 작동하는지 확인
function App() {
	return(
    	<Router>
        	<nav>
            	<Link to="/">Home</Link> | <Link to="/timer">Timer</Link>
            </nav>
        	<Routes>
            	<Route path="/" element={<h1>Home Page</h1>} />
                <Route path="/timer" element={<Timer />} />
            </Routes>
    	</Router>
    );
}

export default App;

 
② Timer.js 수정

콘솔 로그 추가

return () => { } 안에 console.log를 하나 더 찍어준다 
 
 
3) 실행화면 

실행 홈화면

 
아! "componentDidMount"콘솔 출력이 두 번 찍히는 이유는 <React.strictMode>때문이다 

→ 브라우에서 타이머 실행시 콘솔에 "componentDidMount와 동일한 역할!" 출력 
 
헷갈리니까 콘솔을 한 번만 출력해주겠다 
>src/index.js

수정 위치는 index.js

 <React.StrictMode>를 주석처리해준다 
 

한 번만 나온다

→ 한 번 출력
 

제거되면서 새로 콘솔 메시지 출력

→ Timer 화면에서 Home 링크를 클릭하여 Timer 컴포넌트를 제거한다 
 

 


※ 정리 
① 생명주기 메소드(Lifecycle Method)와 생명주기(useEffect Hook)의 차이는 무엇인가? 
→ 클래스형 컴포넌트는 고정된 생명주기 메소드를 사용하지만 
함수형에서는 useEffect로 모든 생명주기를 대체하여 유연하게 동작 제어 가능 
 
② componentWillUnmount가 중요한 경우는 어떤 경우일까? 
→ componentWillUnmount 또는 useEffect의 cleanup이 필요한 경우는 컴포넌트가 제거될 때 정리 작업을 수행해야 할 경우이다. 정리 작업이 필요한 경우 사용하여 메모리 누수 및 성능 저하를 방지함  

반응형