FrontEnd/React.js

[React.js] React Component 생명주기 2 - Updating

푸고배 2021. 2. 3. 20:55

 컴포넌트 생명주기 

 Updating 

 

props 또는 state가 변경되면 갱신이 발생한다. 아래 메서드들은 컴포넌트가 다시 렌더링될 때 순서대로 호출된다.

 

1. static getDerivedStateFromProps()

 static getDerivedStateFromProps(props, state)

 

getDerivedStateFromProps는 최초 마운트 시와 갱신 시 모두에서 render 메서드 호출 직전에 호출된다. state를 갱신하기 위한 객체를 반환하거나, null을 반환하여 아무 것도 갱신하지 않을 수 있다.

 

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

 

state를 끌어오면 코드가 장황해지고, 이로 인해 컴포넌트를 이해하기 어려워진다. 보다 간단한 다른 대안들에 익숙해지는 것을 권장한다.

이 메서드는 컴포넌트 인스턴스에 접근할 수 없다. 인스턴스 접근이 필요하다면, class 정의 외부에서 컴포넌트의 props와 state에 대한 순수 함수를 추출하여 getDerivedStateFromProps()와 다른 클래스 메서드 간에 코드를 공유 및 재사용할 수 있다.

 

이 메서드는 이유와 상관없이 랜더링 때마다 매번 실행되므로 주의가 필요하다. 이는UNSAFE_componentWillReceiveProps와는 다른데, 이 메서드의 경우 부모 컴포넌트가 다시 렌더링을 발생시켰을 때에만 실행되고, 해당 컴포넌트 내에서 지역적인 setState가 발생한 경우에는 실행되지 않는다.

 

 

2. shouldComponentUpdate()

 shouldComponentUpdate(nextProps, nextState)

 

shouldComponentUpdate()를 사용하면 현재 state 또는 props의 변화가 컴포넌트의 출력 결과에 영향을 미치는지 여부를 React가 알 수 있다. 기본 동작은 매 state 변화마다 다시 렌더링을 수행하는 것(기본값 : return true)이며, 대부분의 경우 기본 동작에 따라야한다.

 

shouldComponentUpdate()는 props 또는 state가 새로운 값으로 갱신되어서 렌더링이 발생하기 직전에 호출된다. 이 메서드는 초기 렌더링 또는 forceUpdate()가 사용될 때에는 호출되지 ㅇ낳는다.

 

이 메서드는 오직 성능 최적화만을 위한 것으로, 렌더링을 방지하는 목적으로 사용할 경우 버그로 이어질 수 있다. shouldComponentUpdate()의 내용을 직접 작성하는 대신에 PureComponent를 사용하는 것이 좋다. PureComponent는 props와 state에 대하여 얕은 비교를 수행하고, 해야 할 갱신 작업을 건너뛸 확률을 낮춘다.

 

이 메서드를 직접 작성할 자신이 있다면, this.props와 nextProps, 그리고 this.state와 nextState를 비교한 뒤 false를 반환하는 것으로 React가 갱신 작업을 건너뛰게 만들 수 있다. 여기서 false를 반환하는 것이 자식 컴포넌트들이 각자가 가진 state의 변화에 따라 다시 렌더링을 수행하는 것을 막는 것은 아니라는 점에 주의해야한다.

shouldComponentUpdate() 내에서 깊은 동일성 검사를 수행하거나 JSON.stringify()를 사용하는 것을 권하지 않는다. 아주 비효율적이며 성능을 떨어트릴 수 있다.

현재, shouldComponentUpdate()가 false를 반환할 경우 UNSAFE_componentWillUpdate(), render(), 그리고 componentDidUpdate()는 호출되지 않는다. 나중에는 shouldComponentUpdate()를 엄격한 지시자가 아닌 힌트로서 다루게 될 것이고, false의 반환을 반환하더라도 컴포넌트가 계속해서 다시 렌더링을 수행할 것이다.

 

3. render()

 render()

 

render() 메서드는 클래스 컴포넌트에서 반드시 구현돼야하는 유일한 메서드이다.

이 메서드가 호출되면 this.props와 this.state의 값을 활용하여 아래의 것 중 하나를 반환해야 한다.

 

  • React 엘리먼트. 보통 JSX를 사용하여 생성된다. 예를 들어, <div />와 <MyComponent />는 React가 DOM 노드 또는 사용자가 정의한 컴포넌트를 만들도록 지시하는 React 엘리먼트이다.
  • 배열과 Fragment. render()를 통하여 여러 개의 엘리먼트를 반환한다. 자세한 정보는 Fragments 문서를 통하여 확인할 수 있습니다.
  • Portal. 별도의 DOM 하위 트리에 자식 엘리먼트를 렌더링한다. 자세한 정보는 Portals에서 확인할 수 있다.
  • 문자열과 숫자. 이 값들은 DOM 상에 텍스트 노드로서 렌더링된다.
  • Boolean 또는 null. 아무것도 렌더링하지 않는다. (대부분의 경우 return test && <Child />패턴을 지원하는 데에 사용되며, 여기서 test는 boolean 값이다.)

render() 함수는 순수해야 한다. 즉, 컴포넌트의 state를 변경하지 않고, 호출될 때마다 동일한 결과를 반환해야 하며, 브라우저와 직접적으로 상호작용을 하지 않는다.

브라우저와 상호작용하는 작업이 필요하다면, 해당 작업을 componentDidMount()이나 다른 생명주기 메서드 내에서 수행한다. render()를 순수하게 유지하여야 컴포넌트의 동작을 이해하기 쉽다.

 

주의

shouldComponentUpdate()가 false를 반환하면 render()는 호출되지 않는다.

 

4. getSnapshotBeforeUpdate()

 getSnapshotBeforeUpdate(prevProps, prevState)

 

getSnapshotBeforeUpdate()는 가장 마지막으로 렌더링된 결과가 DOM 등에 반영되었을 때 호출된다. 이 메서드를 사용하면 컴포넌트가 DOM으로부터 스크롤 위치 등과 같은 정보를 이후 변경되기 전에 얻을 수 있다. 이 생명주기가 반환하는 값은 componentDidUpdate()에 인자로 전달된다.

 

이 메서드에 대한 사용례는 흔하지 않지만, 채팅 화면처럼 스크롤 위치를 따로 처리하는 작업이 필요한 UI등을 생각해볼 수 있다.

 

스냅샷 값을 반환하거나 null을 반환한다.

 

사용 예시는 아래와 같다.

 

class ScrollingList extends React.Component {
  constructor(props) {
    super(props);
    this.listRef = React.createRef();
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Are we adding new items to the list?
    // Capture the scroll position so we can adjust scroll later.
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // If we have a snapshot value, we've just added new items.
    // Adjust scroll so these new items don't push the old ones out of view.
    // (snapshot here is the value returned from getSnapshotBeforeUpdate)
    if (snapshot !== null) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      <div ref={this.listRef}>{/* ...contents... */}</div>
    );
  }
}

 

위의 예시에서는 getSnapshotBeforeUpdatescrollHeight 프로퍼티 값을 아는 것이 중요한데, render와 같은 "렌더링" 단계의 생명주기와 getSnapshotBeforeUpdatecomponentDidUpdate와 같은 "커밋" 단계의 생명주기 간에 지연 시간이 발생할 수 있기 때문이다.

 

5. componentDidUpdate()

 componentDidUpdate(prevProps, prevState, snapshot)

 

componentDidUpdate()는 갱신이 일어난 직후에 호출된다. 이 메서드는 최초 렌더링에서는 호출되지 않는다.

 

컴포넌트가 갱신되었을 때 DOM을 조작하기 위하여 이 메서드를 활용하면 좋다. 또한, 이전과 현재의 props를 비교하여 네트워크 요청을 보내는 작업도 이 메서드에서 이루어지면 된다. (가령, props가 변하지 않았다면 네트워크 요청을 보낼 필요가 없다.)

 

componentDidUpdate(prevProps) {
  // 전형적인 사용 사례 (props 비교를 잊지 마세요)
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}

 

componentDidUpdate()에서 setState()를 즉시 호출할 수도 있지만, 위의 예시처럼 조건문으로 감싸지 않으면 무한 반복이 발생할 수 있다는 점에 주의한다. 또한 추가적인 렌더링을 유발하여, 비록 사용자는 눈치채지 못할지라도 컴포넌트 성능에 영향을 미칠 수 있다. 상위에서 내려온 prop을 그대로 state에 저장하는 것은 좋지 않으며, 그 대신 prop을 직접 사용하는 것이 좋다. 이와 관련된 자세한 정보는 props를 state에 복사하는 것이 버그를 유발하는 이유에서 확인할 수 있다.

컴포넌트에서 getSnapshotBeforeUpdate()를 구현한다면, 해당 메서드가 반환하는 값은 componentDidUpdate()에 세 번째 “snapshot” 인자로 넘겨진다. 반환값이 없다면 해당 인자는 undefined를 가진다.

 

주의

componentDidUpdate()는  shouldComponentUpdate()가 false를 반환하면 호출되지 않는다.
주의
아래 메서드는 기존에 사용되었지만 이제는 사용하면 안된다.

 

참고자료 :

 

React.Component – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

 

반응형