컴포넌트는 클래스형 컴포넌트와 함수형 컴포넌트 두 가지로 나뉘는데, 이 포스트는 클래스형 컴포넌트에 대해 다룹니다.
클래스형 컴포넌트는 이전 포스트에서 설명했던 생명주기(Life cycle) 함수와 컴포넌트 구성 요소를 모두 포함하고 있습니다.
특히 클래스형 컴포넌트는 React.Component
와 React.PureComponent
라는 두 종류의 클래스를 사용하는데 이 두 클래스의 특징과 차이점에 대해 알아보려 합니다.
먼저 아래는 우리에게 익숙한 React.Component
를 상속한 일반적인 클래스형 컴포넌트의 예시입니다.
import React from 'react';
class ExpComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
sayHello: "Hello World"
}
}
render() {
return (
<div>{this.state.sayHello}</div>
);
}
}
export default ExpComponent;
constructor
에서 props
를 전달받기 위해 super(props)
를 호출하는 작업을 하고, this.state
에 객체를 할당해 local state 를 리셋해줍니다.
또 이벤트 처리 메서드의 바인딩을 constructort
에서 해줄 수도 있습니다. (ES6 Arrow-function 으로 작성도 가능합니다.)
그리고 전 포스트에서 다룬 여러 Life cycle 메서드를 통해 렌더되는 데이터를 관리할 수도 있습니다.
그렇다면 PureComponent
를 상속하는 것은 어떠한 차이가 있을까요?
React.PureComponent
는 React.Component
와 비슷하지만 한 가지 차이점이 있습니다. 바로 Life cycle 메서드 중 shouldComponentUpdate()
에 관한 것입니다.
React.Component
에서는 shouldComponentUpdate
의 기본값이 true 이기 때문에 매 state 나 props 갱신 시에 렌더링 발생 직전에 호출됩니다.
131 페이지에 실린 예시 코드를 수정하여 사용해보겠습니다.
import React from "react";
class MyComponent extends React.Component {
constructor(props) {
super(props);
}
componentDidUpdate() {
console.log("MyComponent 새로 고침");
}
render() {
return (
<div>
<div>MyComponent</div>
{this.props.value.map(item => {
return <span key={item.name}>{item.name} </span>;
})}
</div>
);
}
}
class MyPureComponent extends React.PureComponent {
constructor(props) {
super(props);
}
componentDidUpdate() {
console.log("MyPureComponent 새로 고침");
}
render() {
return (
<div>
<div>MyPureComponent</div>
{this.props.value.map(item => {
return <span key={item.name}>{item.name} </span>;
})}
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.listValue = [{ name: "Park" }, { name: "Lee" }];
this.state = { version: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
setTimeout(() => {
this.listValue[0].name = "Justin1";
this.setState({ version: 1 });
}, 100);
setTimeout(() => {
this.listValue = [{ name: "Justin2" }, { name: "Lee" }];
this.setState({ version: 2 });
}, 200);
}
render() {
return (
<div className="body">
<MyComponent value={this.listValue} />
<MyPureComponent value={this.listValue} />
<button onClick={this.handleClick}>버튼</button>{" "}
</div>
);
}
}
위 예시에서 state 변화가 일어나는 곳은 handleClick()
안에 있는 두 번의 setTimeout()
함수입니다.
React.Component
를 상속한 클래스 컴포넌트라면 각 setState
후에 컴포넌트 업데이트가 일어나 componentDidUpdate()
안의 콘솔이 두 번 출력됩니다.
그런데 React.PureComponent
를 상속한 클래스 컴포넌트는 두 번째 setTimeout()
안의 변화만을 감지하고 업데이트합니다. 왜 그럴까요?
이유는 React.PureComponent
의 shouldComponentUpdate
함수가 shallow-compare, '얕은 비교'를 하도록 재정의 되어있기 때문입니다.
shallow-equal
라이브러리를 사용한 예시(p. 129, 130)를 들어 설명하자면,
import shallowEqual from 'shallow-equal';
const obj = { name: 'park' };
const myList = [1, 2, 3, obj];
const list1 = [1, 2, 3, obj];
const list2 = [1, 2, 3, { name: 'park' }];
myList
와 list1
은 같은 요소들을 가지고 있지만 각각 새롭게 선언되어 정의된 배열들이기에 일반적인 비교 연산자(Comparison Operator) 로 비교하면 false 를 반환합니다. (shallow/deep copy 의 특징을 생각해보면 object 와 같은 경우는 reference-주소값을 비교합니다. shallow comparison 을 이해하시는데 도움이 될 것 같아 적어보았습니다.)
배열 안의 요소들을 모두 비교하며 검사해볼 수도 있지만 이는 성능에 영향을 미칠 것입니다. 이럴때 shallowEqual
함수를 이용하여 비교하면,
shallowEqaul(myList, list1) //true
true 를 반환하게 됩니다.
마찬가지로 list1
과 list2
를 비교해보면,
shallowEqaul(list1, list2) //false
false 를 반환하게 됩니다.
list2
의 마지막 요소는 obj
와 같은 내용을 포함하지만 새로운 객체로서 list2
에 존재하고 있습니다.
이렇게 React.PureComponent
는 Life cycle shouldComponentUpdate()
메서드에서 얕은 비교를 하여 데이터의 갱신과 화면 출력을 관리합니다. 특히 불변 변수(Immutable Variable) 을 사용하여 비교 속도를 줄이고 성능을 높일 수도 있습니다.
이번은 클래스형에 대한 내용이었으니 다음 포스트 주제는 함수형 컴포넌트에 관해 작성하려 합니다. 공부중인 책에는 React Hooks 에 대한 내용이 없는 것 같아 Hooks 도 포함하여 작성 예정입니다.
Do it! 리액트 프로그래밍 정석
https://velog.io/@lesh/PureComponent%EC%99%80-componentShouldUpdate-9cjz3nh0v1