[React] 컴포넌트 라이프사이클

pds·2022년 11월 29일
0

TIL

목록 보기
16/60
post-thumbnail

에 대해 알아보자


React Component

리액트 컴포넌트가 어떻게 돌아가는지 확인하기 앞서 컴포넌트가 무엇이고 어떤게 있는지 알아보자

In a React application, the basic structure item is a component.
Developers use them to split the code and isolate parts of the application.

  • 데이터를 입력받아 Dom Node를 출력하는 일종의 함수

  • 독립적인 UI 조각들을 만들고 재사용하며 컴포넌트들 끼리 유기적으로 연결되어 애플리케이션으로 동작한다.


리액트에서 컴포넌트 형태를 두 가지로 구분할 수 있다.

클래스형 컴포넌트 : 기본적으로 컴포넌트 수명주기와 상태관리를 할 수 있다.

함수형 컴포넌트 : 과거에는 state와 라이프사이클 관리가 안되었다. (react hook이 나오고 가능해짐)

리액트 16.8에서 hook이 도입되며 상태와 라이프사이클 관리가 함수형 컴포넌트에서 가능해짐에 따라

클래스형보다는 hook을 곁들인 함수형 컴포넌트로의 구성이 권장된다.



그래서 리액트의 라이프사이클은

기존 클래스형 컴포넌트에서 어떤 라이프사이클 단계를 관리했는지 알아보자

우리 인생이랑 비슷하다.


Mount

DOM 객체가 생성되고 브라우저에 나타나는 것을 의미한다. (컴포넌트의 탄생)

Update

상태변화로 인해 나타나던 것이 변경되는 것(컴포넌트의 성장/변화)

Unmount

컴포넌트가 더 이상 사용되지 않아 DOM에서 제거되는 것(컴포넌트의 죽음)


생명주기는 세 가지가 끝이며

클래스형 컴포넌트에서는 각 라이프사이클을 처리하기 위한 내장 동작이 포함되어있었다.



클래스 컴포넌트: Mount

컴포넌트가 DOM tree에 올려지는 것


(1) Constructor

해당 컴포넌트가 만들어질 때 처음으로 호출됨, state 초기값을 지정한다.


(2) componentWillMount()

render 메소드 직전에 호출된다.

17버전 이후로 deprecated 되었으며 getDerivedStateFromProps로 대체되었다.


(3)getDerivedStateFromProps

렌더링 직전에 호출되며 초기props 을 기반으로state 값을 동기화하여 설정하는 함수


(4)render()

데이터가 변경되어 새 화면을 그려야 할 때 호출(상태 변화마다 호출됨)

컴포넌트의 필수 메소드이며 컴포넌트 마운트 시 렌더링되어야 하는 jsx를 포함한다.


(5)componentDidMount()

컴포넌트를 생성하고 첫 렌더링이 끝나 DOM에 반영되고 호출되는 함수


class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      show: true
    };
    console.log(`${props.name} constructor`);
  }
  static getDerivedStateFromProps(props, state) {
    console.log(`${props.name} getDerivedStateFromProps`);
    return null;
  }
  componentDidMount() {
    console.log(`${this.props.name} componentDidMount`);
  }

  render() {
    console.log(`${this.props.name} render`);
    return (
      <div>
        {this.props.name}
        {this.state.show && this.props.children}
      </div>
    );
  }
}

export default function App() {
  return (
    <div className="App">
      <h1>Hello LifeCycle</h1>
      <br />
      <MyComponent name="parent">
        <MyComponent name="child" />
      </MyComponent>
    </div>
  );
}

클래스 컴포넌트: Update

자 이제 componentDidMount까지 와서 렌더링이 모두 되었고

상태가 변경되어 업데이트 되는 상황에서의 라이프사이클을 알아보자


(1)getDerivedStateFromProps


(2)shouldComponentUpdate(nextProps, nextState)

컴포넌트를 리렌더링 할지 말지 결정해준다.

기본적으로 컴포넌트는 prop state 변경에 모두 리렌더링 되는데

리렌더링의 조건을 결정해준다.


(3)render()


(4)getSnapshotBeforeUpdate(prevProps, prevState)

변경 요소에 대해 DOM 객체에 반영하기 직전에 호출됨

render 메서드를 사용하여 다시 렌더링되기 직전에 호출된다.

componentDidUpdate 메소드에 3번째 파라미터로 전달됨


(5)componentDidUpdate(prevProps, prevState, snapshot)

DOM 트리에서 작동하거나 전달된 인수가 변경될 때의 처리를 한다.



클래스 컴포넌트: Unmount

컴포넌트가 DOM 에서 제거되는 것을 말함

더는 컴포넌트를 사용하지 않을 때 발생하는 이벤트가 있다.

componentWillUnmount()

이 메서드는 컴포넌트 소멸 직전에 호출된다.

clean-up, 이벤트리스너, 구독 제거, 보류 중인 요청 중단 등에 사용된다.


class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      show: true
    };
    console.log(`${props.name} constructor`);
  }
  static getDerivedStateFromProps(props, state) {
    console.log(`${props.name} getDerivedStateFromProps`);
    return null;
  }
  componentDidMount() {
    console.log(`${this.props.name} componentDidMount`);
    console.log("====마운트 -완-====");
  }
  shouldComponentUpdate(nextProps, nextState) {
    console.log(`${this.props.name} shouldComponentUpdate`);
    return true;
  }
  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log(`${this.props.name} getSnapshotBeforeUpdate`);
    return null;
  }
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log(`${this.props.name} componentDidUpdate`);
  }
  componentWillUnmount() {
    console.log(`${this.props.name} componentWillUnmount`);
  }
  showHide = () => {
    this.setState({
      show: !this.state.show
    });
  };
  render() {
    console.log(`${this.props.name} render`);
    return (
      <div>
        {this.props.name}
        <button onClick={this.showHide}>Toggle Show/Hide</button>
        {this.state.show && this.props.children}
      </div>
    );
  }
}

export default function App() {
  return (
    <div className="App">
      <h1>Hello LifeCycle</h1>
      <br />
      <MyComponent name="parent">
        <MyComponent name="child" />
      </MyComponent>
    </div>
  );
}

child 컴포넌트를 제거하는 state의 변경이 일어날 때


클래스 컴포넌트 생명주기 CodeSandbox


Diagram


함수형 컴포넌트의 라이프사이클

상태와 사이클 관리가 클래스형 컴포넌트만 가능했었지만

hook이 도입된 이후에는 함수형도 가능해지게 되었고 클래스형 컴포넌트로 구성을 잘 하지 않는다.


Hook

함수형 컴포넌트에서 클래스형 컴포넌트의 상태관리와 라이프사이클 관리를 가능하게 해주는 도구


Hook을 활용한 생명주기 관리


단순화

우리 리액트 창조주님들 께서는 함수형에서도 상태를 관리할 수 있게 하고 기존 클래스형의 라이프사이클을 간소화 하고 쉽게 관리하도록 훅을 하사하셨다.

예를 들어 useState 훅은 컴포넌트에서 상태를 가지고 관리를 할 수 있게 해주었고

componentDidMount, componentDidUpdate, componentWillUnmount

useEffect라는 하나의 hook으로 관리할 수 있게 되었다.


예를 들면 다음과 같은 클래스형 컴포넌트에서의 관리 코드는

componentDidMount() {
    this.subscription = props.data.subscribe(this.handleDataChange);
}

componentDidUpdate(prevProps) {
  if(prevProps.data !== props.data) {
      this.subscription.unsubscribe();
      this.subscription - props.data.subscribe(this.handleDataChange);
  }
}

componentWillUnmount() {
    this.subscription.unsubscribe();
}

함수형에서 다음과 같다.

useEffect(() => {
	// componentDidMount
    // componentDidUpdate with dependency
    const subscription = props.data.subscribe(handleDataChange);

    return () => {
      // componentWillUnmount
      // dependency에 따라 update시에도 수행
      subscription.unsubscribe(handleDataChange)
    }
    //dependencies
}, [props.data])

useEffect는 렌더링 이후 수행되어

componentDidMount 역할을 하면서도

의존성에 따라 componentDidUpdate 처리도 가능하고

return 으로 클린업 기능이 제공되어 컴포넌트 소멸 시의 사이드이펙트 제어가 가능한

componentWillUnmount 처리도 가능한 것이다.


결국 기존의 필요했던 상태 관리와 라이프 사이클 관리를 이론적으로는 저 두가지 hook들로 처리가 가능해졌다.


useEffect Clean-up

구성 요소에서 useEffect Hook을 사용할 때 구성 요소가 페이지에서 제거되면 더 이상 필요하지 않은 장기 실행 코드가 생길 수 있습니다.

예를 들면 이벤트, timeinterval 같은 것들

index.js:1 Warning: Can't perform a React state update on an unmounted
component. This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup
function.

안하면 이렇게 친절하게 알려준다고 한다.


예시

스스로의 이해를 돕고자 코센박으로 예제를 만들어보았다.

setTimeout으로 시계 컴포넌트가 활성화 되었을 때 시간초가 흘러가도록 의도되었고

child 컴포넌트가 있을 경우 텍스트에 마우스를 가져다대면 콘솔이 찍히게끔 구현했다.

하지만 콘솔을 확인해보면 해당 컴포넌트들을 unmount 시켜도 시간이 계속 흘러가고 있고

텍스트에 마우스를 가져다대도 콘솔이 찍히는 것을 알 수 있다.

여전히 백그라운드에서 해당 동작들이 수행되고 있는 것이다



메모리 누수나 비정상적인 동작을 유발할 수 있기 때문에 클린업 처리를 해줘야 한다.

componentWillUnmount 에서 컴포넌트가 소멸되기 전 처리를 해주는 것과 같다고 할 수 있다.

React의 솔루션은 우리의 useEffect 함수가 clean-up 함수를 반환하도록 하는 것입니다. 이 함수는 구성 요소가 마운트 해제될 때, 즉 부모가 더 이상 반환하지 않을 때 실행됩니다.


클린업 적용



클린업이 언제 수행될까??

render -> useEffect -> setState -> re-render -> cleanup -> (optional) useEffect


언제 해야되나

타이머, 이벤트, 웹 소켓 연결같은 stateful에서의 구독해제

의도적으로 clean-up함수를 항상 작성할 필요는 없지만 언제 해야 유용할지는 알고 있을 것!


Reference

profile
강해지고 싶은 주니어 프론트엔드 개발자

0개의 댓글