이 글은 Velopert 님의 인프런 - 누구든지 하는 리액트 강의 를 참고하여 작성한 글입니다. 혹시 저작권 상의 문제가 발생할시에 내리도록 하겠습니다.
개요
리액트 강의를 듣고 기본 개념을 정리하고 기억하기 위해 작성하였습니다. 공부하시는 다른 분들께 도움이 되었으면 합니다.
LifeCycle API
LifeCycle API 는 컴포넌트가 우리의 브라우저에서 나타날 때, 사라질 때, 업데이트 될 때 호출되는 API 입니다.
LifeCycle API는 전부 외울 필요는 없고, 이러한 종류들이 있어서 특정 상황에선 이 API를 사용해야겠다는 정도만 숙지하면 충분합니다. 하나하나 살펴보겠습니다.
Mounting, Updating, Unmounting 가 의미하는 것은 각각 다음과 같습니다.
- Mounting : 컴포넌트가 브라우저에 나타날 때
- Updating : 컴포넌트의 props, state 에 바뀌었을 때
- Unmounting : 컴포넌트가 브라우저에서 사라질 때
그리고 render 함수는 다음과 같은 일을 합니다.
- render 함수 : 어떤 DOM을 만들게 될지, 내부에 있는 태그들은 어떤 값을 전달해주게 될지를 정의해줌
컴포넌트 초기 생성
컴포넌트가 브라우저에 나타나기 전, 후에 호출되는 API 들이 있습니다.
constructor
컴포넌트의 생성자 함수로, 컴포넌트가 새로 만들어질 때마다 호출됩니다.
super를 쓰는 이유는 컴포넌트를 만들게 되면서 Component를 상속받았고, 여기에 constructor를 작성하게 되면 기존의 클래스 생성자를 덮어쓰게 됩니다. 그러므로 React Component가 지니고 있떤 생성자를 super를 통해서 미리 실행하고 그 다음에 우리가 할 작업을 해주는 겁니다.
컴포넌트가 만들어지는 과정에서 미리 해야 될 작업들이 있다면 constructor에서 처리를 합니다.
componentWillMount
컴포넌트가 우리의 화면에 나가기 직전에 호출되는 API 로, 리액트 v16.3 에서 해당 API는 deprecated 되었습니다. 원래는 주로 브라우저가 아닌 환경(서버사이드)에서도 호출하는 용도로 사용했었습니다. v16.3 이후부턴
UNSAFE_componentWillMount() 라는 이름으로 사용됩니다. 기존에 해당 API에서 하던 것들은 constructor 와 아래에서 다룰 componentDidMount 에서 충분히 처리할 수 있습니다.
componentDidMount
우리의 컴포넌트가 화면에 나타나게 됐을 때 호출됩니다. 여기선 주로 D3, masonry 처럼 DOM을 사용해야 하는 외부 라이브러리 연동을 하거나, 해당 컴포넌트에서 필요로 하는 데이터를 요청하기 위해 axios, fetch 등을 통하여 ajax 요청을 하거나, DOM의 속성을 읽거나 직접 변경하는 작업을 진행합니다. 예를 들어 컴포넌트가 화면에 나타난 다음, 특정 DOM 의 위치에 차트를 그리는 코드를 작성하거나 해당 DOM의 스크롤 이벤트를 리스닝하고 싶을 때 사용할 수 있습니다.
컴포넌트 업데이트
컴포넌트 업데이트는 props 의 변화, state 의 변화에 따라 결정됩니다. 업데이트 되기 전과 된 후에 어떤 API 들이 호출되는지 살펴보겠습니다.
componentWillReceiveProps
컴포넌트가 새로운 props 를 받게됐을 때 호출됩니다. 이 안에서는 주로 state 가 props 에 따라 변해야 하는 로직을 작성합니다. 새로 받게될 props는 nextProps로 조회할 수 있고, 이 때 this.props 를 조회하면 업데이트 되기 전의 API 이니 참고하십시오. 이 API는 v16.3 부터 deprecate 됩니다. v16.3 부턴 UNSAFE_componentWillReceiveProps() 라는 이름으로 사용됩니다. 그리고 이 기능은 상황에 따라 새로운 API인 getDerivedStateFromProps 로 대체 될 수도 있습니다.
[NEW] static getDerivedStateFromProps()
이 함수는 v16.3 이후에 만들어진 라이프사이클 API 입니다. 이 API는 props 로 받아온 값을 state 로 동기화 하는 작업을 해줘야 하는 경우에 사용됩니다.
shouldComponentUpdate
컴포넌트가 업데이트 되는 성능을 최적화 시키고 싶을 때 아주 유용하게 사용되는 API입니다. 정말 중요합니다.
리액트에선 부모 컴포넌트가 리렌더링을 되면 자식 컴포넌트도 전부 다 render 함수를 실행하게 되어있습니다. 근데 이 작업들이 가끔씩 불필요해질 때가 있습니다.
리액트를 소개하는 부분에서 리액트는 변화가 발생하는 부분만 업데이트를 해줘서 성능이 꽤 잘 나온다고 얘기한 적이 있습니다. 하지만, 변화가 발생한 부분만 감지해내기 위해선 Virtual DOM에 한번 그려줘야 합니다.
Virtual DOM에 한번 그려준다는 것은, 현재 컴포넌트의 상태가 업데이트 되지 않아도 부모 컴포넌트가 리렌더링되면 자식 컴포넌트들도 렌더링 됨을 말합니다. 여기서 "렌더링" 된다는 것은 render() 함수가 호출된다는 의미입니다.
변화가 없으면 물론 DOM 조작은 하지 않게 됩니다. 그저 Virtual DOM에만 렌더링 할 뿐입니다. 이 작업은 그렇게 부하가 많은 작업은 아니지만 만약 컴포넌트가 무수히 많이 렌더링된다면 얘기가 달라집니다. CPU 자원을 어느정도 사용하게 될테니까요.
쓸데없이 낭비되고 있는 이 CPU 처리량을 줄여주기 위해서 우리는 Virtual DOM 에 리렌더링 하는 것도 불필요한 경우 방지 해주기 위해 shouldComponentUpdate 를 작성합니다.
이 함수는 기본적으로 true 를 반환하여 render 함수를 호출합니다. 만약 우리가 따로 작성을 해주어서 조건에 따라 false 를 반환하면 해당 조건에는 render 함수를 호출하지 않습니다.
[NEW] getSnapshotBeforeUpdate()
이 API가 발생하는 시점은 다음과 같습니다.
- render()
- getSnapshotBeforeUpdate()
- 실제 DOM 에 변화 발생
- componentDidUpdate
즉, 렌더링을 통한 결과물이 브라우저에 반영되기 바로 직전에 호출되는 함수입니다.
이 API를 통해서 DOM 변화가 일어나기 직전의 DOM 상태를 가져오고 여기서 리턴하는 값은 componentDidUpdate 에서 3번째 파라미터로 받아올 수 있게 됩니다. 주로 렌더링 후 업데이트 하기 직전에 해당 DOM의 크기나 스크롤의 위치를 사전에 구하고 싶은 작업에서 사용됩니다.
componentDidUpdate
작업을 마치고 컴포넌트가 update 되었을 때 호출되는 API 입니다. 이 시점에선 this.props와 this.state 가 바뀌어있습니다. 그리고 파라미터를 통해 이전의 값인 prevProps와 prevState 를 조회할 수 있습니다. 그리고 getSnapshotBeforeUpdate 에서 반환한 snapshot 값은 세번째 값으로 받아올 수 있습니다.
주로 업데이트 되기 전의 상태와 현재의 상태가 달라서 특정 작업을 수행하고 싶을 때 사용됩니다.
컴포넌트 제거
컴포넌트가 더 이상 필요하지 않게 되면 단 하나의 API가 호출됩니다 :
componentWillUnmount
컴포넌트가 사라지는 과정에서 호출되는 API 입니다. 이 API 는 앞서 componentDidMount API에서 이벤트를 리스닝 하는 작업 등을 제거할 때 사용될 수 있습니다. 예를 들어 componentDidMount 에서 스크롤 이벤트 등을 리스닝 하게 했었다면 componentWillUnmount 에선 그 설정한 리스너를 지울 수 있고, 또 만약 setTimeout 을 걸은 것이 있다면 clearTimeout 을 통하여 제거를 합니다. 추가적으로 외부 라이브러리를 사용한 게 있고 해당 라이브러리에 dispose 기능이 있다면 여기서 호출해주면 됩니다.
직접 사용해보기
이 부분은 강의를 직접 들으면서 따라하는 것이 효과가 좋습니다. 해당 부분은 코드를 올려놓을테니 따라 쳐보면서 학습하시길 바랍니다.
그리고 getSnapshotBeforeUpdate와 관련해서 다음코드를 확인하세요. (https://codesandbox.io/s/484zvr87ow)
getSnapshotBeforeUpdate는 업데이트 되기 바로 직전의 DOM 상태를 반환하고 componentDidUpdate 에서 그 값을 받아서 사용할 수 있습니다.
click me 를 누르면 박스가 추가되면서 스크롤 영역이 생깁니다. Chrome 의 경우 박스가 추가되더라도 사용자가 보고 있는 똑같은 스크롤 위치를 잡아주는 기능이 자체적으로 있지만, Firefox 나 Edge, Safari와 같은 다른 브라우저들은 기존 스크롤 위치를 기억하지 않으므로 박스가 추가됨에 따라 스크롤 위치도 위로 딸려갑니다.
그래서 getSnapshotBeforeUpdate로 업데이트 되기 직전의 스크롤 위치와 크기를 가져와서 그 값을 반환하고, componentDidUpdate의 snapshot 매개변수로 그 값을 받아 스크롤 위치를 고정할 수 있습니다.
컴포넌트에 에러 발생
render 함수에서 에러가 발생한다면, 리액트 앱이 크래쉬 되버립니다. 그러한 상황에서 유용하게 쓸 수 있는 API가 한 가지 있는데 알아보겠습니다.
componentDidCatch
componentDidCatch는 우리가 실수로 잡지 못했던 버그들을 잡을 때 사용합니다. 그래서 사용자들에게 '오류가 발생했습니다' 라는 메시지를 보여주고, 개발자들에게 해당 에러에 대한 정보를 보여주는 작업을 합니다.
componentDidCatch API를 사용할 때의 주의점이 있는데요,
componentDidCatch 를 사용해 에러를 잡는 코드를 구현할 때는 에러가 발생한 컴포넌트 자체에서 할 순 없고, 에러가 발생할 수 있는 컴포넌트의 부모 컴포넌트에서 구현을 해줘야 합니다. 컴포넌트 자신의 render 함수에서 에러가 발생한 것은 잡을 수 없습니다.
componentDidCatch는 에러가 발생할 수 있는 컴포넌트의 부모 컴포넌트에서 구현을 해줘야 한다
다음 코드를 보세요.
만약 컴포넌트에서 존재하지 않는 값을 보여주려고 한다면 위 예제처럼 에러가 납니다. 지금은 missing 이라는 값이 props로 전달하지도 않아서 undefined 인데 객체인 마냥 값을 쓰려고해서 에러가 발생합니다.
이 에러를 componentDidCatch 로 잡아봅시다.
componentDidCatch의 매개변수인 error와 info 는 각각 다음과 같습니다.
error 는 어떤 에러가 발생했는지를 알려주는 것이고, info 는 그 에러가 어디서 발생했는지를 알려줍니다.
(추가적으로 ref를 이용해서 div 요소의 height 를 받아오는 코드를 주석처리 해줘야 동작합니다. 그렇지 않으면 크로쓰 오리진 오류가 뜹니다 => App이 마운트하며 렌더링을 하는 과정에서 자식 컴포넌트인 MyComponent에 에러가 있으므로 렌더링을 제대로 하지 못한 상태입니다. 그런데 componentDidMount에서 렌더링이 제대로 되지 않은 상태인데 DOM 요소에 접근을 하니 에러를 뱉는 것입니다 )
다음과 같이 작성해주어 사용자에게 '에러가 발생했어요!' 는 페이지를 보여줍니다.
componentDidCatch가 실행되면 state의 error 를 true로 만들면서, render 함수가 실행될 때 error가 true이면 '에러가 났어요!' 문구를 띄웁니다.
보통 렌더링 부분에서 오류가 발생하는 것은 사전에 방지해주어야 하는데, 오류들이 자주 발생하는 이유는 다음과 같습니다.
1. 존재하지 않는 함수를 호출하려고 할 때(props로 받았을줄 알았던 함수가 전달되지 않았을 때)
2. 배열이나 객체가 올 줄 알았는데 해당 객체나 배열이 존재하지 않을 때
이러한 것들은 render 함수에서 다음과 같은 형식으로 막아줄 수 있습니다.
혹은 우리가 이전에도 배웠었던 컴포넌트의 기본값을 설정하는 defaultProps를 통해서 설정하면 됩니다.
하지만 이걸로도 놓친 오류들은 componentDidCatch 를 통해서 잡아주고, 이렇게 오류가 발생했을 때 네트워크로 해당 정보를 특정 서버에 전달을 해주고 에러가 발생했음을 사용자에게 보여줄 수 있습니다.
지금까지 LifeCycle API에 대해서 살펴보았습니다. 처음 사용해본 사람들은 어렵고 익숙하지 않을 수 있으나 앞으로 사용을 하게 되면서 점점 익숙해질 것입니다. 이 API들을 알아두면 여러 상황에서 유용하게 쓸 수 있으니 어떤 API들일 있는지 인지해두고 나중에 해결해야 할 문제가 있을 때 사용하면 됩니다.
'FrontEnd > React 관련자료' 카테고리의 다른 글
리액트 기본 과정 정리 - 7 [배열 다루기 (1) 생성과 렌더링] (0) | 2019.12.11 |
---|---|
리액트 기본 과정 정리 - 6 [input 상태 관리하기] (0) | 2019.12.11 |
리액트 기본 과정 정리 - 4 [props와 state] (0) | 2019.12.08 |
리액트 기본 과정 정리 - 3 [JSX] (0) | 2019.12.06 |
리액트 기본 과정 정리 - 2 [리액트 프로젝트 시작하기] (0) | 2019.12.06 |
댓글