- Published on
클래스 컴포넌트와 함수형 컴포넌트의 생명 주기 비교
- Authors
- Name
- Hyo814
클래스 컴포넌트와 함수형 컴포넌트의 생명 주기 비교
https://nikgraf.github.io/react-hooks/
https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
클래스 컴포넌트와 함수형 컴포넌트의 생명 주기 비교
1. 클래스 컴포넌트의 생명주기 메서드
클래스 컴포넌트는 컴포넌트의 특정 시점에서 동작할 수 있도록 다양한 생명주기 메서드를 제공합니다. 이 메서드는 크게 세 가지 단계로 나눌 수 있습니다.
- 마운트 단계: 컴포넌트가 처음 DOM에 삽입될 때.
constructor()
: 컴포넌트가 생성될 때 호출됩니다.componentDidMount()
: 컴포넌트가 처음 렌더링된 후에 호출됩니다.
- 업데이트 단계: 상태(state) 또는 props가 변경될 때.
componentDidUpdate()
: 컴포넌트가 업데이트된 후에 호출됩니다.
- 언마운트 단계: 컴포넌트가 DOM에서 제거될 때.
componentWillUnmount()
: 컴포넌트가 제거되기 직전에 호출됩니다.
useEffect
2. 함수형 컴포넌트의 함수형 컴포넌트는 useEffect
훅을 사용하여 클래스 컴포넌트의 생명주기 메서드와 같은 동작을 처리합니다. useEffect
는 기본적으로 두 가지 역할을 수행할 수 있습니다.
- 마운트와 업데이트 처리:
useEffect(() => {
// 마운트 및 업데이트 시 실행되는 코드
});
```
의존성 배열을 비워두면 마운트 시에만 실행됩니다.
```jsx
useEffect(() => {
// 마운트 시 한 번만 실행되는 코드
}, []);
```
2. **언마운트 처리**:
컴포넌트가 언마운트될 때 정리(clean-up) 작업을 위해 `return` 값을 통해 정리 함수를 반환할 수 있습니다.
```jsx
useEffect(() => {
// 마운트 및 업데이트 시 실행되는 코드
return () => {
// 언마운트 시 실행되는 코드
};
}, []);
```
### 3. **클래스 컴포넌트 vs 함수형 컴포넌트의 생명주기 비교**
| 기능 | 클래스 컴포넌트 | 함수형 컴포넌트 (`useEffect`) |
| --- | --- | --- |
| 마운트 시 실행 | `componentDidMount()` | `useEffect(() => {}, []);` |
| 업데이트 시 실행 | `componentDidUpdate()` | `useEffect(() => {});` |
| 언마운트 시 정리 작업 | `componentWillUnmount()` | `useEffect(() => { return () => {}; }, []);` |
`useEffect`는 클래스 컴포넌트의 여러 생명 주기 메서드를 하나의 훅으로 처리할 수 있어 더 간결한 코드 작성을 가능하게 합니다.
---
### `useLayoutEffect`와 `useEffect` 비교
### 1. **`useEffect`**
`useEffect`는 컴포넌트가 렌더링된 후, 브라우저가 화면을 그린 다음에 비동기적으로 실행됩니다. 비동기적으로 실행되기 때문에, 화면이 먼저 사용자에게 보이고 나서 사이드 이펙트가 발생합니다. 이 방식은 일반적인 데이터 패칭이나, 비동기 작업에 적합합니다.
```jsx
useEffect(() => {
// 비동기 데이터 패칭
fetchData();
}, []);
useLayoutEffect
2. useLayoutEffect
는 useEffect
와는 다르게, 브라우저가 화면을 그리기 전에 동기적으로 실행됩니다. 즉, DOM이 변경되기 전에 코드를 실행하고 싶은 경우에 사용됩니다. 이로 인해 UI에 직접적인 영향을 미치는 작업에 주로 사용됩니다.
useLayoutEffect(() => {
// DOM이 렌더되기 전에 동기적으로 실행
const rect = element.getBoundingClientRect()
// DOM 요소의 위치에 기반한 레이아웃 조정
}, [])
3. 차이점 정리
항목 | useEffect | useLayoutEffect |
---|---|---|
실행 시점 | 렌더링 이후, 화면 그린 후 실행 | 렌더링 이후, 화면을 그리기 전에 실행 |
주요 사용 시나리오 | 비동기 데이터 패칭, 이벤트 등록/해제 | 레이아웃 변경, DOM 측정 및 수정 작업 |
성능 영향 | 화면이 그려진 후 실행되므로 성능에 적은 영향을 미침 | 동기적으로 실행되어 렌더링 성능에 약간의 영향을 줄 수 있음 |
4. 언제 무엇을 사용할까?
useEffect
: 대부분의 경우 비동기 작업을 처리하기 위한 일반적인 훅입니다. 화면이 렌더링된 후에 실행되므로 성능 최적화에도 도움이 됩니다.useLayoutEffect
: 화면에 영향을 미치는 레이아웃 관련 작업이 필요할 때 사용합니다. 예를 들어, DOM의 크기나 위치를 읽고 수정해야 하는 경우 적합합니다.
componentDidMount()
와 함수형 컴포넌트의 useEffect
는 어떻게 다른가요?
클래스 컴포넌트의 componentDidMount()
는 클래스 컴포넌트에서 컴포넌트가 처음 렌더링된 후 한 번 호출되는 메서드입니다. 반면, 함수형 컴포넌트에서는 useEffect
를 사용해 비슷한 역할을 수행할 수 있으며, 두 번째 인자인 의존성 배열에 빈 배열([]
)을 전달하면 마운트 시에만 한 번 실행되도록 설정할 수 있습니다.
주요 차이점:
- 클래스 컴포넌트:
componentDidMount()
는 생명주기 메서드로서 컴포넌트의 마운트 시점에만 사용됩니다. - 함수형 컴포넌트:
useEffect
는 마운트, 업데이트, 언마운트 모두에 사용 가능하며, 의존성 배열을 통해 마운트 시점에만 실행되도록 조절할 수 있습니다.
// 클래스 컴포넌트
class MyComponent extends React.Component {
componentDidMount() {
// 마운트 시 한 번 실행
console.log('마운트 완료')
}
}
// 함수형 컴포넌트
useEffect(() => {
console.log('마운트 완료')
}, [])
useLayoutEffect
를 사용하는 경우는 언제인가요?
useLayoutEffect
는 화면이 브라우저에 그려지기 전에 실행되므로, DOM을 조작해야 하거나 레이아웃을 측정할 필요가 있을 때 사용합니다. 예를 들어, 컴포넌트가 렌더링된 후 특정 DOM 요소의 크기나 위치를 읽고 그 값을 기반으로 레이아웃을 변경해야 하는 경우 적합합니다.
useLayoutEffect(() => {
const rect = elementRef.current.getBoundingClientRect()
// DOM 조작이 필요한 작업
}, [])
useEffect
와의 차이점:useEffect
는 비동기적으로 실행되어 화면이 먼저 그려진 후 실행되지만,useLayoutEffect
는 화면이 그려지기 전에 동기적으로 실행되기 때문에, 레이아웃 변경 작업에 더 적합합니다.
useEffect
를 사용할 때 언마운트 시 정리(clean-up) 작업은 어떻게 하나요?
useEffect
에서 언마운트 시 정리 작업은 return
값으로 정리(clean-up) 함수를 반환하는 방식으로 처리합니다. 이 정리 함수는 컴포넌트가 언마운트될 때 호출되며, 이벤트 리스너 제거나 타이머 정리 등의 작업을 수행하는 데 유용합니다.
useEffect(() => {
const timer = setInterval(() => {
console.log('타이머 실행 중')
}, 1000)
// 언마운트 시 정리 작업
return () => {
clearInterval(timer)
console.log('타이머 종료')
}
}, [])
componentWillUnmount()
의 역할은 무엇인가요?
클래스 컴포넌트에서 componentWillUnmount()
는 클래스 컴포넌트가 DOM에서 제거되기 직전에 호출되는 생명주기 메서드입니다. 주로 **정리 작업(clean-up)**을 수행하는 데 사용됩니다. 예를 들어, 타이머를 정리하거나 이벤트 리스너를 제거하여 메모리 누수를 방지할 수 있습니다.
class MyComponent extends React.Component {
componentDidMount() {
this.timer = setInterval(() => {
console.log('타이머 실행 중')
}, 1000)
}
componentWillUnmount() {
// 타이머 제거
clearInterval(this.timer)
console.log('타이머 정리됨')
}
render() {
return <div>타이머 컴포넌트</div>
}
}
componentWillUnmount()
는 컴포넌트가 제거되기 전에 마지막으로 호출되어, 리소스를 정리하거나 상태를 리셋하는 역할을 합니다.
useLayoutEffect
는 언제 성능에 부정적인 영향을 줄 수 있나요?
useLayoutEffect
는 동기적으로 실행되므로, DOM이 업데이트된 후 화면이 그려지기 전에 실행됩니다. 따라서 렌더링이 지연될 수 있습니다. 만약 useLayoutEffect
내에서 복잡한 계산이나 시간이 오래 걸리는 작업을 수행하면, 화면이 늦게 그려지기 때문에 사용자 경험에 부정적인 영향을 미칠 수 있습니다.
예를 들어, DOM 크기를 측정하고 이를 바탕으로 다시 렌더링하는 작업이 빈번하게 발생하면 성능이 저하될 수 있습니다. 이 때문에 가능하면 레이아웃에 직접적인 영향을 미치는 경우에만 useLayoutEffect
를 사용하고, 그렇지 않은 경우에는 useEffect
를 사용하는 것이 좋습니다.
useEffect
에 특정 값을 넣으면 어떻게 동작하나요?
의존성 배열을 빈 배열로 두지 않고 useEffect
의 두 번째 인자로 제공되는 의존성 배열에 특정 값을 넣으면, 그 값이 변할 때마다 useEffect
가 다시 실행됩니다. 이를 통해 특정 상태나 props가 변경될 때마다 사이드 이펙트를 발생시키는 로직을 구현할 수 있습니다.
useEffect(() => {
console.log('카운트 값이 변경되었습니다:', count)
// 클린업 함수 (언마운트 또는 count 변경 시 실행됨)
return () => {
console.log('카운트가 변경되기 전에 실행되는 정리 작업')
}
}, [count])
위 코드에서는 count
값이 변경될 때마다 useEffect
가 다시 실행되며, 이전 count
값에 대한 정리 작업이 먼저 실행된 후에 새로운 사이드 이펙트가 발생합니다.
componentDidUpdate()
는 어떤 상황에서 사용되나요?
componentDidUpdate()
는 클래스 컴포넌트에서 컴포넌트가 업데이트된 직후에 호출되는 생명주기 메서드입니다. 주로 props나 state가 변경된 후에 어떤 동작을 수행하고 싶을 때 사용합니다. 예를 들어, 데이터 업데이트 후 특정 로직을 실행하거나, 네트워크 요청을 다시 보낼 때 유용합니다.
class MyComponent extends React.Component {
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
console.log('카운트 값이 변경되었습니다.')
}
}
render() {
return <div>{this.state.count}</div>
}
}
여기서는 count
값이 이전과 달라졌을 때만 추가적인 작업을 수행하도록 설정할 수 있습니다. 주로 업데이트된 후의 상태를 감지하고 이에 따른 작업을 처리하는 경우에 사용됩니다.
useEffect
에서 여러 상태 변수를 의존성 배열에 넣으면 어떻게 동작하나요?
useEffect
의 의존성 배열에 여러 상태 변수를 넣으면, 배열 안의 값들 중 하나라도 변경될 때마다 useEffect
가 실행됩니다. 즉, 배열에 나열된 모든 변수의 변경을 감지하고 그때마다 새로운 사이드 이펙트를 발생시킵니다.
useEffect(() => {
console.log('카운트나 상태가 변경되었습니다:', count, status)
}, [count, status])
위 코드에서 count
또는 status
중 하나라도 변경되면 useEffect
가 다시 실행됩니다. 만약 두 값 모두 변하지 않았다면, useEffect
는 실행되지 않습니다. 이를 통해 컴포넌트 내에서 여러 상태 값이 변경되는 시점을 각각 감지할 수 있습니다.
useLayoutEffect
와 useEffect
를 함께 사용해야 하는 상황이 있을까요?
useLayoutEffect
와 useEffect
를 함께 사용해야 하는 상황은 특정 DOM 조작이나 레이아웃 변경이 필요한 작업과, 그 이후 비동기적인 작업이 연결될 때 발생할 수 있습니다.
예를 들어:
- *
useLayoutEffect
*는 DOM의 레이아웃을 측정하거나 변경하기 위한 작업에 사용됩니다. - *
useEffect
*는 DOM이 그려진 후 비동기 작업이나 사이드 이펙트를 처리하기 위해 사용됩니다.
useLayoutEffect(() => {
// DOM 요소의 크기 측정
const rect = elementRef.current.getBoundingClientRect()
console.log('DOM 레이아웃 측정:', rect)
}, [])
useEffect(() => {
// 비동기 데이터 요청
fetchData().then((data) => {
console.log('데이터 패칭 완료')
})
}, [])
componentWillReceiveProps()
는 어떤 용도로 사용되나요?
componentWillReceiveProps()
는 클래스 컴포넌트에서 새로운 props를 받을 때 호출되는 생명주기 메서드입니다. 주로 부모 컴포넌트로부터 props가 변경되었을 때 그 변화를 감지하고, 이에 따른 상태를 업데이트하거나 특정 작업을 처리하는 데 사용됩니다.
하지만 이 메서드는 React 16.3 이후부터 사용이 권장되지 않으며, 대신 getDerivedStateFromProps()
나 componentDidUpdate()
로 대체되었습니다.
class MyComponent extends React.Component {
componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.props.value) {
// 새로운 props에 따라 상태를 업데이트
this.setState({ updatedValue: nextProps.value })
}
}
}
이 메서드는 새로운 props를 기반으로 상태를 동기화할 때 사용되었지만, 상태와 props의 불필요한 중복을 초래할 수 있어 권장되지 않습니다. 현재는 React에서 더 안전한 생명주기 메서드가 제안되어 있습니다.
useEffect
에서 클린업 함수가 실행되는 시점은 언제인가요?
useEffect
에서 클린업 함수는 다음 두 가지 경우에 실행됩니다:
- 컴포넌트가 언마운트될 때:
- 컴포넌트가 DOM에서 제거될 때 클린업 함수가 호출됩니다. 이는 주로 이벤트 리스너 제거, 타이머 정리, 네트워크 요청 취소 등을 처리하는 데 사용됩니다.
- 의존성 배열에 있는 값이 변경될 때:
useEffect
가 실행되기 전에 이전 사이클의 클린업 함수가 먼저 호출됩니다. 즉, 새로운 사이드 이펙트가 발생하기 전에 기존 작업이 정리됩니다.
useEffect(() => {
const timer = setInterval(() => {
console.log('타이머 실행 중')
}, 1000)
return () => {
// 클린업 함수: 타이머 정리
clearInterval(timer)
console.log('타이머 종료')
}
}, [count])
위 코드에서는 count
값이 변경될 때마다 기존 타이머가 정리되고, 새로운 타이머가 설정됩니다. 또한, 컴포넌트가 언마운트될 때도 클린업 함수가 호출됩니다.
useLayoutEffect
를 너무 많이 사용할 때 발생할 수 있는 문제는 무엇인가요?
useLayoutEffect
는 동기적으로 실행되기 때문에, 화면을 그리기 전에 모든 작업이 완료될 때까지 렌더링을 중단시킵니다. 따라서 이를 과도하게 사용하면 렌더링 성능에 부정적인 영향을 미칠 수 있습니다.
문제가 발생할 수 있는 상황:
- 긴 작업이 포함된 경우:
useLayoutEffect
에서 DOM 조작, 복잡한 계산, 또는 시간이 오래 걸리는 작업을 수행하면 렌더링이 지연되고, 사용자에게 화면이 늦게 표시됩니다. - 다수의
useLayoutEffect
사용: 여러 개의useLayoutEffect
훅을 사용하는 경우, 각각의 작업이 렌더링 전에 동기적으로 처리되어 성능이 떨어질 수 있습니다.
이러한 이유로, DOM 조작이 필요하지 않은 작업은 useEffect
에서 처리하는 것이 성능 측면에서 유리합니다.
getDerivedStateFromProps()
는 어떤 역할을 하나요?
getDerivedStateFromProps()
는 클래스 컴포넌트에서 제공되는 정적(static) 생명주기 메서드로, props의 변화를 기반으로 컴포넌트의 state를 업데이트할 때 사용됩니다. 이 메서드는 컴포넌트가 리렌더링되기 전에 호출되어, props에 따라 내부 state를 업데이트할 수 있습니다.
class MyComponent extends React.Component {
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.value !== prevState.value) {
// 새로운 props에 따라 state를 업데이트
return { value: nextProps.value }
}
// 변경 사항이 없으면 null 반환
return null
}
}
이 메서드는 props가 변경될 때 state와 동기화하고 싶을 때 사용되며, React 16.3 이후 도입된 메서드입니다. 불필요한 상태 업데이트를 방지할 수 있으며, 안전하게 props에서 state로 값이 내려올 때 사용할 수 있습니다.
useEffect
에서 의존성 배열이 없는 경우에는 어떻게 동작하나요?
useEffect
에서 의존성 배열이 없는 경우에는, 그 훅은 매번 렌더링될 때마다 실행됩니다. 이는 마운트 시뿐만 아니라 컴포넌트가 업데이트될 때도 매번 실행되므로, 모든 렌더링 후에 실행된다고 볼 수 있습니다.
useEffect(() => {
console.log('컴포넌트가 렌더링될 때마다 실행됩니다')
})
의존성 배열이 없는 useEffect
는 컴포넌트의 렌더링 주기에 맞춰 계속해서 실행되므로, 비효율적인 반복 실행을 유발할 수 있습니다. 의존성 배열을 지정하지 않으면 무조건적인 실행이 발생하기 때문에 주의가 필요합니다.
useLayoutEffect
를 사용하지 않고 useEffect
로 대체 가능한 상황은 무엇인가요?
useLayoutEffect
대신 useEffect
로 대체할 수 있는 상황은 화면이 그려진 후에 발생해야 할 작업이 필요할 때입니다. 즉, DOM이 완전히 렌더된 후에 실행되어도 되는 비동기 작업이나, 사용자 인터페이스에 직접적인 영향을 미치지 않는 작업은 useEffect
로 처리하는 것이 적합합니다.
대체 가능한 상황:
- 비동기 데이터 패칭
- 브라우저 이벤트 리스너 추가
- 타이머 설정
- 콘솔 로그나 네트워크 요청 등 화면에 영향을 주지 않는 작업
useEffect(() => {
// 화면이 그려진 후에 비동기 작업을 수행
fetchData().then((data) => {
console.log('데이터 패칭 완료')
})
}, [])
useLayoutEffect
가 필요한 경우는 주로 DOM 크기 측정이나 레이아웃 관련 작업처럼 화면이 렌더링되기 전에 완료되어야 하는 동기 작업입니다. 그렇지 않은 경우에는 useEffect
를 사용하는 것이 성능 면에서 더 효율적입니다.
getDerivedStateFromProps()
대신 componentDidUpdate()
를 사용하는 경우는 언제인가요?
getDerivedStateFromProps()
는 props가 변경될 때 state를 업데이트하기 위한 목적으로 사용되지만, componentDidUpdate()
는 업데이트 이후의 작업을 처리하는 데 사용됩니다. 따라서 getDerivedStateFromProps()
는 주로 렌더링 전에 props와 state를 동기화할 필요가 있을 때 사용되며, componentDidUpdate()
는 렌더링 이후에 발생하는 사이드 이펙트(예: 네트워크 요청, DOM 업데이트, 또는 로그 기록) 처리를 할 때 더 적합합니다.
componentDidUpdate()
는 다음과 같은 상황에서 사용됩니다:
- 렌더링 이후에 발생하는 비동기 작업을 수행할 때 (예: 데이터 패칭).
- props나 state가 변경된 후에 실행해야 할 작업이 있을 때.
class MyComponent extends React.Component {
componentDidUpdate(prevProps) {
if (this.props.value !== prevProps.value) {
// props가 변경된 후에 추가 작업 수행
console.log('props가 업데이트되었습니다.')
}
}
render() {
return <div>{this.props.value}</div>
}
}
getDerivedStateFromProps()
는 props를 기반으로 state를 업데이트할 때 적합하지만, 만약 렌더링 이후에 비동기 작업이나 추가적인 로직을 처리하고 싶다면 componentDidUpdate()
가 더 나은 선택입니다.
useEffect
에서 의존성 배열에 값이 있으면 어떤 상황에서 실행되나요?
useEffect
에서 의존성 배열에 값을 넣으면, 그 값이 변경될 때마다 useEffect
가 실행됩니다. 즉, 의존성 배열에 포함된 상태나 props가 변화할 때마다 사이드 이펙트가 발생합니다.
useEffect(() => {
console.log('count 값이 변경되었습니다:', count)
}, [count])
위 예시에서는 count
값이 변경될 때마다 useEffect
가 다시 실행됩니다. 의존성 배열에 여러 값이 포함될 경우, 배열 안의 어떤 값이라도 변경되면 useEffect
가 실행됩니다. 이 방식은 특정 상태나 props가 변경될 때만 사이드 이펙트를 발생시키고, 불필요한 실행을 방지할 수 있습니다.
useLayoutEffect
가 필수적인 상황에서 사용할 때 주의해야 할 점은 무엇인가요?
useLayoutEffect
는 화면이 렌더링되기 전에 동기적으로 실행되기 때문에, 레이아웃 변경이나 DOM 측정과 같은 작업에서 꼭 필요할 수 있습니다. 하지만 사용 시 다음과 같은 사항에 주의해야 합니다:
- 성능 저하 가능성:
useLayoutEffect
는 렌더링이 완료되기 전에 실행되기 때문에, 너무 복잡한 작업을 수행하면 렌더링이 지연될 수 있습니다. 이는 UI가 늦게 표시되는 현상을 초래할 수 있으므로, 간단한 DOM 조작 작업에만 사용하는 것이 좋습니다.
- 사용 빈도:
- 여러 곳에서
useLayoutEffect
를 사용하면 성능에 부정적인 영향을 줄 수 있습니다. 필요하지 않은 경우에는useEffect
로 대체하여 성능을 최적화하는 것이 좋습니다.
- DOM 조작:
useLayoutEffect
는 DOM을 즉시 조작할 수 있는 시점에서 실행되므로, DOM의 크기나 위치를 기반으로 한 레이아웃 작업(예: 애니메이션, 스크롤 위치 설정)에 적합합니다. 이 경우, 화면이 깜빡이거나 레이아웃이 깨지지 않도록 주의해야 합니다.
useLayoutEffect(() => {
const element = document.getElementById('myElement')
const rect = element.getBoundingClientRect()
console.log('DOM 요소의 위치:', rect)
}, [])
useLayoutEffect
는 성능에 영향을 줄 수 있으므로, 화면 렌더링이 지연되지 않도록 꼭 필요한 상황에서만 사용해야 하며, 복잡한 작업은 피하는 것이 좋습니다.