API는 컴포넌트가 브라우저에 나타날때, 사라질때, 그리고 업데이트 될 때 호출되는 API이다. 정말 중요한 역할 !
컴포넌트 초기 생성
일단, 컴포넌트가 브라우저에 나타나기 전, 후에 호출되는 API들이 있다.
constructor
constructor(props) {
super(props);
}
컴포넌트 생성자 함수이다. 컴포넌트가 새로 만들어질 때마다 이 함수가 호출된다.
componentWillMount
componentWillMount() {
}
이 API는 컴포넌트가 화면에 나타나기 직전에 호출되는 API인데, 별로 신경쓰지 않아도된다.
componentDidMount
componentDidMount() {
// 외부 라이브러리 연동: D3, masonry, etc
// 컴포넌트에서 필요한 데이터 요청: Ajax, GraphQL, etc
// DOM 에 관련된 작업: 스크롤 설정, 크기 읽어오기 등
}
이 API는 컴포넌트가 화면에 나타나게 됐을 때 호출된다. 이 부분에서는 주로 D3, masonry 처럼 DOM을 사용해야하는 외부 라이브러리 연동을 하거나, 해당 컴포넌트에서 필요로하는 데이터를 요청하기 위해 axios, fetch 등을 통해 Ajax 요청을 하거나, DOM의 속성을 읽거나 직접 변경하는 작업을 진행한다.
컴포넌트 업데이트
컴포넌트가 업데이트되는 props의 변화, 그리고 state의 변화에 따라 결정된다. 업데이트가 되기 전과 된 후에 어떠한 API가 호출될까?
componentWillReciveProps
componentWillReceiveProps(nextProps) {
// this.props 는 아직 바뀌지 않은 상태
}
이 API는 컴포넌트가 새로운 props를 받게되었을 때 호출된다. 이 안에서는 주로 state가 props에 따라 변해야하는 로직을 작성한다. 새로 받게될 props는 nextProps로 조회할 수 있으며, 이 때 this.props를 조회하면 업데이트가 되기 전의 상태를 의미한다. v16.3 이후에 getDerivedStateFomProps로 대체될 수 있다.
[NEW] static getDerivedStateFromProps()
static getDerivedStateFromProps(nextProps, prevState) {
// 여기서는 setState 를 하는 것이 아니라
// 특정 props 가 바뀔 때 설정하고 설정하고 싶은 state 값을 리턴하는 형태로
// 사용됩니다.
/*
if (nextProps.value !== prevState.value) {
return { value: nextProps.value };
}
return null; // null 을 리턴하면 따로 업데이트 할 것은 없다라는 의미
*/
}
이 함수는 v16.3 이후에 만들어진 라이프사이클 API인데, 이 API는 props로 받아온 값을 state로 동기화하는 작업을 해줘야하는 경우에 사용된다.
shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {
// return false 하면 업데이트를 안함
// return this.props.checked !== nextProps.checked
return true;
}
이 API는 컴포넌트를 최적화하는 작업에서 매우 유용하게 사용된다. 리액트는 변화가 발생하는 부분만 업데이트를 해주기 때문에 성능이 좋은 편인데, 변화가 발생한 부분만 감지해내기 위해서는 Virtual DOM에 한번 그려줘야된다.
즉, 현재 컴포넌트의 상태가 업데이트되지 않아도 부모 컴포넌트가 리렌더링되면, 자식 컴포넌트들도 렌더링된다. "렌더링" 된다는 것은 render() 함수가 호출된다는 의미이다. 변화가 없으면 DOM 조작은 하지 않게 된다. 그저 Virtual DOM에만 렌더링 할 뿐이다. 이 작업은 부하가 많은 작업은 아니지만, 컴포넌트가 무수히 많이 렌더링된다면 얘기가 달라진다. 쓸데없이 낭비되고 있는 이 CPU 처리량을 줄여주기 위해서 우리는 Virtual DOM에 리렌더링 하는 것이 불필요할 경우에는 방지하기 위해 shouldComponentUpdate를 사용한다. 이 함수는 기본적으로 true를 반환하는 함수이며, 조건에 따라 false를 반환하면, 해당 조건에는 render 함수를 호출하지 않는다.
componentWillUpdate
componentWillUpdate(nextProps, nextState) {
}
이 API는 shouldComponentUpdate에서 true를 반환했을 때만 호출된다. 만약에 shouldComponentUpdate가 false를 반환했다면 이 함수는 호출되지 않는다. 여기서는 주로 애니메이션 효과를 초기화하거나, 이벤트 리스너를 없애는 작업을 한다. 이 함수가 호출되고난 다음 render()가 호출된다. 이 API 또한 v16.3 이후 getSnapshotBeforeUpdate로 대체될 수 있다.
[NEW] getSnapshotBeforeUpdate()
getSnapshotBeforeUpdate(prevProps, prevState) {
// DOM 업데이트가 일어나기 직전의 시점입니다.
// 새 데이터가 상단에 추가되어도 스크롤바를 유지해보겠습니다.
// scrollHeight 는 전 후를 비교해서 스크롤 위치를 설정하기 위함이고,
// scrollTop 은, 이 기능이 크롬에 이미 구현이 되어있는데,
// 이미 구현이 되어있다면 처리하지 않도록 하기 위함입니다.
if (prevState.array !== this.state.array) {
const {
scrollTop, scrollHeight
} = this.list;
// 여기서 반환 하는 값은 componentDidMount 에서 snapshot 값으로 받아올 수 있습니다.
return {
scrollTop, scrollHeight
};
}
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot) {
const { scrollTop } = this.list;
if (scrollTop !== snapshot.scrollTop) return; // 기능이 이미 구현되어있다면 처리하지 않습니다.
const diff = this.list.scrollHeight - snapshot.scrollHeight;
this.list.scrollTop += diff;
}
}
이 API가 발생하는 시점은 다음과 같다.
1) render()
2) getSnapshotBeforeUpdate()
3) 실제 DOM에 변화 발생
4) componentDidUpdate
이 API를 통해, DOM 변화가 일어나기 직전의 DOM 상태를 가져오고, 여기서 리턴하는 값은 componenetDidUpdate에서 세번째 파라미터로 받아올 수 있게 된다.
(전체코드는 https://codesandbox.io/s/484zvr87ow 에서 확인 !)
componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot) {
}
이 API는 컴포넌트에서 render()을 호출하고 난 후 발생하게 된다. 이 시점에서는 this.props와 this.state가 바뀌어있다. 그리고 파라미터를 통해 이전의 값인 prevProps와 prevState를 조회할 수 있다. 앞서 말했듯이, getSnapshotBeforeUpdate에서 반환한 snapshot 값은 세번째 파라미터로 받아온다.
컴포넌트 제거
컴포넌트가 더이상 필요하지 않게되면, 단 하나의 API가 호출된다.
componentWillUnmount
componentWillUnmount() {
// 이벤트, setTimeout, 외부 라이브러리 인스턴스 제거
}
여기서는 주로 등록했었던 이벤트를 제거하고, 만약에 setTimeout 을 설정한 것이 있다면, clearTimeout을 통하여 제거를 하게된다. 추가적으로, 외부 라이브러리를 사용했고, 해당 라이브러리에 dispose 기능이 있다면 여기서 호출하면 된다.
컴포넌트 에러 발생
render 함수에서 에러가 발생한다면, 리액트 앱이 크래시(crash) 되어버린다. 그러한 상황에 유용하게 사용할 수 있는 API가 있다.
componenetDidCatch
componentDidCatch(error, info) {
this.setState({
error: true
});
}
에러가 발생하면 이런식으로 componentDidCatch가 실행되게 하고, state.error를 true로 설정하게 하고, render 함수 쪽에서 이에 따라 에러를 띄워주면 된다. 컴포넌트 자신의 render 함수에서 에러가 발생해버리는 것은 잡아낼 수 없지만, 자식 컴포넌트 내부에서 발생하는 에러들을 잡아낼 수 있다.
일단 문제가 발생하는 코드를 작성해보자 !
import React, { Component } from 'react';
const Problematic = () => {
throw (new Error('버그가 나타났다!'));
return (
<div>
</div>
);
};
class Counter extends Component {
// ... 생략
render() {
return (
<div>
<h1>카운터</h1>
<div>값: {this.state.number}</div>
{ this.state.number === 4 && <Problematic /> }
<button onClick={this.handleIncrease}>+</button>
<button onClick={this.handleDecrease}>-</button>
</div>
);
}
}
export default Counter;
Problematic 이라는 컴포넌트를 만들고 이 값이 4가 되면 렌더링을 하도록 설정했다. Problematic은 렌더링이 될 때 에러가 발생했음을 알리는 throw를 사용하게끔 했는데, 카운터 값이 4까지 올라갔을 때는 어떻게 될까?
여기서 이렇게 빨갛게 "Error: 버그가 나타났다!" 라고 뜨는 부분은 개발모드에서 제공해주는 기능이다. 프로덕션에 이 화면은 나타나지 않는다. 여기서 X를 눌러보면,
그냥 비어있는 페이지가 나타난다.
이제 componentDidCatch를 통하여 자식 컴포넌트에서 발생한 에러를 잡아보자.
import React, { Component } from 'react';
const Promblematic = () => {
throw (new Error('버그가 나타났다!'));
return (
<div>
</div>
);
};
class Counter extends Component {
state = {
number: 0,
error: false
}
// (...)
componentDidCatch(error, info) {
this.setState({
error: true
});
}
render() {
if (this.state.error) return (<h1>에러발생!</h1>);
return (
<div>
<h1>카운터</h1>
<div>값: {this.state.number}</div>
{ this.state.number === 4 && <Promblematic /> }
<button onClick={this.handleIncrease}>+</button>
<button onClick={this.handleDecrease}>-</button>
</div>
);
}
}
export default Counter;
다시 카운터 값을 4까지 올렸을 때, 빨간 에러창은 여전히 뜨게된다. 하지만 X 를 눌렀을 때 앱이 크래시되는 것이 아니라 에러가 발생했다는 메세지가 뜨게된다.
주로 자주 에러가 발생하는 이유
보통은 렌더링 부분에서 오류가 발생하는 것은 사전에 방지해주어야한다.
1. 존재하지 않는 함수를 호출하려고 할 때 (예를 들어, props로 받은 줄 알아던 함수가 전달되지 않았을 때)
this.props.onClick();
2. 배열이나 객체가 올 줄 알았는데, 해당 객체나 배열이 존재하지 않을 때
this.props.object.value; // object is undefined
this.props.array.length; // array is undefined
이러한 것들은 render 함수에서 다음과 같은 형식으로 막아줄 수 있다.
render() {
if (!this.props.object || !this.props.array || this.props.array.length ===0) return null;
// object 나 array 를 사용하는 코드
}
혹은, 이전에 말했던 컴포넌트의 기본값을 설정하는 defaultProps를 통해 설정하면 된다.
class Sample extends Component {
static defaultProps = {
onIncrement: () => console.warn('onIncrement is not defined'),
object: {},
array: []
}
}
'Progamming > ReactJS' 카테고리의 다른 글
[벨로퍼트] 배열 다루기(1) 생성과 렌더링 (0) | 2019.07.12 |
---|---|
[벨로퍼트] input 상태 관리하기 (0) | 2019.07.12 |
[벨로퍼트] props와 state (0) | 2019.07.11 |
[벨로퍼트] JSX (0) | 2019.07.05 |
[벨로퍼트] 리액트 프로젝트 시작하기 (0) | 2019.07.05 |
댓글