순수 컴포넌트와 일반 컴포넌트

Daniel Min
3 min readSep 6, 2017

순수 컴포넌트와 일반 컴포넌트의 적절한 사용처는 무엇인가?

원문은 paul christophe님이 써주셨습니다. (original article written by paul christophe)

요약: 컴포넌트 중에서도 순수 컴포넌트를 사용하고, 절대로 객체들을 직접 수정하지 마세요. (다른 더 좋은 방법을 사용하세요)

순수 컴포넌트와 일반 컴포넌트를 사용해야 할 때?

일반 컴포넌트를 사용했을 때 보다 얻을 수 있는 성능상의 이득에 기인하여 순수 컴포넌트를 사용하게 되었습니다. 실제로 순수 컴포넌트를 사용 했을 때 성능 향상을 불러왔지만, 이를 사용하려면 몇 가지 전제 조건을 만족해야 합니다.

그뿐만 아니라. 이번 글에서는 순수 컴포넌트가 무엇인지, 어떻게 사용하는지. 왜 우리가 꼭 사용해야 하는지에 대해서 다뤄 볼 것입니다.

둘 사이에는 단 하나의 차이점이 있습니다.

PureComponentComponent 와 동일하게 동작합니다. 다만, 순수 컴포넌트는 당신을 위해 shouldComponentUpdate 함수를 다뤄준다는 게 하나의 차이점입니다. props 혹은 state 가 변경될때, 순수 컴포넌트는 두 속성에 있어 얕은 비교를 수행합니다.

한편, Component 는 바뀔값과 현재 값을 비교 해주지 않으므로 매번 컴포넌트가 변경되면 불필요한 렌더링을 다시 수행하게 됩니다.

얕은 비교가 뭔가요?

propsstate 의 이전 값과 바뀔 값을 비교할 때, 얕은 비교를 수행하게 되는데, 원시형 값에 있어서는 같은 값을 가지는지 확인하게 됩니다. (예: 11과 같고, truetrue와 같은 것처럼.)

그리고 오브젝트와 배열과 같은 복잡한 자바스크립트 값에 있어서는 참조하고 있는 객체가 같은지 확인합니다.

참조: https://goo.gl/oQfAMz

절대로 직접 변경하지 마세요.

아마도 이 글을 읽는 당신은 propsstate 내부에 있는 오브젝트와 배열의 값을 직접 변경하지 말라는 이야기를 들어오셨을 겁니다. 만일 부모 컴포넌트에서 순수 컴포넌트인 자식 컴포넌트의 props 로 제공되는 오브젝트를 직접 변경했다고 해도 당신의 “순수” 컴포넌트는 업데이트 되지 않는데, 이는 순수 컴포넌트가 오브젝트 혹은 배열에 있어서는 이전 값과 레퍼런스를 비교하기 때문입니다.

따라서 이런 방법 대신, [ ...array ] 혹은 { ...obj } 와 같은 방법으로 완전히 새로운 오브젝트 또는 배열을 생성하는 방법을 사용하세요.

성능상의 문제가 있나요?

원시형 값 혹은 오브젝트의 레퍼런스들을 비교하는 것은 엄청나게 값싼 연산입니다. 만일 당신이 자식 오브젝트들을 포함하는 배열을 가지고 있으며 그중 하나가 업데이트 됐을 때, 컴포넌트 하나를 다시 렌더링하는 것보다는 그것들을 비교하는 것이 정말로 번개같이 빠를 것입니다.

당신이 실수할만한 다른 방법들

렌더시에 값을 함수 안에서 넘겨주지 마세요.

당신이 만일 여러 아이템을 가지는 배열(list of items)을 가지고 있고, 각각 고유한 매개변수를 부모 컴포넌트의 메소드로 전달한다고 했을 때. 다음과 같이 코드를 작성할 수 있을 것입니다:

<CommentItem likeComment={() => this.likeComment(user.id)} />

이러한 방법의 문제점은, 매번 부모 컴포넌트의 렌더 시점에 새로운 레퍼런스를 가지는 새로운 함수가 생성되고 likeComment 으로 넘겨진다는 점입니다. 또한, CommentItem 이 순수 컴포넌트일 때는 새로운 레퍼런스를 가지는 함수 때문에 데이터는 같은 상태임에도 불구하고 불필요한 렌더링을 여러 번 수행하게 될 것입니다.

이를 해결하기 위해, 부모 컴포넌트 메소드의 레퍼런스를 직접 넘겨주세요. 그렇게 한다면 자식 컴포넌트의 likeComment 프로퍼티는 언제나 같은 값을 가지게 되며 절대로 불필요한 렌더링을 여러 번 수행하지 않을 것입니다.

<CommentItem likeComment={this.likeComment} userID={user.id} />

위와 같이 구현한 다음, 자식 컴포넌트에 props 를 참조하는 메소드를 만들어 주세요:

class CommentItem extends PureComponent {
...
handleLike() {
this.props.likeComment(this.props.userID)
}
...
}

렌더 함수 안에서 데이터를 재창조(derive)하지 마세요.

프로필 컴포넌트에서 사용자가 “좋아요” 한 10가지 게시물을 보여준다고 가정해봅시다.

render() {
const { posts } = this.props
const topTen = posts.sort((a, b) => b.likes - a.likes).slice(0, 9)
return //...
}

topTenposts 가 변경되지 않았으며, 같은 데이터를 가지고 있음에도 매번 컴포넌트가 렌더링 될 때마다 새로운 레퍼런스를 가지게 됩니다. 이는 불필요한 재 렌더링을 초래하게 됩니다.

당신은 이런 문제를 재창조된 데이터를 캐싱하는 것으로 해결할 수 있습니다. 예를 들어, 재창조된 데이터를 컴포넌트의 state 에 저장(캐시)해 두었다가, posts 가 업데이트 될 때만 업데이트해주는 것입니다.

componentWillMount() {
this.setTopTenPosts(this.props.posts)
}
componentWillReceiveProps(nextProps) {
if (this.props.posts !== nextProps.posts) {
this.setTopTenPosts(nextProps)
}
}
setTopTenPosts(posts) {
this.setState({
topTen: posts.sort((a, b) => b.likes - a.likes).slice(0, 9)
})
}

만약 당신이 Redux 를 사용한다면, 재창조될 데이터를 구성하고 캐시 해주는 reselect를 사용하는 것을 고려해보세요.

끝내며

당신이 다음의 두 가지 간단한 규칙만 지킨다면 일반 컴포넌트 대신 순수 컴포넌트를 사용하는 것은 좋은 선택이라고 할 수 있습니다:

  1. 직접 변경하는 것은 대부분 나쁜 행위입니다만, 순수 컴포넌트를 사용할 때 복합적인 문제로써 다가오게 될 것입니다.
  2. 만일 당신이 함수, 오브젝트 혹은 배열을 렌더 함수 내에서 새로 만들고 있다면, 그것은 (아마도) 잘못 설계하고 있는 것 일 겁니다.

--

--

Daniel Min

Looking forward to become full-stack web developer as well as a UX designer