※ 체크 리스트
① 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
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) 결과
콘솔 화면 > 메시지 확인
브라우저 화면
4-4. 컴포넌트 제거(언마운트) 실행
▷ useEffect를 사용해 타이머를 구현하고 컴포넌트가 제거될 때 타이머를 정리하세요
☞ react- router를 사용해 페이지간 이동 구현 / 상태를 변경하여 컴포넌트를 조건부 렌더링 중 선택 1
1) React Router 설치
npm install react-router-dom
2) 코드 수정
① App.js 수정
> src/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
<React.StrictMode>를 주석처리해준다
→ 한 번 출력
→ Timer 화면에서 Home 링크를 클릭하여 Timer 컴포넌트를 제거한다
※ 정리
① 생명주기 메소드(Lifecycle Method)와 생명주기(useEffect Hook)의 차이는 무엇인가?
→ 클래스형 컴포넌트는 고정된 생명주기 메소드를 사용하지만
함수형에서는 useEffect로 모든 생명주기를 대체하여 유연하게 동작 제어 가능
② componentWillUnmount가 중요한 경우는 어떤 경우일까?
→ componentWillUnmount 또는 useEffect의 cleanup이 필요한 경우는 컴포넌트가 제거될 때 정리 작업을 수행해야 할 경우이다. 정리 작업이 필요한 경우 사용하여 메모리 누수 및 성능 저하를 방지함
'개발 기록 > front - react' 카테고리의 다른 글
React Query로 서버 상태 관리하기 : 데이터 패칭과 캐싱 (0) | 2024.12.17 |
---|---|
[React] 함수형 컴포넌트 (0) | 2024.11.27 |
[react] React 컴포넌트와 Props, State (1) | 2024.11.25 |
[JavaScript] 화살표 함수 = (arrow function) => {} (2) | 2024.11.20 |
React의 UseEffect는 왜 두 번씩 실행되어 빡치게 만드는가 (1) | 2024.11.16 |