IT/React

React 라이프 사이클 이해하기 (함수형 컴포넌트에서의 생명주기)

라임웨일 2022. 8. 16. 10:34
반응형

TL;DR😄

리액트나 Vue를 이용해서 개발을 하게 될 때 React와 Vue모두 각각의 라이프사이클(생명주기)이 있습니다.  

 

'라이프사이클(생명주기)'라는 말을 처음 접하는 분이라면 라이프사이클이라는 말이 막연하게 느껴질 수 있는데 이 부분은 아래 글을 읽으면 자연스럽게 이해할 수 있습니다. 

리액트에서 라이프사이클의 개념은 클래스 컴포넌트를 사용할 때 명확히 사용되는 부분인데 React 16.8 이후 리액트 훅(함수형 컴포넌트)의 개념이 등장하면서 생명주기의 개념도 이전처럼 명확하게 구분되어 사용되진 않습니다.

그럼에도 우리는 리액트의 라이프사이클을 알아야 합니다. 아직 React에서는 공식적으로 클래스 컴포넌트와 함수형 컴포넌트 모두 지원을 할 것이라고 발표했고 이전에 작성된 코드들은 대부분 다 클래스 컴포넌트로 작성되어 있기 때문입니다. 더욱이 함수형 컴포넌트를 사용한다고 해서 클래스 컴포넌트처럼 명확히 모든 케이스를 구분해서 라이프사이클을 사용하지 않는다 뿐이지 함수형 컴포넌트에서도 리액트의 라이프사이클을 사용하고 있기 때문에 우리는 올바를 프로그래밍을 위해 라이프사이클 개념을 익혀야 합니다.

 

그럼 이제 같이 React 라이프사이클을 알아보도록 해요.

 

📖 리액트 라이프 사이클이란?

리액트 라이프 사이클(생명주기)

 

React는 컴포넌트 기반의 View를 중심으로 한 라이브러리입니다. 그러다보니 각각의 컴포넌트에는 라이프사이클(생명주기) 즉, 컴포넌트의 수명 주기가 존재하는데 컴포넌트의 수명은 보통 페이지에서 렌더링(Rendering)되기 전인 준비 과정에서 시작하여 페이지에서 사라질 때 끝이납니다.

 

👉 라이프사이클의 분류

라이프 사이클은 총 9개가 존재합니다. 하지만 크게 세 가지 유형으로 나눌 수 있는데 생성될 때, 업데이트할 때, 제거할 때로 구분할 수 있습니다.

이를 리액트에서는 마운트, 업데이트, 언마운트라고 말합니다. 여기서 마운트는 DOM이 생성되고 웹 브라우저 상에서 나타나는 것을 뜻하고, 반대로 언마운트는 DOM에서 제거되는 것을 뜻합니다.

 

주의하여 볼 것은 업데이트 부분인데, 업데이트는 다음과 같은 4가지 상황에서 발생합니다.

  1. props가 바뀔 때
  2. state가 바뀔 때
  3. 부모 컴포넌트가 리 렌더링 될 때
  4. this.forceUpdate로 강제로 렌더링을 트리거할 때

 

💡 라이프 사이클 메서드 살펴보기

👉 마운트

먼저 마운트 될 때 발생하는 생명주기들을 알아봅시다.

  • constructor
  • getDerivedStateFromProps
  • render
  • componentDidMount

 

1. constructor

constructor(생성자)는 우리말로 생성자라고 말하는데 컴포넌트를 만들어지면서 가장 먼저 실행되는 메서드입니다. 이 메서드에서는 초기 state 값을 정할 수 있습니다.

 

  • 클래스형:  초기 state를 정할 때 constructor를 사용해야 합니다.
  • 함수형(hooks):  useState hook을 사용하여 초기 상태를 설정해줄 수 있습니다.
// Class
class Example extends React.Component {
  constructor(props) {
    super(props);
      this.state = { count: 0 };
}

// Hooks
const Example = () => {
  const [count,setCount] = useState(0);
}

 

2. getDerivedStateFromProps

이 메서드는 리액트 16.3 버전 이후에 생긴 메서드입니다.

getDerivedStateFromProps는 props로 받아 온 값을 state에 동기화시키는 용도로 사용하며, 컴포넌트가 마운트 될 때와 업데이트될 때 호출됩니다. getDerivedStateFromProps는 최초 마운트 시와 갱신 시 모두에서 render 메서드를 호출하기 직전에 호출됩니다. state를 갱신하기 위한 객체를 반환하거나, null을 반환하여 아무것도 갱신하지 않을 수 있습니다.

다른 생명주기 메서드와는 달리 앞에 static을 필요로 하고, 이 안에서는 this 롤 조회할 수 없습니다. 여기서 특정 객체를 반환하게 되면 해당 객체 안에 있는 내용들이 컴포넌트의 state로 설정이 됩니다. 반면 null을 반환하게 되면 아무 일도 발생하지 않습니다.

참고로 이 메서드는 컴포넌트가 처음 렌더링 되기 전에도 호출되고, 그 이후 리 렌더링 되기 전에도 매번 실행됩니다.

 

공식문서에도 나와 있듯 이 메서드는 시간이 흐름에 따라 변하는 props에 state가 의존하는 아주 드문 사용 케이스를 위하여 존재합니다. 예를 들어, 무엇을 움직이도록 만들지 결정하기 위하여 이전과 현재의 자식 엘리먼트를 비교하는 <Transition>와 같은 컴포넌트를 구현할 때에 편리하게 사용할 수 있습니다.

 

// Class
class Example extends React.Component {
  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.value !== prevState.value) {
      return { value: nextProps.value }
    }
    return null
  }
}

 

3. render

가장 기초적인 메서드이며 가장 중요한 메서드입니다. 컴포넌트를 렌더링 할 때 필요한 메서드로 유일한 필수 메서드이기도 합니다. 함수형 컴포넌트에서는 render를 안 쓰고 컴포넌트를 렌더링 할 수 있습니다.

// Class
class Example extends React.Component {
  render() {
    return <div>컴포넌트</div>
  }
}

// Hooks
const example = () => {
  return <div>컴포넌트</div>
}

 

4. componentDidMount

컴포넌트의 첫 번째 렌더링이 마치고 나면 호출되는 메서드입니다. 이 메서드가 호출되는 시점에는 우리가 만든 컴포넌트가 화면에 나타난 상태입니다. 여기선 외부 라이브러리 연동하거나 해당 컴포넌트에서 필요로 하는 데이터를 요청하기 위해 axios, fetch 등을 통하여 데이터를 요청하기, DOM의 속성을 읽거나 직접 변경하는 작업을 진행합니다.

 

함수형 Hooks에서는 useEffect를 활용하여 다음의 기능을 구현할 수 있습니다.  hooks로 사용 시 useEffect의 [] 의존성 배열을 비워야지만 componentDidMount 컴포넌트와 똑같은 메서드를 구현할 수 있습니다.

useEffect에 익숙하지 않으신 분들이라면 리액트 공식문서-useEffect를 한번 읽어보시는 것을 추천드립니다.

// Class
class Example extends React.Component {
    componentDidMount() {
        ...
    }
}

// Hooks
const Example = () => {
    useEffect(() => {
        ...
    }, []);
}

 

👉 업데이트

컴포넌트가 업데이트되는 시점에 어떤 생명주기 메서드들이 호출되는지 알아봅시다.

  • getDerivedStateFromProps (마운트에서 배웠던 매서드입니다. 컴포넌트의 props 나 state 가 바뀌었을 때도 이 메서드가 호출됩니다.)
  • shouldComponentUpdate
  • render (마운트에서 배웠던 매서드입니다. 이때 랜더링 되는 것을 리 랜더링이라고 합니다.)
  • getSnapshotBeforeUpdate
  • componentDidUpdate

 

5. shouldComponentUpdate

이 메서드는 props나 state를 변경했을 때, 리 렌더링을 할지 말지 결정하는 메서드입니다. 이 메서드에서는 반드시 true나 false를 반환해야 하며 이 메서드는 목적은 오직 성능 최적화만을 위한 것입니다. 렌더링 목적을 방지하는 목적으로 사용하게 된다면 버그로 이어질 수 있습니다. 클래스형도 보통은 PureComponent를 추천한다고 하고 Hooks에서도 props는 React.memo, state는 useMemo를 활용하면 렌더링 성능을 개선할 수 있습니다.

// Class
class Example extends React.Component {
  shouldComponentUpdate(nextProps) {
    // 이전값과 현재값이 같으면 리랜더링을 하지 않습니다.
    return nextProps.value !== this.props.value
  }
}

// Hooks
const Example = React.memo(() => {
      ...
  },
  (prevProps, nextProps) => {
    return nextProps.value === prevProps.value
  }
)

 

5. getSnapshotBeforeUpdate

이 메서드는 render에서 만들어진 결과가 브라우저에 실제로 반영되기 직전(컴포넌트에 변화가 일어나기 직전의 DOM 상태)의 DOM 상태를 가져와서 특정 값을 반환하면 그다음 발생하게 되는 componentDidUpdate 함수에서 받아와서 사용을 할 수 있습니다.

공식문서의 말을 빌려보면 이 메서드에 대한 사용 예는 흔하지 않지만, 채팅 화면처럼 스크롤 위치를 따로 처리하는 작업이 필요한 UI 등을 생각해볼 수 있다고합니다.

함수형에서는 아직 이 기능을 대체할만한 hook이 없다고 합니다.

class Example extends React.Component {
  getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current
      return list.scrollHeight - list.scrollTop
    }
    return null
  }
}

 

7. ComponentDidUpdate

componentDidUpdate는 리 렌더링이 완료한 후 실행합니다, 화면에 우리가 원하는 변화가 모두 반영되고 난 뒤 호출되는 메서드입니다. 3번째 파라미터로 getSnapshotBeforeUpdate에서 반환한 값을 조회할 수 있습니다.

// Class
class Example extends React.Component {
    componentDidUpdate(prevProps, prevState) {
        ...
    }
}

// Hooks
const Example = () => {
    useEffect(() => {
        ...
    });
}

 

👉 언마운트

언마운트라는것은, 컴포넌트가 화면에서 사라지는 것을 의미합니다. 언마운트에 관련된 생명주기 메서드는 componentWillUnmount 하나입니다.

8. componentWillUnmount

이 메서드는 컴포넌트가 화면에서 사라지기 직전(DOM에서 제거할 때)에 호출됩니다. componentDidMount에서 등록한 이벤트가 있다면 여기서 제거 작업을 해야합니다. 함수형 컴포넌트에서는 useEffect CleanUp 함수를 통해 해당 메서드를 구현할 수 있습니다.

// Class
class Example extends React.Component {
    coomponentWillUnmount() {
        ...
    }
}

// Hooks
const Example = () => {
    useEffect(() => {
        return () => {
            ...
        }
    }, []);
}

 

👉 그 외

9. componentDidCatch

마지막으로 맨 위의 사진에는 보이지 않지만 componentDidCatch라는 메서드가 존재합니다. 이 메서드는 컴포넌트 렌더링 도중에 에러가 발생했을 때 애플리케이션이 멈추지 않고 오류 UI를 보여줄 수 있게 해 줍니다. 리액트 공식문서에 따르면 componentDidCatch 생명주기에 해당하는 Hook은 아직 없지만, 곧 추가할 계획이라고 합니다.

// Class
class Example extends React.Component {
  componentDidCatch(error, info) {
    console.log('에러가 발생했습니다.')
  }
}

 

참고

반응형