Published on

클래스 컴포넌트와 함수형 컴포넌트의 생명 주기 비교

Authors
  • avatar
    Name
    Hyo814
    Twitter

클래스 컴포넌트와 함수형 컴포넌트의 생명 주기 비교

https://nikgraf.github.io/react-hooks/

https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

클래스 컴포넌트와 함수형 컴포넌트의 생명 주기 비교

1. 클래스 컴포넌트의 생명주기 메서드

클래스 컴포넌트는 컴포넌트의 특정 시점에서 동작할 수 있도록 다양한 생명주기 메서드를 제공합니다. 이 메서드는 크게 세 가지 단계로 나눌 수 있습니다.

  1. 마운트 단계: 컴포넌트가 처음 DOM에 삽입될 때.
  • constructor(): 컴포넌트가 생성될 때 호출됩니다.
  • componentDidMount(): 컴포넌트가 처음 렌더링된 후에 호출됩니다.
  1. 업데이트 단계: 상태(state) 또는 props가 변경될 때.
  • componentDidUpdate(): 컴포넌트가 업데이트된 후에 호출됩니다.
  1. 언마운트 단계: 컴포넌트가 DOM에서 제거될 때.
  • componentWillUnmount(): 컴포넌트가 제거되기 직전에 호출됩니다.

2. 함수형 컴포넌트의 useEffect

함수형 컴포넌트는 useEffect 훅을 사용하여 클래스 컴포넌트의 생명주기 메서드와 같은 동작을 처리합니다. useEffect는 기본적으로 두 가지 역할을 수행할 수 있습니다.

  1. 마운트와 업데이트 처리:
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();
}, []);

2. useLayoutEffect

useLayoutEffectuseEffect와는 다르게, 브라우저가 화면을 그리기 전에 동기적으로 실행됩니다. 즉, DOM이 변경되기 전에 코드를 실행하고 싶은 경우에 사용됩니다. 이로 인해 UI에 직접적인 영향을 미치는 작업에 주로 사용됩니다.

useLayoutEffect(() => {
  // DOM이 렌더되기 전에 동기적으로 실행
  const rect = element.getBoundingClientRect()
  // DOM 요소의 위치에 기반한 레이아웃 조정
}, [])

3. 차이점 정리

항목useEffectuseLayoutEffect
실행 시점렌더링 이후, 화면 그린 후 실행렌더링 이후, 화면을 그리기 전에 실행
주요 사용 시나리오비동기 데이터 패칭, 이벤트 등록/해제레이아웃 변경, 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는 실행되지 않습니다. 이를 통해 컴포넌트 내에서 여러 상태 값이 변경되는 시점을 각각 감지할 수 있습니다.

useLayoutEffectuseEffect를 함께 사용해야 하는 상황이 있을까요?

useLayoutEffectuseEffect함께 사용해야 하는 상황은 특정 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에서 클린업 함수는 다음 두 가지 경우에 실행됩니다:

  1. 컴포넌트가 언마운트될 때:
  • 컴포넌트가 DOM에서 제거될 때 클린업 함수가 호출됩니다. 이는 주로 이벤트 리스너 제거, 타이머 정리, 네트워크 요청 취소 등을 처리하는 데 사용됩니다.
  1. 의존성 배열에 있는 값이 변경될 때:
  • 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 측정과 같은 작업에서 꼭 필요할 수 있습니다. 하지만 사용 시 다음과 같은 사항에 주의해야 합니다:

  1. 성능 저하 가능성:
  • useLayoutEffect는 렌더링이 완료되기 전에 실행되기 때문에, 너무 복잡한 작업을 수행하면 렌더링이 지연될 수 있습니다. 이는 UI가 늦게 표시되는 현상을 초래할 수 있으므로, 간단한 DOM 조작 작업에만 사용하는 것이 좋습니다.
  1. 사용 빈도:
  • 여러 곳에서 useLayoutEffect를 사용하면 성능에 부정적인 영향을 줄 수 있습니다. 필요하지 않은 경우에는 useEffect로 대체하여 성능을 최적화하는 것이 좋습니다.
  1. DOM 조작:
  • useLayoutEffect는 DOM을 즉시 조작할 수 있는 시점에서 실행되므로, DOM의 크기나 위치를 기반으로 한 레이아웃 작업(예: 애니메이션, 스크롤 위치 설정)에 적합합니다. 이 경우, 화면이 깜빡이거나 레이아웃이 깨지지 않도록 주의해야 합니다.
useLayoutEffect(() => {
  const element = document.getElementById('myElement')
  const rect = element.getBoundingClientRect()
  console.log('DOM 요소의 위치:', rect)
}, [])

useLayoutEffect는 성능에 영향을 줄 수 있으므로, 화면 렌더링이 지연되지 않도록 꼭 필요한 상황에서만 사용해야 하며, 복잡한 작업은 피하는 것이 좋습니다.