ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 컴포넌트 생명주기
    리액트 2021. 8. 22. 17:49

    저는 리액트를 활용할 때 주로 함수를 통해 작성해서 생명주기에 대한 공부를 자세히 안했는데, 요새 리액트 에러를 볼 때 생명주기 관련한 단어들을 자주 만나서 이번에 한번 리액트 공식문서 및 블로그를 보며 공부할 겸 작성하게 됐습니다.

     

    코드 예시들은 전부 리액트 공식문서 및 리액트 블로그에서 갖고왔습니다.

    리액트 생명주기 표

    우리가 컴포넌트를 렌더링할 때 위의 생명주기에 따라 진행하게 되는데 class를 사용할 때 위 함수들을 접근 할 수 있어 어느정도 제어를 할 수 있습니다. 

     

    리액트 컴포넌트를 클래스로 사용하기 위해서는 React.Component를 상속받아야 하며 render() 메소드를 무조건 작성해야합니다. 그 이외는 전부 선택사항입니다. 리액트 문서를 보면 Component를 클래스를 작성하는 것을 추천하지는 않습니다. 왜냐하면 클래스를 통해 상속을 하는 것보다 함수를 활용하여 컴포넌트끼리 합성하는 방법이 더 쉽고, 직관적이며, 에러방지에 효과적이기 때문입니다.

     

    Mounting

    class Welcome extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }

    Render()

    render()

    render() 메서드는 클래스 컴포넌트를 사용시 무조건 구현해야하는 유일한 메서드입니다.

    이 안에는 아래 리스트 중 하나를 작성할 수 있습니다.

    • 리액트 엘리먼트(ex. jsx, <div />, <Component />등)
    • 배열과 Fragment (ex. [1, 2, 3], <table />, <td />)
    • Portal (createPortal()를 활용하여 부모 컴포넌트 밖의 DOM 노드에 접근하기 위해 사용. 주로 Modal 만들 때 사용)
    • 문자열과 숫자 (ex. "abc", 100 - 텍스트 노드 렌더링됨)
    • Boolean 혹은 null (false && <Child /> 패턴을 활용하여 <Child /> 컴포넌트를 유기적으로 렌더링할지말지 때 사용)

     

    Constructor()

    constructor(props)

    Constructor은 메서드를 바인딩하거나 state를 초기화할 때 사용합니다. 두개의 작업이 필요없다면 사용 안해도 됩니다.

    constructor(props) {
      super(props);
      // 여기서 this.setState()를 호출하면 안 됩니다!
      this.state = { counter: 0 };
      this.handleClick = this.handleClick.bind(this);
    }

    주의할 점

    • super(props)를 가장 앞에  작성해야한다! 작성을 안하면 this.props가 Contructor 내에서 정의가 되지 않는다!
    • this.state = { color: props.color }; state 내에 props를 넣으면 안된다! 안그럼 버그가 일어난다!
    • Constructor 내에서는 this.setState()를 사용 못한다! Contructor 밖에 필요시 사용해라!
    • state 초기화와 메서드 바인딩만 작성해라! 다른거 하고 싶으면 componentDidMount()에 작성해라!

     

    static getDerivedStateFromProps()

    static getDerivedStateFromProps(props, state)

    getDerivedStateFromProps()는 render() 메소드를 호출하기 전에 state의 객체를 반환하는 메소드입니다. 이 메소드의 유일한 목적은 props의 변화에 따라 Component 내부 state의 값을 업데이트하여 변화를 주기 위함입니다. 렌더링할때마다 이 메소드는 호출되기에 주로 애니메이션 등 props의 변화에 따라 state값을 업데이트할 때 사용합니다.

    class ExampleComponent extends React.Component {
      // Initialize state in constructor,
      // Or with a property initializer.
      state = {
        isScrollingDown: false,
        lastRow: null,
      };
    
      static getDerivedStateFromProps(props, state) {
        if (props.currentRow !== state.lastRow) {
          return {
            isScrollingDown: props.currentRow > state.lastRow,
            lastRow: props.currentRow,
          };
        }
    
        // Return null to indicate no change to state.
        return null;
      }
    }

    위의 예시를 보자면 렌더링 하기 직전에 바뀐 props를 받아 비교하여 필요시 state를 수정하여 리턴하는 형태로 사용하는 것을 볼 수 있습니다.

     

    componentDidMount()

    componentDidMount()

    componentDidMount()는 컴포넌트가 마운트 된 직후에 호출합니다. 주로 추가적으로 외부에서 데이터를 불러와야하거나 네트워크 요청을 할 때 사용합니다. 예를 들어 fetch API를 사용할 때 componentDidMount() 단계에서 작성하는 것이 가장 바람직합니다.

     

    만약에 외부에서 데이터를 불러왔다면 Unmounting 단계에서 componentWillUnmount()를 사용하여 fetch를 취소한다는 것을 잊으면 안됩니다!

     

    setState()를 사용할 수 있지만 렌더링을 다시 한번 일으키기에 필요할 때에만 사용하도록 합시다!

     

    Updating

    shouldComponentUpdate()

    shouldComponentUpdate(nextProps, nextState)

    shouldComponentUpdate()는 getDerivedStateFromProps()와 render() 사이에 껴있는 것을 볼 수 있습니다. 즉 이미 props와 state가 바뀌었지만 아직 렌더링하지 않은 상태입니다. 이 단계에서는 props와 state가 실제로 바뀌어서 render()를 호출해야하는지 아닌지를 판별하기 위해, 불필요한 렌더링을 방지하기 위해 사용하는 메소드입니다. 

     

    예를 들어, 현재 state=1인 상태에서 제가 state=1로 만드는 버튼을 만들었다고 합시다. state의 값이 실질적으로 바뀌지 않았지만, 리액트에는 state의 값을 주는 행동이 취해졌기에 state가 바뀌었다고 생각하여 렌더링을 하게 되어있습니다. 이런 경우 shouldComponentUpdate()를 통해 state의 값이 바뀌지 않았음을 판별한 후 false를 리턴하여 render()와 componentDidUpdate()를 실행하지 않습니다. 

     

    그 외

    •  forceUpdate() 사용시 shouldComponent()는 호출되지 않습니다.
    • 디폴트 값은 true입니다.
    • Component Class가 React.PureComponent 상속 시 shouldComponent()를 직접 작성하지 않아도 자동적으로 얕은 비교를 하여, 갱신작업을 건너뛸 수도 있습니다.
    • shouldComponentUpdate()에서 깊은 비교를 하거나 JSON.stringify()를 사용을 하면 성능이 떨어지기에 추천하지 않습니다.
    • 현재 shouldComponentUpdate()에서 false를 리턴할 시 무조건 render()와 componentDidUpdate()를 실행하지 않지만, 가까운 미래에는 false를 반환하더라도 힌트로만 차용하여 render()을 실행할 수도 있을 예정이라고 합니다. 

     

    componentDidUpdate()

    componentDidUpdate(prevProps, prevState, snapshot)

    ComponentDidUpdate()는 없데이트 직후에 불러옵니다. ComponentDidMount()와 마찬가지로 데이터를 불러오거나 네트워크 요청을 할 때 사용합니다. 

    ComponentDidUpdate(prevProps) {
      // Typical usage (don't forget to compare props):
      if (this.props.userID !== prevProps.userID) {
        this.fetchData(this.props.userID);
      }
    }

    위 예시와 같이 조건문을 사용하여 외부 데이터를 불러오도록 해야합니다. 만약 조건문을 사용하지 않는다면 무한반복이 일어날 수 있습니다. 만약에 getSnapShotBeforeUpdate()를 사용했다면 snapshot 파라미터를 사용할 수 있습니다.

     

    getSnapShotBeforeUpdate()

    getSnapshotBeforeUpdate(prevProps, prevState)

    말 그대로 리액트가 DOM을 업데이트 하기 직전, 몇몇 필요한 값들을 저장하기 위해 사용하는 메서드입니다.

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

    componentDidUpdate()에서 할 수 있는 일 아닌가? 라고 생각할 수 있지만, DOM에 업데이트 하기 전과 후에는 딜레이가 발생할 수 있어 스크롤값과 같이 필요한 값이 빨리 바뀌는 경우에는 이전 값을 저장을 해야하기에 필요한 메소드라고 합니다. 

     

    Unmounting

    componentWillUnmount()

    componentWillUnmount()

    componentWillUnmount()는 컴포넌트가 마운트 해제되어 제거되기 직전에 호출합니다. 이 메서드 내에서는 타이머 제거, 네트워크 요청 취소, 외부 데이터 fetch 취소 등의 정리 작업을 하기 존재합니다. 이 컴포넌트는 다시 렌더링되지 않기에 setState()를 호출하면 안됩니다. 만약에 하게 되면 에러가 나게 되는데... 사실 해당 에러 때문에 컴포넌트의 생명주기를 정리하게 됐습니다!

     

    참고자료

    https://ko.reactjs.org/docs/react-component.html

    '리액트' 카테고리의 다른 글

    Portals  (0) 2021.09.08
    Context  (0) 2021.09.05
    Lists & Keys & Reconciliation  (0) 2021.08.29
    JSX & createElement() & reactDOM.render()  (0) 2021.08.24
    Hooks (useState, useEffect)  (0) 2021.08.23
Designed by Tistory.