다시 도전해보는 원티드 프리온보딩 프론트엔드 챌린지 4월 과제. 이번 4월 챌린지에서는 초심으로 돌아가 리액트의 전격 해부를 주제로 하여 2주간 챌린지를 진행한다.
리액트는 현재, 클래스형에 대한 지원을 중단하고 함수형으로 앱을 제작할 것을 권고하고 있다. 따라서 클래스형에서만 사용이 가능한 라이프사이클 메서드를 현업에서 쓸 일은 아마 많지 않을 것이며, 있다고 해도 16이하 버전으로 제작된 레거시 프로젝트에서 사용할 가능성이 높다.
하지만 프론트엔드 개발자로, 리액트를 다루고자 한다면 라이프사이클의 개념 정도는 내재하고 있어야 '누수' 없는 개발이 가능하다.
회사 서버의 과부하 원인 순위권에 신입 프론트엔드 개발자가 든다는 우스갯소리가 있다. 뭔가 변경했을때 바로 반영되는 진짜DOM과 달리 가상DOM에서는 개발자가 재렌더링을 발생시켜주어야 한다.
이때 useEffect를 지역 컴포넌트에서 남발하거나, 이벤트를 전역으로 불러오고 구독을 해지해두지 않는 등 생명주기에 대한 '이해'가 동반되지 않는 코딩을 한다면 그것은 불필요한 리소스 소모와, 나아가서는 잠재적인 페이지 크러시 및 고객 이탈 등의 결과를 불러올 수 있다.
React 17에서는 일부 라이프사이클 메서드들이 제거되었으며, 더 간단하고 예측 가능한 동작을 제공하는 새로운 메서드들이 도입되었다.
componentWillMount(): 컴포넌트가 DOM에 추가되기 전에 호출되는 메서드. 대신 컴포넌트가 DOM에 추가된 이후 호출되는 componentDidMount()를 사용하도록 권장된다.
componentWillReceiveProps(nextProps): props가 변경될 때 호출되는 메서드. 역시 React 16 이후부터는 사용이 권장되지 않으며 대신, static getDerivedStateFromProps()와 componentDidUpdate()를 사용하면 좋다.
componentWillUpdate(nextProps, nextState): 컴포넌트가 업데이트되기 전에 호출되는 메서드. 이 메서드 대신 static getDerivedStateFromProps()와 getSnapshotBeforeUpdate()를 사용할 수 있다.
사라진 메서드들의 공통점은 will이 들어간다는 것이다. 이러한 비동기적 렌더링을 지원하는 메서드들은 불필요하고 예측하기 어려우며 사이드이펙트를 발생시킬 수 있다.
그래서 새로 나온 메서드들은 '예측' 하는 대신에 '이전 상태'와 '다음 상태'를 비교하는 더 확실한 방법을 사용하도록 함으로써 비동기적인 렌더링시 예측 가능한 동작을 수행할 수 있으며, 불필요한 렌더링을 방지하여 성능또한 상대적으로 우수해질 수 있게 되었다.
벨로퍼트님의 라이프사이클 포스트를 첨부한다.
예전에도 그랬고 지금도 그렇듯이 라이프사이클은 3가지의 생명주기를 가졌다. 생성과 업데이트, 소멸이 그것이다.
constructor(): 컴포넌트가 생성될 때 호출되는 메서드. 가장 먼저 실행되며, 초기 상태를 설정하거나 이벤트 핸들러를 등록하는 등의 작업을 수행한다.
static getDerivedStateFromProps(): props로부터 state를 가져오는 메서드. 이 메서드는 컴포넌트가 생성되고 렌더링되기 전에 한번 호출되며, 이후 props가 변경될 때마다 계속 호출된다.
- 다른 메서드와 달리 앞에 static 을 필요로 함
- 이 안에서는 this를 조회 할 수 없음
- 객체를 반환하면 그 컨텐츠가 컴포넌트의 state 로 설정됨
- null 을 반환하게 되면 아무 일도 발생하지 않는다.
render(): UI를 렌더링
componentDidMount(): 컴포넌트가 DOM에 추가되고 나서 = 렌더링이 완료되고 나서 호출되는 메서드. 외부 데이터를 가져오거나 다른 라이브러리와 연동하는 등의 작업을 수행한다.
D3, masonry 처럼 DOM 을 사용해야하는 외부 라이브러리 연동을 하거나, 해당 컴포넌트에서 필요로하는 데이터를 요청하기 위해 axios, fetch 등을 통하여 ajax 요청을 하는 등의 작업
static getDerivedStateFromProps(): props로부터 state를 가져오는 메서드. 이 메서드는 컴포넌트가 업데이트될 때도 호출된다.
shouldComponentUpdate(): 컴포넌트를 리렌더링 할지 말지 여부를 결정하는 메서드. 성능 최적화를 위해서는 이 메서드로 작업을 하면 된다. 함수형의 useMemo와 비슷한 효과.
render(): UI를 렌더링
getSnapshotBeforeUpdate(): 렌더링 후 DOM을 조작하기 직전에 호출되는 메서드. 스크롤 위치나 뷰포트 크기 등의 값을 저장할 수 있다.
컴포넌트에 변화가 일어나기 직전의 DOM 상태를 가져와서 특정 값을 저장하면 -> 그 다음 발생하게 되는 componentDidUpdate 함수에서 받아와서 사용할 수 있다.
함수형 컴포넌트 + Hooks 를 사용 할 때에는 이 getSnapshotBeforeUpdate 를 대체 할 수 있는 기능이 아직 없다고 한다.
componentDidUpdate(): 마지막으로 컴포넌트가 업데이트된 이후 호출되는 메서드. 외부 데이터를 다시 가져오거나, getSnapshotBeforeUpdate에 저장된 값을 쓰거나 다른 라이브러리와 연동하는 등의 작업을 수행한다.
componentWillUnmount(): 컴포넌트가 DOM에서 제거되기 전에 호출되는 메서드이다. 이벤트 핸들러를 해제하거나 주로 DOM에 직접 등록했었던 이벤트를 제거하고, 외부 라이브러리를 사용한게 있고 해당 라이브러리에 dispose 기능이 있다면 여기서 호출할 수 있다.
작년 10월 쯤, 리액트 쿼리에 에러바운더리를 적용하기 위해 여러 레퍼런스를 찾아본적이 있었다.
리액트 공식문서의 설명으로는 에러 바운더리는 리액트의 라이프사이클을 이용하기 때문에 클래스형에서만 사용이 가능하다는 설명이었고, 나는
과제 주제: 함수형 컴포넌트로 제작할 것
이라는 조건을 지키기 위해 외부 패키지(react-error-boundery)를 사용하였다. 해당 패키지는 리액트 쿼리 공식문서에서 추천하고 있는 라이브러리이다.
그리고 몇개월이 지난 지금 찬찬히 읽어보니, 그때는 이해가 잘 가지 않았던 원리에 대한 설명이 눈에 들어오는 것을 알수 있었다.
https://react.vlpt.us/basic/26-componentDidCatch-and-sentry.html
센트리와 연동하여 에러리포트를 받는 방법을 설명한 글이다.
정보는 항상 그 자리에 있는데 다만 내가 소화시킬 수 있느냐가 중요한 듯 하다. 6개월 전 작성한 코드가 부끄럽다면 개발자로서 잘 성장해나가고 있는거라고 멘토님이 그랬는데 지금 조금 부끄럽긴 하다.
나는 과연 잘하고 있는가, 라는 의문을 가지며 끝없이 노력하고 정진해야겠다는 생각이 들었다.