성능
이 페이지는 PageTurner AI로 번역되었습니다(베타). 프로젝트 공식 승인을 받지 않았습니다. 오류를 발견하셨나요? 문제 신고 →
Redux FAQ: 성능
성능과 아키텍처 측면에서 Redux의 "확장성"은 어느 정도인가요?
이에 대한 단일의 명확한 답은 없지만, 대부분의 경우 두 측면 모두에서 걱정할 필요가 없습니다.
Redux에서 수행하는 작업은 일반적으로 몇 가지 영역으로 나뉩니다: 미들웨어와 리듀서에서의 액션 처리(불변성 업데이트를 위한 객체 복제 포함), 액션 디스패치 후 구독자에게 알림, 상태 변경에 따른 UI 컴포넌트 업데이트. 충분히 복잡한 상황에서는 각각이 성능 문제가 될 가능성이 있지만, Redux 구현 자체가 본질적으로 느리거나 비효율적인 것은 아닙니다. 실제로 특히 React Redux는 불필요한 리렌더링을 줄이기 위해 심각하게 최적화되어 있으며, React-Redux v5는 이전 버전 대비 뚜렷한 개선 사항을 보여줍니다.
Redux는 다른 라이브러리와 비교했을 때 기본 설정으로는 효율적이지 않을 수 있습니다. React 애플리케이션에서 최대 렌더링 성능을 달성하려면 상태를 정규화된 형태로 저장해야 하며, 소수의 컴포넌트만 연결하는 대신 많은 개별 컴포넌트를 스토어에 연결해야 합니다. 또한 연결된 리스트 컴포넌트는 연결된 자식 리스트 항목에 항목 ID를 전달해야 합니다(리스트 항목이 ID로 자신의 데이터를 조회할 수 있도록). 이렇게 하면 전체 렌더링 양을 최소화할 수 있습니다. 메모이즈된 셀렉터 함수 사용 역시 중요한 성능 고려 사항입니다.
아키텍처 측면에서, 경험적으로 Redux는 다양한 프로젝트 규모와 팀 규모에서 잘 작동합니다. Redux는 현재 수백 개의 기업과 수천 명의 개발자가 사용하고 있으며, NPM에서 월간 수십만 건의 설치가 이루어지고 있습니다. 한 개발자는 다음과 같이 보고했습니다:
규모를 말씀드리자면, 저희는 약 500개의 액션 타입, 약 400개의 리듀서 케이스, 약 150개의 컴포넌트, 5개의 미들웨어, 약 200개의 액션, 약 2300개의 테스트를 가지고 있습니다
추가 정보
문서
아티클
-
React 애플리케이션 확장 방법 (관련 발표: 애플리케이션 확장)
토론
모든 리듀서를 매 액션마다 호출하면 성능이 느려지지 않나요?
Redux 스토어는 실제로 단 하나의 리듀서 함수만을 가진다는 점을 알아두는 것이 중요합니다. 스토어는 현재 상태와 디스패치된 액션을 이 단일 리듀서 함수에 전달하며, 리듀서가 적절히 처리하도록 합니다.
분명히 단일 함수에서 모든 가능한 액션을 처리하려는 것은 함수 크기와 가독성 측면에서 확장성이 떨어집니다. 따라서 실제 작업을 최상위 리듀서가 호출할 수 있는 별도 함수들로 분할하는 것이 합리적입니다. 특히, 일반적으로 권장되는 패턴은 특정 키에서 상태의 특정 부분을 관리하는 전담 서브 리듀서 함수를 갖는 것입니다. Redux에 내장된 combineReducers()는 이를 달성하는 여러 방법 중 하나입니다. 또한 스토어 상태를 가능한 한 평평하고 정규화된 형태로 유지하는 것이 매우 권장됩니다. 궁극적으로 리듀서 로직을 어떻게 구성할지는 여러분의 선택입니다.
그러나 많은 수의 리듀서 함수가 조합되어 있고 상태가 깊게 중첩되어 있더라도 리듀서 속도는 문제가 되지 않을 가능성이 높습니다. JavaScript 엔진은 초당 매우 많은 함수 호출을 처리할 수 있으며, 대부분의 리듀서는 대체로 switch 문을 사용하고 대부분의 액션에 대해 기본적으로 기존 상태를 반환하기만 합니다.
실제로 리듀서 성능이 우려된다면 redux-ignore나 reduxr-scoped-reducer 같은 유틸리티를 사용해 특정 리듀서만 특정 액션을 처리하도록 제한할 수 있습니다. 또한 redux-log-slow-reducers를 사용해 성능 벤치마킹을 수행할 수도 있습니다.
추가 정보
토론
리듀서에서 상태를 깊은 복사해야 하나요? 상태 복사가 느려지지 않나요?
불변 상태 업데이트는 일반적으로 깊은 복사가 아닌 얕은 복사를 의미합니다. 얕은 복사는 더 적은 객체와 필드를 복사하면 되므로 깊은 복사보다 훨씬 빠르며, 본질적으로 일부 포인터 이동만으로 처리됩니다.
또한 상태를 깊게 복제하면 모든 필드에 대한 새로운 참조가 생성됩니다. React-Redux의 connect 함수는 데이터 변경 여부를 판단하기 위해 참조 비교에 의존하므로, 이는 다른 데이터가 실제로 변경되지 않았음에도 UI 컴포넌트가 불필요하게 재렌더링되게 할 수 있습니다.
그러나 영향을 받는 각 중첩 레벨마다 복사하고 업데이트된 객체를 생성해야 합니다. 이 작업이 특히 비용이 많이 들지는 않겠지만, 가능하다면 상태를 정규화되고 얕게 유지해야 하는 또 다른 중요한 이유입니다.
일반적인 Redux 오해: 상태를 깊은 복제(deep clone)해야 한다는 것입니다. 실제로: 내부의 어떤 것이 변경되지 않았다면 그 참조를 동일하게 유지하세요!
추가 정보
문서
토론
스토어 업데이트 이벤트 수를 줄이려면 어떻게 해야 하나요?
Redux는 각각 성공적으로 디스패치된 액션(즉, 액션이 스토어에 도달하고 리듀서에 의해 처리된 경우) 후에 구독자(subscriber)에게 알립니다. 특정 상황에서는 구독자가 호출되는 횟수를 줄이는 것이 유용할 수 있으며, 특히 액션 생성자가 여러 개의 서로 다른 액션을 연속으로 디스패치할 때 유용합니다.
다양한 방식으로 배칭(batching) 기능을 추가하는 여러 애드온이 있습니다: redux-batched-actions (여러 액션을 하나처럼 디스패치하고 리듀서에서 "언팩"할 수 있는 고차 리듀서), redux-batched-subscribe (여러 디스패치에 대한 구독자 호출을 디바운스할 수 있는 스토어 인핸서), redux-batch (단일 구독자 알림으로 액션 배열을 처리하는 스토어 인핸서) 등이 있습니다.
특히 React-Redux의 경우 React-Redux v7부터 React 이벤트 핸들러 외부에서 액션을 디스패치할 때 React 리렌더링 횟수를 최소화하는 새로운 batch 공개 API가 도입되었습니다. 이 API는 React의 unstable_batchedUpdate() API를 래핑하여 이벤트 루프 틱 내의 모든 React 업데이트를 단일 렌더 패스로 배치 처리할 수 있게 합니다. React는 자체 이벤트 핸들러 콜백에 내부적으로 이 방식을 이미 사용하고 있습니다. 이 API는 실제로 ReactDOM 및 React Native와 같은 렌더러 패키지의 일부이며 React 코어 자체에는 포함되지 않습니다.
React-Redux는 ReactDOM과 React Native 환경 모두에서 작동해야 하므로, 빌드 시점에 올바른 렌더러에서 이 API를 가져와 자체적으로 사용할 수 있도록 처리했습니다. 또한 이제 이 함수를 batch()로 이름 변경하여 공개적으로 재익스포트합니다. 이를 사용하면 React 외부에서 디스패치된 여러 액션이 단일 렌더 업데이트만 발생하도록 할 수 있습니다. 예시:
import { batch } from 'react-redux'
function myThunk() {
return (dispatch, getState) => {
// should only result in one combined re-render, not two
batch(() => {
dispatch(increment())
dispatch(increment())
})
}
}
추가 정보
토론
라이브러리
“하나의 상태 트리”가 메모리 문제를 일으킬까요? 많은 액션을 디스패치하면 메모리를 차지할까요?
먼저, 순수 메모리 사용 측면에서 Redux는 다른 JavaScript 라이브러리와 다르지 않습니다. 유일한 차이점은 다양한 객체 참조가 Backbone처럼 독립적인 모델 인스턴스로 저장되기보다 하나의 트리로 중첩된다는 점입니다. 둘째, 일반적인 Redux 앱은 Model 및 Collection 인스턴스를 생성하지 않고 일반 JavaScript 객체와 배열 사용을 권장하기 때문에 동등한 Backbone 앱보다 메모리 사용량이 다소 적을 것입니다. 마지막으로 Redux는 한 번에 단일 상태 트리 참조만 유지합니다. 해당 트리에서 더 이상 참조되지 않는 객체는 평소처럼 가비지 컬렉션됩니다.
Redux 자체는 액션 기록을 저장하지 않습니다. 하지만 Redux DevTools는 액션을 재생할 수 있도록 저장하며, 이는 일반적으로 개발 중에만 활성화되고 프로덕션에서는 사용되지 않습니다.
추가 정보
문서
토론
원격 데이터 캐싱이 메모리 문제를 일으킬까요?
브라우저에서 실행되는 JavaScript 애플리케이션의 사용 가능한 메모리 양은 유한합니다. 캐시 크기가 사용 가능한 메모리 양에 근접하면 데이터 캐싱이 성능 문제를 일으킵니다. 이는 캐시된 데이터가 매우 크거나 세션이 매우 길게 실행될 때 문제가 되는 경향이 있습니다. 이러한 문제 발생 가능성을 인지하는 것은 좋지만, 합리적인 양의 데이터를 효율적으로 캐싱하는 데 방해가 되어서는 안 됩니다.
원격 데이터를 효율적으로 캐싱하는 몇 가지 접근법은 다음과 같습니다:
첫째, 사용자에게 필요한 만큼의 데이터만 캐시하세요. 애플리케이션이 페이징된 레코드 목록을 표시하는 경우 전체 컬렉션을 반드시 캐시할 필요는 없습니다. 대신 사용자에게 보이는 데이터만 캐시하고, 사용자가 추가 데이터를 즉시 필요로 하거나(곧 필요하게 될 경우) 그때마다 캐시에 추가하세요.
둘째, 가능하면 레코드의 요약된 형태를 캐시하세요. 때로 레코드에는 사용자와 관련 없는 데이터가 포함됩니다. 애플리케이션이 이 데이터에 의존하지 않는다면 캐시에서 생략할 수 있습니다.
셋째, 레코드의 단일 복사본만 캐시하세요. 이는 레코드에 다른 레코드의 사본이 포함될 때 특히 중요합니다. 각 레코드에 대해 고유한 복사본을 캐시하고 중첩된 각 복사본을 참조로 대체하세요. 이를 정규화(normalization)라고 합니다. 정규화는 효율적인 메모리 소비를 포함한 여러 이유로 관계형 데이터 저장 시 선호되는 접근법입니다.
추가 정보
토론