React는 기본적으로 언제 리렌더링 될까?
React에 대해 조금 지식이 있다면 잘 알다시피
아래와 같이 크게 2가지로 나눌 수 있다.
첫 번째, 컴포넌트의 상태(state), 프로퍼티(props) 변경되었을 때
두 번째, 상위 부모 컴포넌트가 변경되었을 때
첫 번째의 경우는 React의 가상 DOM(VDOM)에서
이전 VDOM과 비교해서 변경 된 곳만 실제 DOM에
업데이트 해주기 때문에 이 경우 신경 쓰지 않아도 된다.
하지만,
이 중 두 번째의 경우가 가장 문제일 수 있는데
부모 컴포넌트가 어떤 값이던 간에 바뀐다면
그 하위의 자식 컴포넌트 모두가 리렌더링 대상이 되기 때문이다
즉, 어떤 한 하위 자식 컴포넌트의 값이 변했을 때,
그와 관련 없는 또 다른 자식 컴포넌트 까지 렌더링 대상이 된다는 것 이다.
그렇기 때문에
여기서 우리는 최적화에 대한 문제에 직면하게 된다.
먼저 두 번째에 경우에 대해 이야기하기 전에
React의 이해를 돕기 위해 첫 번째의 경우 부터 이야기를 나누어보자.
첫 번째: 상태와 프로퍼티가 변경되었을 때
본론에 들어가기 앞서
먼저 VDOM에 대해 이야기를 해보자.
사실 React의 매력은 이 VDOM에 있다고 볼 수 있다.
왜냐하면 일반적인 DOM에서 내부 구성 변경되면
UI를 새로 다시 그리기 때문에
기본적으로 애플리케이션의 성능이 그렇게 썩 좋다고 보기는 힘들다.
하지만 이와 다르게 React의 VDOM은
업데이트 된 부분만 수정하기 때문에
일반 DOM을 활용한 시스템보다는
퍼포먼스 측면에서 유리하다고 할 수 있다.
이렇게 실제 DOM과 가상 VDOM을 동기하는 과정을
reconciliation(조정)이라고 하는 것 같지만
실제로는 diffing이라는 단어를 자주 사용하는 것 같다.
VDOM
[1] 에 대한 내용과 Reconciliation
[2] 에 대해서는
공식 도큐먼트에서 확인하기를 바란다.
VDOM에 대한 이야기도 조금 했으니
이제 본론으로 넘어가 보자.
여기서 상태(State)와 프로퍼티(props)가 무엇을 말하는 것일까?
React에 익숙하지 않다면, 다소 이해 하기 힘들 수 있다.
나는 이 포스트 바로 전에 React를 소개하는 글을 포스팅 했는데,
이 포스팅에는 버튼을 눌렀을 때,
카운트 횟수를 화면에 보여주는 앱이 있다.
class App extends React.Component {
state = { display: true };
delete = () => {
this.setState({ display: false });
};
render() {
let comp;
if (this.state.display) {
comp = <Hello />;
}
return (
<div>
<div>
{comp}
<button onClick={this.delete}>Hello Component unMount</button>
</div>
<Welcome />
</div>
);
}
}
위의 코드에서 상태의 변경은
state = { display: true };
delete = () => {
this.setState({ display: false });
};
이 부분이다.
버튼을 누를 때 마다,
delete 함수가 실행되고
내부 처리 부분
즉, setState에 의해 변수 display상태가 변경된다.
위에서 부터 설명에 따르면
이 부분에서 리렌더링이 일어
우리의 인식이 같다면 말이다.
다음은 프로퍼티에 대한 이야기이다.
class App extends React.Component {
render() {
return (
<NameTextDisplay firstName="kim" secondName="minsu"/>
);
}
}
const NameTextDisplay = ({firstName, secondName}:props) => {
return <p>`${firstName} ${secondName}`</p>;
};
위의 코드에서 프로퍼티에 대한 부분은
NameTextDisplay 컴포넌트의 내부 속성인 firstName와 secondName를 말한다.
({firstName, secondName}:props)
실제 자식 컴포넌트로 넘길 때
props 변수 안에 넣어서 보내기 때문에 위와 같은 코드가 필요하다.
단순히 props만 받는다면
props.firstName
props.secondName
위와 같은 방식으로 접근 할 수 있다.
물론 TypeScript까지 활용해서
타입(변수형)까지 설정해준다면 좀 더 완벽한 코드가 될 것이다.
TypeScript까지 이야기 하면
이 포스팅의 목적과 벗어나기 때문에 여기까지 하자.
어쨋든
이 코드는 처음 렌더링 될때 빼고는
값의 변경이 이루어지지 않기 때문에
리렌더링은 일어나지 않지만,
state로 변수를 넣어주고
이를 변경하는 코드를 추가한다면 리렌더링이 일어날 것 이다.
위에서 언급했듯이
이 부분은 React의 VDOM에서 자동적으로 처리해주기 때문에
크게 신경 쓸 필요는 없을 것이다.
두 번째: 상위 부모 컴포넌트가 변경되었을 때 그리고 문제점
이제 본격적인 내용으로 들어가보자.
문제에 대해서는 서론에서 이야기를 했기 때문에
좀 더 깊은 이해에 다가가기 위해
간단한 예를 통해 이야기를 나누어보자.
아래와 같이 현재 시간을 표시하는 컴포넌트를
대략적으로 설계했다고 가정해보자.
여기서 Clock Component는 부모 컴포넌트가 되며,
하위 DateDisplay와 TimeDisplay는 각각 자식 컴포넌트가 된다.
여기서 DateDisplay는 년월일을 보여주며,
TimeDisplay는 시분초를 보여주는 컴포넌트라 가정해보자.
내부 로직에 대해서는 잠시 머리속에 지워버리고 결과값에만 집중해보자.
우리가 시계에 대한 정의가 일치한다면
DateDisplay의 값은 비교적 그대로 인데 비해,
시간은 초 단위로 계속 바뀌기 때문에 TimeDisplay는 계속적으로 변하게 될 것이다.
문제는 TimeDisplay의 값이 변하면서 리렌더링 되는 것은 상관없지만,
불필요한 DateDisplay 까지 리렌더링 대상이 된다는 것이다.
따라서 이런 최적화 문제를 해결하기 위해서
값이 변하지 않았을 경우, DateDisplay가 리렌더링 되지 않도록 하는
솔루션이 우리에게 필요하다는 결론에 도달할 수 있다.
해결
다행히도 React는 이 문제에 대한 솔루션을 기본적으로 제공해준다.
바로 memo라는 메소드를 통해서 말이다.
사용 방법은 여러가지가 있을 수 있지만
위의 예라면 아래와 같이 해당 컴포넌트를 감싸주면 해결이 된다.
export default React.memo(DateDisplay);
이렇게 함으로써 TimeDisplay는 계속해서 값이 변하게 되므로
리렌더링이 이루어질 것이고,
DateDisplay는 날짜가 변하게 되면 값이 변하기 때문에
그 때만 값이 리렌더링 될 것이다.
간단한 예이기 때문에
코드로 살펴보자.
마치며
물론 위의 예제는 매우 간단한 예제로
사실 굳이 memo 메소드를 사용한다고 해도 성능에 큰 차이는 없을 것이다.
하지만,
우리가 개발해야하는 대량의 데이터를 처리해야하는
사용자가 많은 엔터프라이즈 급 시스템이라면 이야기가 다를 것 이다.
경우에 따라서 고작 몇 단어에 불과한
이 메소드를 추가하지 않았기 때문에 성능에 큰 문제를 야기할 수 있기 때문이다.
따라서 최적화 단계에 들어가 있는 React를 사용하고 있는 시스템이라면
이 memo 함수를 어디에 적절하게 사용하면
성능을 향상 시킬 수 있는지에 대해 생각해보는 것이 좋을 지도 모른다.