React를 반응형으로 만들기: 고성능, 손쉬운 유지 관리가 가능한 React 앱 추구 | Mendix

메인 컨텐츠로 가기

React를 반응형으로 만들기: 고성능, 쉽게 유지 관리 가능한 React 앱 추구

애플리케이션 개발 블로그 배경

2년 3월 2016일 편집: Mobservable이 MobX로 리브랜딩되었습니다.

어떻게 하면 엄청나게 빠른 React 앱을 만들 수 있을까요? 최근에 우리는 대규모 프로젝트 중 하나에서 React를 사용하기 시작했고, React는 구성 요소를 구축하는 구조화된 방식과 수많은 UI 업데이트를 저장하는 빠른 가상 DOM 덕분에 큰 도움이 되었습니다. 이 프로젝트의 장점은 꽤 괜찮은 과제가 있다는 것입니다. 브라우저에 수천 개의 객체를 그려야 하며, 이러한 객체는 서로 매우 결합되어 있습니다. 한 객체의 값은 다른 임의의 객체에서 사용될 수 있으므로 작은 변경 사항도 UI의 관련 없는 많은 부분에서 업데이트가 필요할 수 있습니다. 이러한 값은 사용자의 드래그 앤 드롭 동작으로 업데이트될 수 있으므로 UI를 반응성 있게 유지하려면 모든 업데이트와 다시 그리기가 40밀리초 이내에 이루어져야 합니다. 그리고 일반 React는 빠르지만 React만으로는 작업을 수행할 수 없다는 것을 알아내는 데 오래 걸리지 않았습니다.

그래서 우리는 필요한 성능을 제공하면서도 React 원칙에 따라 코드 기반을 유지 관리할 수 있는 솔루션을 찾기 시작했습니다. 간단히 말해서, 우리는 다음을 원했습니다. 우아한 해결책. 그래서 우리는 함수형 반응형 프로그래밍의 세계에서 개념을 활용하려고 했습니다. 즉, Observable. 관찰 가능 항목의 판매 포인트는 모든 계산이 다른 관찰 가능 항목을 사용하는지 자동으로 감지한다는 것입니다. 그런 다음 이러한 관찰 가능 항목 중 하나가 미래에 변경되면 계산이 자동으로 다시 평가됩니다. Observable Ember 및 Knockout과 같은 다른 UI 프레임워크에서 사용되는 개념입니다. 우리는 모든 모델 객체가 다음과 같은 경우를 알아냈습니다. Observable 그리고 우리의 모든 React 구성 요소가 되었습니다. 관찰자 모델의 경우, UI의 관련 부분, 오직 관련 부분만 업데이트되도록 하기 위해 추가적인 마법을 적용할 필요가 없습니다. 계속 읽어서 모든 멋진 점을 확인하세요. 끝 부분에 짝수가 있습니다!

훨씬 덜 이론적으로(또는 원하신다면 덜 까다롭게) 만들기 위해 인위적인 예로 시작해 보겠습니다. 작은 상점을 나타내는 리액트 앱을 상상해 보세요. 몇 가지 기사가 있고, 이 기사 중 일부를 넣을 수 있는 쇼핑 카트가 있습니다. 다음과 같은 것입니다.

응용 프로그램 예제 스크린샷

푸프, 그건 현실에서도 그렇습니다.

데이터 모델

우선, 데이터 모델을 정의해 보겠습니다. 이름과 가격이 있는 기사가 있고, 총 비용이 있는 쇼핑 카트가 있는데, 이는 항목의 합계에 따라 결정됩니다. 각 항목은 기사를 참조하고, 금액을 저장하며, 파생된 가격을 갖습니다. 데이터 모델 내의 관계는 아래와 같이 시각화됩니다. 열린 글머리 기호는 다른 데이터가 변경되면 업데이트해야 하는 파생 데이터를 나타내며, UI에서의 표현도 마찬가지입니다. 따라서 이 간단한 모델에서도 많은 데이터가 흐르고, 사물이 변경될 때 많은 UI 업데이트가 필요합니다.

데이터모델 차트

요구 사항 목록을 작성해 보겠습니다.

  • 상품의 가격이 변경되면 관련된 모든 쇼핑 카트 항목도 가격을 다시 평가해야 합니다.
  • .. 그리고 카트의 총 비용도 마찬가지여야 합니다.
  • 장바구니에 담긴 상품의 양이 변경되면 총 비용도 업데이트되어야 합니다.
  • 기사 이름이 변경되면 해당 기사의 뷰도 업데이트되어야 합니다.
  • 기사 이름이 변경되면 관련 카트 항목의 보기도 업데이트되어야 합니다.
  • 장바구니에 새로운 상품이 추가되면..
  • 기타 등등 ..

아마도 지금쯤은 UI 문제의 요점이 명확해졌을 겁니다. 프로그래머로서, 모든 종류의 가능한 업데이트를 처리하기 위해 보일러플레이트 코드를 쓰고 싶지는 않겠지만, 애플리케이션이 항상 각 데이터 변경 시에 다시 렌더링된다면 사용자는 불편할 정도로 오래 기다려야 할 수도 있습니다.

그러므로 이 문제를 한번에 해결하고 데이터 모델을 작성해 보겠습니다.

좋아요, 그렇게 어렵지 않았죠? 위의 생성자 함수는 다음에 크게 의존합니다. 몹X 관찰 가능한 개념의 독립 실행형 구현을 제공하는 라이브러리(React와 마찬가지로 다른 자바스크립트 기반 라이브러리와 쉽게 결합할 수 있음). props 함수는 제공된 키와 값의 유형을 기반으로 대상 객체에 대한 새로운 관찰 가능한 속성을 만듭니다. 모든 속성의 관찰 가능한 특성으로 인해 위의 함수는 일부 종속성이 변경될 때만 자동으로(그리고 오직) 업데이트됩니다. 이는 예를 들어 다음과 같은 일부 요구 사항을 즉시 충족합니다. total 장바구니는 새로운 항목이 추가되거나, 품목 가격이 변경되는 경우 등에 자동으로 업데이트됩니다.

사용자 인터페이스

보고 믿으면 믿게 되니, 이 모델을 중심으로 사용자 인터페이스를 구축해 보겠습니다. 초기 데이터를 렌더링하는 React 구성 요소를 만듭니다. 다음 JSX 스니펫은 쇼핑 카트의 뷰를 보여주고, 카트의 모든 항목을 렌더링하고, 카트의 총 가격을 보여줍니다. 상상할 수 있듯이, 기사의 뷰와 같은 앱의 다른 구성 요소도 매우 유사합니다.

꽤 간단하죠? CartView 구성 요소는 카트를 받고, CartEntryView를 사용하여 총액과 개별 항목을 렌더링합니다. 그러면 관련 기사의 이름과 원하는 기사의 양이 인쇄됩니다. React 모범 사례에 따르면 나열된 각 항목은 고유하게 식별 가능해야 하므로 각 항목에 임의적이지만 변경할 수 없는 ID를 할당합니다. 제거 버튼을 누르면 이 양이 1만큼 감소하고 0에 도달하면 전체 항목이 카트에서 제거됩니다. 어디에도 removeArticle UI를 업데이트해야 한다는 것을 나타냈습니다.

다음 큰 단계는 해당 구성 요소가 데이터 모델과 최신 상태를 유지하도록 강제하는 것입니다. 예를 들어 항목이 제거될 때입니다. 렌더링 코드에서 쉽게 알 수 있듯이 가능한 데이터 전환이 많이 있습니다. 기사의 양이 변경될 수 있고, 총 카트 비용이 변경될 수 있으며, 기사 이름이 변경될 수 있으며, 심지어 항목과 기사 간의 참조도 변경될 수 있습니다. 이 모든 변경 사항을 어떻게 수신할 것인가요?

글쎄요, 꽤 간단하죠. 그냥 사용하세요 mobxReact.observer 인사말 mobx-react 각 구성 요소에 대한 패키지이며 이것으로 다른 모든 요구 사항을 충족하기에 충분합니다.

잠깐, 뭐, 그게 전부야? 응, 위의 데모와 소스를 확인해 보세요. JS피들. 그럼 여기서 무슨 일이 일어났나요? observer 함수는 두 가지 일을 대신해 주었습니다. 첫째, 구성 요소의 렌더링 함수를 관찰 가능한 함수로 바꾸었습니다. 둘째, 구성 요소 자체가 해당 함수의 관찰자로 등록되어 렌더링이 오래될 때마다 다시 렌더링이 강제로 실행됩니다. 따라서 이 함수(ES6를 사용하는 경우 데코레이터)는 관찰 가능한 데이터가 변경될 때마다 UI의 관련 부분만 업데이트되도록 합니다. 예제 앱에서 간단히 놀아보고, 그러는 동안 로그 패널을 주시하고 실제 작업과 실제 데이터에 따라 UI가 어떻게 업데이트되는지 살펴보세요.

  • 장바구니에 없는 상품의 이름을 변경해보세요
  • 장바구니에 상품을 추가한 후 이름을 변경하세요.
  • 장바구니에 품목을 추가하고 가격을 업데이트합니다.
  • 장바구니에서 제거하고 가격을 다시 업데이트하세요
  • … 등등. 각 작업에서 최소한의 구성 요소가 다시 렌더링되는 것을 알 수 있습니다.

마지막으로, 각 구성 요소가 자체 종속성을 추적하기 때문에 일반적으로 구성 요소의 자식을 명시적으로 다시 렌더링할 필요가 없습니다. 예를 들어, 쇼핑 카트의 총액이 다시 렌더링되는 경우 항목도 다시 렌더링할 필요가 없습니다. React의 자체 퓨어렌더믹신 그런 일이 일어나지 않도록 합니다.

숫자들

그럼 우리는 무엇을 성취했나요? 비교 목적으로, 여기에서 확인하세요 정확히 동일한 앱을 찾을 수 있지만 관찰 가능 항목과 순진한 '모든 것을 다시 렌더링' 접근 방식이 없습니다. 기사가 몇 개뿐이면 차이를 알아차리지 못할 것입니다. 하지만 기사 수가 늘어나면 성능 차이가 정말 커집니다.

 

기사 차트 만들기 및 렌더링기사 차트 업데이트

대량의 데이터와 구성 요소를 만드는 것은 관찰 가능 항목이 있든 없든 매우 유사하게 동작합니다. 하지만 데이터가 변경되면 관찰 가능 항목이 실제로 빛을 발하기 시작합니다. 10개 항목의 컬렉션에서 10,000개 기사를 업데이트하는 것이 약 2.5배 더 빠릅니다! 250초가 10밀리초로 줄었습니다. 지연되는 경험과 지연되지 않는 경험의 차이입니다. 이 차이는 어디에서 오는 것일까요? 먼저 관찰 가능 항목이 없는 "10000개 기사 목록에서 XNUMX개 기사 업데이트" 시나리오에서 업데이트를 실행한 후의 React 렌더링 보고서를 살펴보겠습니다.

로그 스크린샷

보시다시피, 2,145만 개의 ArticleViews와 CartEntryViews가 모두 다시 렌더링됩니다. 그러나 React에 따르면 총 2,433밀리초의 렌더링 시간 중 XNUMX밀리초가 낭비되었습니다. 낭비는 실제 DOM의 업데이트로 이어지지 않는 렌더링 함수를 실행하는 데 소요된 시간을 의미합니다. 이는 구성 요소가 많을 경우 모든 것을 순진하게 다시 렌더링하는 것은 CPU 시간의 큰 낭비임을 강력히 시사합니다. 비교를 위해, 관찰 가능 항목을 사용할 때의 동일한 시나리오에 대한 보고서는 다음과 같습니다.

로그 스크린샷

큰 차이입니다! 20,006개의 구성 요소를 다시 렌더링하는 대신 31개의 구성 요소만 다시 렌더링됩니다. 그리고 더 중요한 것은 낭비가 보고되지 않는다는 것입니다! 즉, 다시 렌더링된 모든 구성 요소가 실제로 DOM에서 무언가를 변경했다는 것을 의미합니다. 바로 이것이 관찰 가능 항목을 사용하여 달성하고자 했던 것입니다!

보고서에서 남은 렌더링 시간의 대부분, 총 243밀리초 중 267밀리초가 CartView를 렌더링하는 데 소요된다는 것이 분명해졌습니다. CartView는 카트의 총 비용을 새로 고치기 위해 다시 렌더링됩니다. 그러나 CartView를 다시 렌더링하려면 모든 60개 항목을 다시 방문하여 CartEntryViews에 대한 인수가 변경되었는지 확인해야 합니다. 따라서 CartView의 총 비용을 자체 구성 요소인 CartTotalView에 간단히 넣으면 총 비용만 변경되면 CartView의 전체 렌더링을 건너뛸 수 있습니다. 이렇게 하면 렌더링 시간이 약 40밀리초로 훨씬 더 단축됩니다(위 차트의 '최적화된' 시리즈 참조). 이는 vanilla React 앱에서 동일한 업데이트보다 약 XNUMX배 빠릅니다!

맺음말

관찰 가능 항목을 사용하여 순진하게 모든 구성 요소를 다시 렌더링하는 동일한 앱보다 훨씬 빠른 애플리케이션을 구축했습니다. 그리고 (프로그래머인 여러분에게 중요한 점은) 코드의 유지 관리를 손상시키지 않고 이를 수행했다는 것입니다. 위에 링크된 두 JSFiddles의 소스 코드를 살펴보세요. 두 목록은 매우 유사하고 둘 다 작업하기에 똑같이 편리합니다.

다른 기술로도 같은 것을 달성할 수 있었을까요? 아마도요. 예를 들어, ImmutableJS가 있는데, 이 역시 변경된 데이터를 수신하는 구성 요소만 업데이트하여 React 렌더링을 매우 빠르게 만듭니다. 그러나 데이터 모델에 대해 훨씬 더 큰 양보를 해야 합니다. 결국, 제 생각에는 가변 클래스는 불변 클래스보다 작업하기가 조금 더 편리합니다. 게다가 불변 데이터 구조는 계산된 값을 최신 상태로 유지하는 데 도움이 되지 않습니다. 따라서 불변 데이터를 사용하면 기사 이름을 변경하면 ArticleView가 정말 빠르게 다시 렌더링되지만 동일한 기사를 참조하는 기존 CartEntryView는 여전히 무효화되지 않습니다.

React 앱을 최적화하는 데 적용할 수 있는 또 다른 기술은 데이터의 각 가능한 돌연변이에 대한 이벤트를 생성하고 적절한 시점과 적절한 구성 요소에서 해당 이벤트에 대한 리스너를 (등록) 취소하는 것입니다. 하지만 이렇게 하면 오류가 발생하기 쉬운 보일러플레이트 코드가 많이 생깁니다. 게다가 저는 그런 일을 하기에는 너무 게으른 것 같습니다.

그런데 프로젝트에서 관심사 분리를 명확하게 유지하기 위해 모델 데이터 업데이트를 추상화하는 데 컨트롤러나 액션 디스패처를 사용하는 것을 적극 권장합니다.

결론적으로, 대규모 프로젝트에서 React와 Observables를 결합하는 것이 매우 잘 작동해서, 가끔은 내가 아직 생각지도 못했던 코너 케이스에서 UI를 올바르게 업데이트하는 데이터 변형을 보았고, 성능 문제가 발생하지 않았습니다. 그래서 저는 UI를 가능한 한 빨리 업데이트하는 시기와 방법을 알아내는 힘든 작업을 React와 Observables에 맡기고, 코딩의 흥미로운 부분에 집중하겠습니다 :).

이 게시물에 대해 토론하세요 해커 뉴스.

리소스

언어를 선택하세요