[React] 리액트 라이프 사이클

Lemon·2022년 9월 12일
5

React

목록 보기
11/21
post-thumbnail

React 라이프 사이클이란?

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

라이프 사이클을 다루는 것은 컴포넌트가 생겨나고, 변화하고, 없어지는 일련의 프로세스를 프로그래머가 통제하는 것을 뜻합니다. 우리는 실제로 HTML을 작성한 것이 아니고 JSX를 작성했기 때문에 이런 부분들을 알아야 하는 것입니다. 라이프 사이클을 이해하게 되면, 왜 그동안 리액트에 render 메서드를 작성만 했는데도 브라우저에게 어떻게 HTML을 만들어 주었는지 알 수 있게 됩니다.

생성자를 통해서 필요한 메모리를 할당하고, 객체의 역할이 끝나면 소멸자를 통하여 메모리를 반환합니다. 컴퓨터의 자원은 한정적이기 때문에 역할이 끝나면 모든 메모리를 반환해야 메모리 누수에 대한 문제가 생기지 않고, 더 좋은 성능을 발휘할 수 있습니다. 라이프 사이클이 있는 이유는 메모리 비우기가 가장 큰 이유입니다.


라이프 사이클의 분류

라이프 사이클은 아래의 그림과 같이 9가지로 분류됩니다.

크게 세 가지 유형으로 나눌 수 있습니다.

라이프 사이클 세 가지 유형

  • 생성될 때 (마운팅)
  • 업데이트 될 때 (업데이팅)
  • 제거할 때 (언마운팅)

React를 사용하면 Component 단위로 UI를 화면에 보이게 하거나, 다른 UI로 변경하거나, 현재 보이는 UI를 화면에서 제거할 수 있습니다.

따라서 각 Component들을 생성(Mounting)하고, 업데이트(Updating)하고, 제거(Unmounting)하는 단계를 차례로 겪는 생명주기(Life Cycle)를 가지고 있는 것입니다.

제대로 된 기능을 수행하려면 자동으로 업데이트되는 과정에 끼어들어서 데이터를 가공하기도 해야합니다. 따라서 라이프사이클에서 각 단계별로 필요한 순간에 필요한 작업들을 끼워넣을 수 있는 메서드들이 존재합니다.

함수방식 라이프 사이클

위에 있는 사진은 Class 방식의 라이프 사이클입니다. 함수 방식의 라이프 사이클은 아래와 같습니다.

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

원래 함수형 방식에서는 안됐지만 React 16.8 Hooks 업데이트되면서 라이프사이클을 다룰 수 있게 되었습니다.


라이프 사이클 메서드

1. 컴포넌트가 로딩되기 시작하는 Mount (생성)

DOM이 생성되고, 웹 브라우저 상에서 나타나는 것을 뜻합니다.

1-1. constructor (클래스 생성자)

클래스 인스턴스 객체를 생성하고 초기화하는 메서드로 리액트에서 컴포넌트를 만들 때 처음으로 실행됩니다. 이 메서드에서는 초기 state를 정할 수 있습니다.
막 실행된 시점이기 때문에 브라우저에서 우리가 작성한 JSX는 보이지 않습니다.
React 컴포넌트의 constructor는 해당 컴포넌트가 생성시에 호출됩니다.
React.Component를 상속한 컴포넌트의 생성자를 구현할 때는 다른 구문에 앞서 super(props)를 호출해야합니다. 그렇지 않으면 this.props가 생성자 내에서 정의되지 않아서 버그가 생길 수 있습니다. this.props, this.state에 접근이 가능하고 리액트 요소를 반환합니다.

// constructor 기본 구조
class MyComponent() extends React.Component{
  constructor(props){
    super(props)
    //...
  }
}
// super 함수를 호출해야 React.Component class의 method가 호출 됩니다. 
// super를 호출하지 않으면 컴포넌트는 작동하지 않습니다.
//class
class Example extends React.Component {
	constructor(props) {
      	super(props);
      	this.state = { count: 0 } ;
    }
}

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

클래스형에서는 초기 state를 정할 때 constructor를 사용해야 합니다.
하지만 Hook에서는 useState hook을 사용하면 초기 상태를 쉽게 설정해줄 수 있습니다.
메서드를 바인딩하거나 state를 초기화하는 작업이 없다면, 해당 React 컴포넌트에는 constructor을 구현하지 않아도 됩니다.


1-2. static getDerivedStateFromProp

React 16.3버전 이후에 생긴 메서드입니다. props로 받아 온 값을 state에 동기화시키는 용도로 사용합니다. 컴포넌트가 마운트 될 때와 업데이트 될 때 호출됩니다.

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

주의할 점은 정적 메서드이기 때문에 this를 호출할 수 없습니다.
이 메서드는 이유와 상관없이 렌더링 때마다 매번 실행되므로 주의해야합니다.
최초 마운트 시와 갱신 시 모두에서 render 메서드를 호출하기 직전에 호출됩니다.
state를 갱신하기 위한 객체를 반환하거나, null을 반환하여 아무 것도 갱신하지 않을 수 있습니다.


1-3. shouldComponentUpdate

props나 state를 변경했을 때, 리렌더링을 할지 말지 결정하는 메서드입니다.
이 메서드에서는 반드시 truefalse를 반환해야합니다. 기본적으로 true를 반환합니다. 조건에 따라 false를 반환하면 해당 조건에는 render 함수를 호출하지 않습니다.
이 메서드는 오직 성능 최적화만을 위한 것이며 렌더링 방지 목적으로 사용하게 된다면 버그가 생길 수 있습니다.

//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
	}                        
)

Hook에선 보통 propsReact.memo, stateuseMemo를 활용하면 렌더링 성능을 개선할 수 있습니다.


1-4 실제 로딩이 이루어지는 render

render 메서드는 가장 기초적이면서 가장 중요한 메서드입니다. 컴포넌트 렌더링할 때 필요한 메서드 중 유일한 필수 메서드이기도 합니다.

최종적으로 component에서 작업한 결과물을 return하는 메서드입니다. React Element는 보통 JSX 를 사용하여 작성합니다. render 메소드가 실행되면서 JSX가 HTML로 변환되어 우리가 보는 웹 브라우저에 나타나게 됩니다.

render 메서드는 컴포넌트가 로딩될 때에도 실행되지만 컴포넌트의 데이터 (state, props)가 업데이트 되었을 때에도 동작합니다. render 메소드에서 setState나 props를 변화시키는 메소드를 가능하면 수행하지 않는 것을 추천합니다. (잘못하면 무한루프가 일어나게 됩니다.)

render 메서드는 순수 함수여야 합니다. 이것은 input에 대해서 같은 output이 나와야한다는 것을 의미합니다.

함수형 컴포넌트에서는 render를 안 쓰고 컴포넌트를 렌더링할 수 있습니다.

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

// Hooks
const example = () => {
  return <div>컴포넌트</div>
}
render() {
	return (
      <div>
        <header className="title">
          Yuntroll React
        </header>
      </div>
	);
}

결과물로 나온 Element들이 Virtual DOM에 mount되고 실제 DOM에 업데이트됩니다.


1-5. 처음 로딩이 끝난 뒤에 수행되는 componentDidMount

Mounting의 마지막 부분입니다.
render 메서드에 있는 모든 부분들이 브라우저에 나타나게 되었을 때만 실행되는 메서드입니다. 즉, 이 메서드는 컴포넌트를 만들고 첫 렌더링을 마친 후 실행합니다.
오직 초기 컴포넌트의 로딩 이후에 한번만 실행되는 라이프사이클 메소드입니다.
이제 여기서 DOM을 직접 조작할 수 있게 됩니다.
주로

  • D3, masonry 처럼 DOM 을 사용해야하는 외부 라이브러리 연동을 하거나,
  • 해당 컴포넌트에서 필요로하는 데이터를 요청하기 위해 axios, fetch * 등을 통하여 ajax 요청을 하거나,
  • DOM 의 속성을 읽거나 직접 변경하는 작업을 진행합니다.
    → DOM이 렌더링 되기 전에 DOM에 접근하게 되면 에러가 발생하기 때문입니다.

일반적으로 이 메서드에서 비동기 API를 사용합니다. 응답받은 외부 데이터는 내부 컴포넌트 상태에 저장되어 컴포넌트가 업데이트 되면 render 메서드가 실행됩니다.

import React, { Component } from "react";

class UserListClass extends Component {
  state = {
    loading: true,
    users: [],
  };

  componentDidMount() {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((response) => response.json())
      .then((users) => this.setState({ users, loading: false }));
  }

  render() {
    const { loading, users } = this.state;
    if (loading) return <div>Loading...</div>;
    return (
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    );
  }
}

코드출처: https://www.daleseo.com/react-hooks-use-effect/

Hook 에서는 useEffect를 활용하여 다음의 기능을 구현할 수 있습니다.

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

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

여기서 useEffect[]의존성 배열을 비워야지만 똑같은 메소드를 구현할 수 있습니다.

https://ko.reactjs.org/docs/hooks-effect.html


2. 컴포넌트의 업데이트 Updating (업데이트)

업데이트는 4가지 상황에서 발생합니다.
1. Props가 바뀔 때
2. State가 바뀔 때
3. 부모 컴포넌트가 리렌더링 될 때
4. this.forceUpdate로 강제로 렌더링을 trigger하는 경우
→ input이 달라지니 output도 달라져야 하기 때문입니다.
→ 상위 component가 update되면 하위 component도 rendering되고 다시 mount됩니다.


2-1. componentWillReceiveProps

컴포넌트가 새로운 속성을 전달받을 때 실행됩니다.
컴포넌트에 새로운 속성을 받아오는 시점에 끼어들어서 render 메서드를 호출하기 전에 일부 로직을 추가할 수 있습니다.
해당 메서드는 새로운 속성을 인자로 받습니다.
컴포넌트를 최초로 실행할때는 렌더되지 않습니다.
setState 메서드를 호출해도 추가로 다시 렌더링이 발생하지 않습니다.
속성 값의 변경과 상관없이 (부모 구조 또는 호출에 따라) 재렌더링이 이루어질 때마다 실행합니다. newProps가 항상 현재 속성과 다른 값이라고 가정할 수 없습니다.

componentWillReceiveProps(newProps) {
  this.setState({
      opacity: (newProps.isVisible) ? 1 : 0
  })
}

기존 속성 값은 this.props 객체에 있습니다. 예를 들어 다음의 예제 코드는 불리언 값인 isVisible을 기준으로하여, CSS에 사용할 opacity 상태 값을 변경합니다.

출처: https://thebook.io/006961/part01/ch05/06/01/


2-2. shouldComponentUpdate

( Mount 부분 1-3에서도 언급되었습니다. )
propsstate를 변경했을 때, 리렌더링을 할지 말지 결정하는 메서드입니다.
이 메서드에서는 반드시 truefalse를 반환해야합니다.
이 메서드는 기본적으로 true를 반환합니다. 조건에 따라 false를 반환하면 해당 조건에는 render 함수를 호출하지 않습니다.
이 메서드는 오직 성능 최적화만을 위한 것이며 렌더링 방지 목적으로 사용하게 된다면 버그가 생길 수 있습니다.

shouldComponentUpdate(nextProps, nextState) {
  // return false 하면 업데이트 ❌
  // return this.props.checked !== nextProps.checked
  return true;
}

2-3. getSnapShotBeforeUpdate

이 메서드는 render에서 만들어진 결과가 브라우저에 실제로 반영되기 직전에 호출됩니다.
업데이트 되기 직전에 snapshot(props & states)을 확보하는게 목적입니다.
인자값에서 반환되는 prevPropsprevState는 변경되기 전에 값을 보여줍니다.

getSnapshotBeforeUpdate(prevProps,prevState){
   // DOM 업데이트가 일어나기 직전의 시점입니다. 
   return {};
}

컴포넌트에 변화가 일어나기 직전의 DOM 상태를 가져와서 특정 값을 반환하면 그 다음 발생하게 되는 componentDidUpdate함수에서 받아와서 사용합니다.
이 메서드에 대한 사용 예는 흔하지 않지만, 채팅 화면처럼 스크롤 위치를 따로 처리하는 작업이 필요한 UI 등을 생각해볼 수 있습니다.

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
  }
}

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


2-4. componentWillUpdate

새로운 속성이나 상태를 받은 후 렌더링 직전에 호출합니다. 초기 렌더링 시에는 호출되지 않습니다.
shouldComponentUpdate 가 false를 반환하면 해당 메서드는 실행되지 않습니다.
컴포넌트를 DOM에서 제거할 때 실행합니다.
componentDidMount에서 등록한 이벤트, 타이머, 직접 생성한 DOM이 있다면 여기서 제거 작업을 해야합니다.

componentWillUpdate(nextProps, nextState){...}

2-5. componentDidUpdate

컴포넌트의 변경이 완료되었을 때 수행되는 메소드입니다. 조금 복잡한 작업을 수행하기에 최적화 되어있습니다. 처음 렌더링 될때는 실행되지 않고, 리 렌더링을 완료한 후 실행합니다. 이 말은 stateprops가 변경되어 DOM 변화가 발생한 뒤 발생된다는 것입니다.
render메소드가 실행되어 업데이트 된 state , props 와 업데이트 되기 전인 state, props 를 가지고 비교 작업을 가능하게 해줍니다.
업데이트가 끝난 직후이므로 DOM관련 처리를 해도 무방합니다.

prevProps 또는 prevState를 사용하여 컴포넌트가 이전에 가졌던 데이터에 접근이 가능합니다.

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

componentDidUpdate에서 setState를 즉시 호출할 수 있지만, …조건문으로 감싸지 않으면 무한 반복이 발생할 수 있다는 점에서 주의해야합니다. break와 같은 역할을 가진 조건문을 걸어줘야합니다. 실제 변경이 일어난 경우만 작동하도록 코드를 작성해야합니다.

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

3. 컴포넌트의 삭제 Unmounting(제거)

Unmounting은 DOM에서 제거되는 것을 뜻합니다. JSX에 포함되었다가 이후에 제거되는 경우에 발생합니다.

언마운트 예시

return (
	<div>
		{ value ? <Comp1 /> : <Comp2 /> }
		<button onClick={()=>setValue(!value)}>Change</button>
	</div>
);

초기에 valueture인 상황에서 버튼을 클릭하면 Comp1은 언마운트되고 Comp2는 마운트 됩니다. 또 버튼을 클릭하면 Comp2는 언마운트되고 Comp1은 마운트됩니다.

<Comp1 key={value} />

key가 변하는 경우도 언마운트됩니다.

위 코드에서 value가 변할 때마다 Comp2는 언마운트되고 새로운 Comp1이 마운트 됩니다.


3-1. ComponentWillUnmount

이 메서드는 컴포넌트를 DOM에서 제거할 때 실행합니다. 컴포넌트가 사라질 때 수행되는 메소드입니다. componentDidMount에서 등록한 이벤트가 있다면 여기서 제거 작업을 해야합니다.

타이머를 제거하거나, DOM 요소를 정리하거나, componentDidMount에서 연결한 이벤트를 제거할 수 있습니다. 컴포넌트가 화면에서 사라지기 직전에 호출합니다.

외부 라이브러리를 사용한게 있거나, 해당 라이브러리에 dispose 기능이 있다면 여기서 호출합니다.

함수형 컴포넌트에서는 useEffect CleanUp 함수를 통해 해당 메서드를 구현할 수 있습니다.

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

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

4. Error Handling

4-1. componentDidCatch

맨 위의 사진에는 보이지 않지만 componentDidCatch라는 메서드가 존재합니다. 이 메서드는 컴포넌트 렌더링 도중에 에러가 발생 했을 때 애플리케이션이 멈추지 않고 오류 UI를 보여줄 수 있게 합니다.


🔗 참고 링크
https://ljh86029926.gitbook.io/coding-apple-react/2/undefined-1/componentdidmount
https://ljh86029926.gitbook.io/coding-apple-react/2/lifecycle-of-react-component
https://ko.reactjs.org/docs/react-dom.html#render
https://codechasseur.tistory.com/99
https://velog.io/@hwang-eunji/React-Hooks-3-useEffect#2-useeffectcb
https://velog.io/@cadyky95/20220810React-Lifecycle-Methods
https://velog.io/@kimdlzp/라이프사이클
React class vs function style - 4.1. 클래스에서 라이프 사이클 구현 하기

profile
프론트엔드 개발자 가보자고~!!

1개의 댓글

comment-user-thumbnail
2024년 4월 16일

감사합니다 공부하는데 참고가 많이 되었습니다! 제 블로그에 참고하여 작성해도 될까요?

답글 달기