shouldComponentUpdate는 강력한 도구였지만, 동시에 양날의 검이었다. 만약 개발자가 이 메서드의 비교 로직을 잘못 작성하면, UI가 업데이트되어야 할 때 갱신되지 않는 심각한 버그를 낳을 수 있었다.
“this.props.story.id만 비교하는 건 너무 위험하지 않나요?”
맷이 <Story> 컴포넌트 예제를 보며 우려를 표했다. “만약 스토리의 내용은 바뀌었는데 ID는 그대로라면, 화면에는 예전 내용이 계속 보이게 될 텐데요.”
그의 지적은 정확했다. shouldComponentUpdate를 안전하게 사용하려면, nextProps와 nextState의 모든 속성을 현재의 props와 state와 꼼꼼하게 비교해야만 했다.
// 안전하지만, 번거로운 비교 로직
shouldComponentUpdate: function(nextProps, nextState) {
// props의 모든 키를 비교
if (this.props.title !== nextProps.title ||
this.props.content !== nextProps.content ||
this.props.author.id !== nextProps.author.id) {
return true;
}
// state의 모든 키를 비교
if (this.state.isExpanded !== nextState.isExpanded) {
return true;
}
// 모든 것이 같다면 업데이트하지 않는다.
return false;
}
이런 코드를 모든 컴포넌트에 반복해서 작성하는 것은 지루하고 오류를 일으키기 쉬운 일이었다. 팀은 이 문제를 해결할 더 나은 방법이 필요하다고 느꼈다.
바로 이때, 조던 워크가 함수형 프로그래밍에서 가져온 또 다른 아이디어, ‘불변 데이터(Immutable Data)’의 중요성이 다시 한번 빛을 발했다.
“만약 우리의 props와 state 데이터가 모두 불변(immutable)하다면, 우리는 이 복잡한 비교를 훨씬 더 간단하고 빠르게 할 수 있습니다.” 조던이 설명했다.
그는 두 가지 시나리오를 비교했다.
시나리오 1: 데이터가 가변적일 때 (Mutable)
- 부모 컴포넌트가
story객체를 가지고 있다. - 부모는
story객체의title속성만 직접 수정한다:story.title = 'New Title'; - 그리고 이 수정된
story객체를 자식에게props로 내려준다. - 자식의
shouldComponentUpdate에서this.props.story === nextProps.story를 비교하면, 결과는true가 나온다. 왜냐하면 두props는 같은 객체를 참조하고 있기 때문이다. 내용물은 바뀌었지만, 객체의 참조(주소)는 그대로다. 리액트는 변화를 감지할 수 없다.
시나리오 2: 데이터가 불변일 때 (Immutable)
- 부모 컴포넌트가
story객체를 가지고 있다. - 부모는
story객체를 직접 수정하는 대신,title만 다른 완전히 새로운story객체의 복사본을 만든다. - 그리고 이 ‘새로운’ 객체를 자식에게
props로 내려준다. - 자식의
shouldComponentUpdate에서this.props.story === nextProps.story를 비교하면, 결과는false가 나온다. 두props는 서로 다른 객체를 참조하고 있기 때문이다.
“불변 데이터를 사용하면, 객체의 속성을 하나하나 비교하는 ‘깊은 비교(Deep Comparison)’ 대신, 객체의 참조 주소만 비교하는 ‘얕은 비교(Shallow Comparison)’만으로도 변화를 감지할 수 있습니다.”
이 깨달음은 엄청난 성능 향상의 가능성을 열었다. 얕은 비교는 깊은 비교보다 수백, 수천 배 빠르기 때문이다.
팀은 이 아이디어를 바탕으로, 개발자들이 반복적인 비교 코드를 작성하지 않도록 도와주는 특별한 ‘믹스인(Mixin)’을 만들기로 했다. 믹스인은 React.createClass에 재사용 가능한 기능을 주입하는 방법이었다.
그들은 이 믹스인을 PureRenderMixin이라고 불렀다.
var Story = React.createClass({
// PureRenderMixin을 컴포넌트에 포함시킨다.
mixins: [PureRenderMixin],
// 이제 shouldComponentUpdate를 직접 작성할 필요가 없다!
// mixins이 알아서 얕은 비교를 수행해준다.
render: function() {
// ...
}
});
개발자가 PureRenderMixin을 추가하기만 하면, 리액트는 해당 컴포넌트에 대해 props와 state의 모든 속성을 얕은 비교하는 shouldComponentUpdate 로직을 자동으로 적용해주었다.
이것은 개발자에게 중요한 약속을 요구했다.
“PureRenderMixin을 사용하려면, 당신은 반드시 불변 데이터를 사용해야만 합니다.”
불변성이라는 함수형 프로그래밍의 철학이, 이제 리액트의 성능을 한계까지 끌어올리는 가장 실용적이고 강력한 기술이 된 것이다. shouldComponentUpdate와 PureRenderMixin, 그리고 불변 데이터의 조합은, 리액트가 다른 어떤 프레임워크도 따라올 수 없는 압도적인 성능 최적화 능력을 갖추게 되는 결정적인 계기가 되었다.


