[번역] 리액트 성능 최적화 테크닉

Kim Kyeseung·2020년 3월 16일
3

번역

목록 보기
2/4
post-thumbnail

리액트는 UI를 업데이트할 때 불필요한 DOM 작동을 하지 않도록 내부적으로 많은 기술들이 들어있습니다.

그렇지만 리액트를 비롯하여 다른 어떤 프론트엔드 프레임워크들도 시간이 지나면 그 성능은 점차 떨어지겠죠.

개발자라면 응당 리액트와 리액트의 컴포넌트의 생태주기가 어떻게 작동하는지 꼭 알아야만 합니다. 이 부분에 어느정도 높은 이해도가 바탕이 되어 있다면 어플리케이션의 꽤 많은 성능 향상을 기대해볼 만하겠습니다. 이는 언제, 어떻게 컴포넌트의 렌더링이 이뤄지는지 측정하고 최적화함으로써 가능한 일 일것 입니다.

이 외에도 리액트 어플리케이션의 속도를 개선시키는 수많은 방법들이 존재합니다. 우리 팀의 경험에 비추어, 최적화에 도움이 될 것이라 생각되는 몇 가지를 적어봅니다.

이 리스트는 절대로 만능이 아니며, 단지 몇 가지 도움만 될 것이라는 점을 기억해두세요. 성능의 최적화에 도움이 되는 방법은 아주 많습니다.

Production Build 사용

이건 아주 기초적인 테크닉이고 모두가 이렇게 하고 있으리라 믿고 있습니다. 하지만 충분히 짚고 넘어갈만한 가치가 있습니다. 우리는 사용자들에게 우리의 어플리케이션을 배포할 때 경량화 프로덕션 빌드를 사용해야 합니다.

번들링과 경량화

모든 리액트 SPA는 자바스크립트 코드를 한 파일에 경량화하여 번들할 수 있습니다. 그건 어플리케이션의 규모가 작다면 별 문제없어요.

하지만 어플리케이션이 점차 커질수록, 모든 JS 코드를 한 파일로 관리하는 것은 지루하고 짜증나는 일이 될 것이고, 코드는 점점 이해하기 힘들어 질 것입니다. 또한 이 커다란 파일 하나를 브라우저에게 보내는 일도 굉장히 소모적인 과정입니다. 그래서, 우리는 어플리케이션을 여러 파일로 쪼개어 브라우저에게 필요한 파일만 전달하는 몇가지 메커니즘을 사용할 필요가 있습니다.

만약 웹팩을 사용한다면, 상당히 효율적으로 코드를 여러개의 '청크' 파일로 쪼개버릴 수 있습니다.

코드 쪼개기는 두 가지 유형이 있습니다. 자원 쪼개기(Resource Splitting)와 수요에 맞춰 쪼개기(On-Demand code splitting)입니다.

자원 쪼개기로는 우리 자원을 여러개의 파일로 쪼갤 수 있습니다. 예를 들어 CommonsChunkPlugin를 사용하여 공통되는 반복적인 코드(모든 외부 라이브러리들 같은)를 고유한 "청크" 파일로 추출할 수 있습니다.

ExtractTextWebpackPlugin를 사용하여 모든 CSS 코드를 하나의 CSS 파일로 분리하여 추출할 수 있습니다. 이렇게 쪼개는 것은 두 가지의 도움이 될 것입니다.

  • 브라우저가 자원 변화의 캐싱을 덜하도록 도와줍니다.
  • 또한 브라우저가 여러 다운로드를 병행할 수 있어 잠재적으로 로딩 시간을 줄이는 이점이 있습니다.

주목할만한 또 다른 웹팩의 기능은 수요에 맞춰 코드 쪼개기입니다. 이 기능은 우리가 필요할 때 로드된 코드를 청크로 쪼개기 위해 사용할 수 있습니다.

이 기능은 초기 다운로드를 작게 유지할 수 있고, 어플리케이션을 로드하는데 걸리는 시간을 줄일 수 있습니다. 어플리케이션이 다른 코드 청크를 필요로 할 때에 브라우저가 그에 맞는 청크를 다운로드 할 수 있습니다.

.map()을 사용할 때 배열의 인덱스를 key로 사용하지 마세요

우리가 리스트를 렌더링하려 하고 key를 설정하지 않으면 콘솔창에서 아래와 같은 경고를 보게될 것 입니다.

Warning: Each child in an array or iterator should have a unique “key” prop.
경고: 배열의 각각의 요소나 반복자는 고유한 "key"를 가져야 합니다.

우리는 이것을 해결하기 위해 오랫동안 단순하게 반복문의 index 값을 key로 넘겼습니다.

{todos.map((todo, index) =>
  <Todo
    {...todo}
    key={index}
  />
)}

깔끔해 보이고 마무리된 것처럼 보입니다. 경고도 없어졌고요. 이제는 문제가 없는 걸까요? 하지만 진짜 위험이 앞에 있습니다.

우리의 어플리케이션이 오작동을 일으키고 엉뚱한 데이터를 보여줄 수도 있어요..!

인덱스를 key로 사용하는 것은 Anti-Pattern입니다.

왜냐? key는 그냥 리액트가 DOM 요소를 식별하기 위한 것일 뿐입니다. 우리가 리스트에서 항목을 넣었다 뺐다 하더라도 key는 그 전과 계속 같을 겁니다. 그럼 리액트는 리스트에서 DOM 요소가 컴포넌트의 변화가 없다고 생각합니다. 그러면 리스트가 업데이트 되어도 더 이상 정확 화면을 볼 수가 없겠죠.

key 프로퍼티로 고유한 값을 사용하는 것은 언제나 옳습니다. 그러나 마땅히 key로 넘길만한 고유한 것이 없다면 shortid npm 패키지를 이용해서 고유한 key 값을 만들어 낼 수 있습니다.

var shortid = require('shortid');
function createNewTodo(text) {
  return {
    completed: false,
    id: shortid.generate(),
    text
  }
}

하지만 안전하게 인덱스를 key로 사용할 수 있는 경우도 많이 있다는 것도 기억해두세요.

  • 리스트의 요소이 정적인 경우 — 프로그래밍으로 연산된 것이 아니고 앞으로도 변하지 않을 경우
  • 리스트의 요소들이 고유 id가 없을 경우
  • 리스트가 절대로 재정렬되거나 필터링되지 않을 경우

이 모든 조건들이 부합하는 경우, 인덱스를 key로 사용하는 것도 아무런 문제되지 않을 것 입니다.

DOM 요소에 props 뿌리기

우리는 일반적으로 일일이 하나씩 프로퍼티를 작성하는 것 보다는 엘리먼트에 프로퍼티를 뿌려버립니다.

그렇지만 우리가 엘리먼트에 props를 뿌릴때, 우리가 잘 알지 못하고 요구하지 않는 HTML 속성을 추가하게 될 위험을 무릅써야 합니다. 그리고 그건 나쁜 습관입니다.

예시를 보시죠.

const Spread = () => <div hero="superman" />

hero 속성은 div에 유효한 속성이 아니기 때문에 다음과 같은 메세지가 나옵니다.

Unknown props 'hero' on <div> tag. Remove this prop from the element
<div> 태그에 모르는 'hero' props이 있습니다. 엘리먼트에서 이 prop을 제거하세요

위와 같은 경우에 오류를 찾아내고 제거하기가 쉽습니다. 그렇지만 아래의 경우와 같이 스프레드 연산자를 사용한 경우, 부모요소로부터 전달되는 전체 프로퍼티를 통제할 수가 없습니다.

const Spread = props => <div {...props} />

그러므로 props 뿌리기 대신, 엘리먼트에 직접 어트리뷰트를 지정하는 것을 추천합니다.

const SpecificAttribute = props => 
  <div specificAttr={props.specificAttr} />

필요한 경우에만 컴포넌트를 업데이트 하기

컴포넌트의 props나 state의 모든 변화에도 리액트는 실제 DOM을 업데이트할 필요가 있는지 체크합니다.

리액트는 기존에 렌더된 컴포넌트와 새롭게 리턴된 컴포넌트를 비교하여 작동합니다. 만약 컴포넌트들이 같다면 리액트는 아무것도 하지 않고, 다르면 새 컴포넌트를 바탕으로 DOM을 업데이트 합니다.

리액트는 페이지 전체가 아니라 오직 새롭게 변화된 DOM 요소들만 업데이트합니다. 그러나 단일 렌더링에도 시간을 잡아먹을 수도 있고, 어떤 경우에도 요소를 업데이트하고 싶지 않을 때도 있습니다.

그러기 위해 우리가 할 수 있는 것은 리렌더 과정이 시작하기 전에 자동으로 실행되는 shouldComponentUpdate를 덮어씌우는 것입니다. 우리는 우리의 시나리오를 바탕으로 false를 리턴하는 함수로 기본 shouldComponentUpdate 함수를 덮어씌울 수 있습니다.

그리고 또 shouldComponentUpdate()를 개선하는 것보다 차라리 React.PureComponent를 쓸수도 있습니다. 그렇지만 무엇을 사용하길 원하는지는 개발자들에게 맡기도록 하겠습니다.

불변성의(Immutable) 자료 구조

만약 우리가 복잡한 state 변화를 체크하기 위해 React.PureComponent를 사용한다면 불변성의 자료 구조를 사용해야할 필요가 있습니다. 이건 시스템 설계나 디자인 패턴이라기보단 코드를 쓰는 하나의 방법에 더 가깝습니다.

불변성의 자료 구조를 설정하는 것은 같은 객체를 업데이트하는게 아니고 그 객체의 사본을 만들어 낼 수 있어야 합니다.

이 방법으로 객체의 변화를 감지하는 프로세스를 단순화시킬 수 있습니다. 그렇지만 이것 역시 제한 사항이 있습니다. 불변의 데이터는 한 번 만들어지고 나서는 바꿀 수 없다는 것을 까먹으면 안됩니다.(역자 주: 바꿀 수 없고, 새로 만들 뿐이겠죠)

장점:

  • 사이드 이펙트가 없습니다.
  • 불변 자료 객체의 생성, 테스트, 사용이 쉬워집니다.
  • 제한적인 결합을 방지하는데 도움이 됩니다. 그런건 어떤식으로든 코드가 시간에 의존하는 결합일 뿐입니다.
  • 재귀적으로 자료를 체크하지 않아도 업데이트를 빠르게 확인할 수 있는 로직을 작성하기 쉬워집니다.

아래의 라이브러리들이 우리가 사용할 수 있는 불변성의 자료구조를 제공하고 있습니다.

  • Immutability Helper: 소스의 변경없이 데이터를 변화시키는데 유용한 라이브러리입니다.
  • Immutable.js: 저의 최애 라이브러리입니다. List, Stack, Map, OrderedMap, Set, OrderedSet 그리고 Record 같은 수 많은 불변성 자료 구조를 끊임없이 제공해주고 있습니다.
  • Seamless-immutable: 불변성 자료 구조 라이브러리 중 하나입니다. 일반 배열과 객체의 하위호환성을 지원합니다.
  • React-copy-write: 심플한 변동(mutable) API, 메모이즈드 선택자(memoized selectors) 그리고 구조적 공유(structural sharing)를 제공하는 불변성의 리액트 state 관리 라이브러리입니다.

요약

성능 최적화의 핵심은 진짜 필요할 때만 컴포넌트를 업데이트 하도록 리액트 어플리케이션을 잘 튜닝하는 것입니다.

또 이 말을 하지 않을 수가 없는데, 우리는 성능 최적화가 개발 끝자락에가서 하는 것이 아니라 꾸준히 해줘야 한다는 걸 확신할 필요가 있습니다.

마지막으로, 우리가 어플리케이션의 성능에 관련하여 코드에 변화를 줄 때, 변화의 전후로 성능의 향상이 있는지 잘 분석하고 관찰해야 합니다.

성능 최적화에 있어서 아직 우리가 다룰 수 있는 수 많은 방법과 영역들이 남아있습니다. 그렇지만 하나의 글에 다 담지는 않을 겁니다. 장황해지거든요. 언젠가 다음에 글을 쓰게 되면 한번 다뤄보도록 하겠습니다.

지금은 이게 답니다. 즐코하세요!


원문: Performance Optimization Techniques In React

처음 의미, 어순, 단어 뜻 그대로 직역 후 두어번 정도 읽고 자연스럽게 읽힐 수 있도록 몇 번 다듬었습니다.

문장의 주체와 객체가 우리나라라는 다른 점을 고려해서 의미에 맞게 적절하게 의역하기도 했습니다.

불변의 자료 구조의 장점에 대한 리스트 중
'Helps prevent temporal coupling, it is a type of coupling where code is dependent on time in some way'는 정확하게 어떤 의미인지 알기가 힘들었습니다. 파파고의 도움을 받아 어떻게든 조금 더 자연스러운 문장으로 표현하였지만 마음에 들지가 않네요.

그리고 불변성 자료 구조를 지원하는 라이브러리와 그에 대한 간단한 소개에 대한 부분도 번역하기가 힘들었습니다.
사용해본적 없는 라이브러리들이었고, 제가 아직 불변적인 리액트 상태관리에 깊은 이해도가 없는것 같네요.

오타, 오역 혹은 팁, 더 나은 번역을 위한 지적은 언제든지 환영합니다.

profile
웹 프론트엔드 개발자입니다.

0개의 댓글