본문으로 건너뛰기

React Redux

비공식 베타 번역

이 페이지는 PageTurner AI로 번역되었습니다(베타). 프로젝트 공식 승인을 받지 않았습니다. 오류를 발견하셨나요? 문제 신고 →

Redux FAQ: React Redux

React-Redux를 사용해야 하는 이유는 무엇인가요?

Redux 자체는 React, Angular, Vue, Ember, 순수 JS를 포함한 모든 UI 레이어나 프레임워크와 함께 사용할 수 있는 독립형 라이브러리입니다. Redux와 React가 일반적으로 함께 사용되지만, 이들은 서로 독립적으로 동작합니다.

UI 프레임워크와 함께 Redux를 사용할 때는 일반적으로 스토어와 직접 상호작용하기보다 "UI 바인딩" 라이브러리를 사용해 Redux와 UI 프레임워크를 연결합니다.

React-Redux는 React용 공식 Redux UI 바인딩 라이브러리입니다. Redux와 React를 함께 사용한다면 이 두 라이브러리를 연결하기 위해 React-Redux도 사용해야 합니다.

Redux 스토어 구독 로직을 직접 작성하는 것도 가능하지만, 이는 매우 반복적인 작업이 됩니다. 또한 UI 성능 최적화에는 복잡한 로직이 필요합니다.

스토어 구독, 업데이트된 데이터 확인, 리렌더링 트리거 과정은 보다 일반적이고 재사용 가능하게 만들 수 있습니다. React-Redux 같은 UI 바인딩 라이브러리는 스토어 상호작용 로직을 처리하므로 직접 그 코드를 작성할 필요가 없습니다.

전반적으로 React-Redux는 훌륭한 React 아키텍처를 장려하며, 복잡한 성능 최적화를 구현해 줍니다. 또한 Redux와 React의 최신 API 변경 사항을 반영하여 최신 상태를 유지합니다.

추가 정보

문서

컴포넌트가 리렌더링되지 않거나 mapStateToProps가 실행되지 않는 이유는 무엇인가요?

액션 디스패치 후 컴포넌트가 리렌더링되지 않는 가장 흔한 이유는 실수로 상태를 직접 변형(mutate)했기 때문입니다. Redux는 리듀서가 상태를 "불변적으로(immutably)" 업데이트할 것을 기대하며, 이는 항상 데이터 복사본을 만들고 변경 사항을 복사본에 적용하는 것을 의미합니다. 리듀서가 동일한 객체를 반환하면 내용을 변경했더라도 Redux는 아무것도 변경되지 않았다고 가정합니다. 마찬가지로 React Redux는 shouldComponentUpdate에서 들어오는 props에 대한 얕은 참조 동등성(shallow equality) 검사를 수행해 성능을 향상시키려고 합니다. 모든 참조가 동일하면 shouldComponentUpdatefalse를 반환해 실제 컴포넌트 업데이트를 건너뜁니다.

중첩된 값을 업데이트할 때마다 상태 트리 상위의 모든 요소도 새 복사본으로 반환해야 한다는 점을 기억해야 합니다. state.a.b.c.d가 있을 때 d를 업데이트하려면 c, b, a, state의 새 복사본도 반환해야 합니다. 이 상태 트리 변형 다이어그램은 트리 깊숙한 곳의 변경이 상위 전체에 걸쳐 변경을 필요로 하는 방식을 보여줍니다.

"데이터를 불변적으로 업데이트"한다고 해서 반드시 Immer를 사용해야 한다는 의미는 아니며, 물론 사용할 수도 있습니다. 일반 JS 객체와 배열에 대해 여러 다른 접근법으로 불변 업데이트를 수행할 수 있습니다:

  • Object.assign()이나 _.extend() 같은 함수로 객체 복사, slice()concat() 같은 배열 함수 사용

  • ES2015의 배열 스프레드 연산자, ES2018의 유사한 객체 스프레드 연산자

  • 불변 업데이트 로직을 간단한 함수로 래핑하는 유틸리티 라이브러리

추가 정보

문서

아티클

토론

왜 컴포넌트가 너무 자주 리렌더링되나요?

React Redux는 실제 컴포넌트가 정말 필요할 때만 리렌더링되도록 여러 최적화를 구현합니다. 그중 하나는 connect에 전달된 mapStateToPropsmapDispatchToProps 인수로 생성된 결합된 props 객체에 대한 얕은 동등성(shallow equality) 검사입니다. 안타깝게도 mapStateToProps가 호출될 때마다 새로운 배열이나 객체 인스턴스가 생성되는 경우에는 얕은 동등성 검사가 도움이 되지 않습니다. 대표적인 예시로 ID 배열을 매핑하고 일치하는 객체 참조를 반환하는 경우가 있습니다:

const mapStateToProps = state => {
return {
objects: state.objectIds.map(id => state.objects[id])
}
}

매번 동일한 객체 참조가 포함되어 있더라도 배열 자체는 다른 참조이므로 얕은 동등성 검사가 실패하고 React Redux는 래핑된 컴포넌트를 리렌더링합니다.

추가 리렌더링 문제는 리듀서를 사용해 객체 배열을 상태에 저장하거나, Reselect로 매핑된 배열을 캐싱하거나, 컴포넌트에서 shouldComponentUpdate를 직접 구현하고 _.isEqual 같은 함수로 props를 심층 비교하는 것으로 해결할 수 있습니다. 커스텀 shouldComponentUpdate()가 실제 렌더링보다 더 많은 비용을 발생시키지 않도록 주의하세요! 성능 가정을 검증할 때는 항상 프로파일러를 사용하십시오.

연결되지 않은(non-connected) 컴포넌트의 경우 전달되는 props를 확인해야 합니다. 흔한 문제로 부모 컴포넌트가 렌더 함수 내에서 콜백을 재바인딩하는 경우가 있습니다: <Child onClick={this.handleClick.bind(this)} />. 이는 부모가 리렌더링될 때마다 새로운 함수 참조를 생성합니다. 일반적으로 부모 컴포넌트의 생성자에서 콜백을 한 번만 바인딩하는 것이 좋은 관행입니다.

추가 정보

문서

아티클

토론

라이브러리

mapStateToProps 성능을 어떻게 향상시킬 수 있나요?

React Redux는 mapStateToProps 함수 호출 횟수를 최소화하도록 설계되었지만, mapStateToProps 실행 속도를 빠르게 유지하고 작업량을 최소화하는 것이 중요합니다. 일반적으로 권장하는 방법은 Reselect를 사용해 메모이제이션된 "셀렉터" 함수를 생성하는 것입니다. 이러한 셀렉터는 조합 및 연계가 가능하며, 입력값이 변경된 경우에만 파이프라인 후반부 셀렉터가 실행됩니다. 이로 인해 필터링이나 정렬 같은 작업을 수행하는 셀렉터를 생성할 수 있으며 실제 작업은 필요한 경우에만 발생하도록 보장됩니다.

추가 정보

문서

아티클

토론

연결된 컴포넌트에서 this.props.dispatch를 사용할 수 없는 이유는 무엇인가요?

connect() 함수는 두 가지 주요 인수를 받으며 둘 다 선택사항입니다. 첫 번째 mapStateToProps는 스토어 변경 시 데이터를 가져와 컴포넌트에 props로 전달하는 함수입니다. 두 번째 mapDispatchToProps는 스토어의 dispatch 함수를 활용하기 위한 함수로, 일반적으로 액션 생성자를 미리 바인딩하여 호출 즉시 액션을 자동으로 디스패치합니다.

connect() 호출 시 커스텀 mapDispatchToProps 함수를 제공하지 않으면 React Redux는 기본 버전을 제공하며, 이는 dispatch 함수를 prop으로 반환합니다. 즉, 커스텀 함수를 제공할 경우 dispatch가 자동으로 prop으로 제공되지 않습니다. 여전히 prop으로 사용하려면 mapDispatchToProps 구현에서 명시적으로 반환해야 합니다.

추가 정보

문서

토론

최상위 컴포넌트만 연결해야 할까요, 아니면 트리 내 여러 컴포넌트를 연결할 수 있나요?

초기 Redux 문서는 컴포넌트 트리 상단에 소수의 연결된 컴포넌트만 두도록 권장했습니다. 그러나 시간 경험상 이러한 아키텍처는 컴포넌트가 모든 하위 요소의 데이터 요구사항을 과도하게 인지하게 하며 혼란스러운 수준의 props 전달을 강제합니다.

현재 권장되는 모범 사례는 컴포넌트를 "표현형(Presentational)"과 "컨테이너(Container)"로 분류하고, 적절한 위치에서 연결된 컨테이너 컴포넌트를 추출하는 것입니다:

Redux 예제에서 "최상단 하나의 컨테이너 컴포넌트"를 강조한 것은 실수였습니다. 이를 원칙으로 삼지 마세요. 표현형 컴포넌트를 분리하세요. 편리할 때 연결하여 컨테이너 컴포넌트를 생성하세요. 동일한 유형의 자식 컴포넌트에 데이터를 제공하기 위해 부모 컴포넌트에서 코드를 중복 작성한다고 느껴질 때가 컨테이너를 추출할 시점입니다. 일반적으로 부모가 자식의 "개인적" 데이터나 액션을 지나치게 많이 알게 될 때 컨테이너를 추출하세요.

실제로 벤치마크에 따르면 더 많은 연결된 컴포넌트가 더 적은 연결된 컴포넌트보다 일반적으로 성능이 우수합니다.

일반적으로 컴포넌트 간에 이해하기 쉬운 데이터 흐름과 책임 영역 사이의 균형을 찾으려고 노력하세요.

추가 정보

문서

아티클

토론

Redux는 React Context API와 어떻게 비교되나요?

유사점

Redux와 React의 Context API 모두 "프롭 드릴링" 문제를 해결합니다. 둘 다 여러 컴포넌트 계층을 거치지 않고도 데이터를 전달할 수 있게 합니다. 내부적으로 Redux는 컴포넌트 트리 전체에 스토어를 전달할 수 있게 해주는 React Context API를 사용합니다.

차이점

Redux를 사용하면 Redux DevTools 확장 프로그램의 강력한 기능을 활용할 수 있습니다. 이는 앱에서 수행되는 모든 액션을 자동으로 기록하며, 타임 트래블링 기능을 제공합니다. 즉, 과거의 특정 액션을 클릭해 해당 시점으로 이동할 수 있습니다. 또한 Redux는 미들웨어 개념을 지원하여 모든 액션 디스패치 시 사용자 정의 함수 호출을 바인딩할 수 있습니다. 대표적인 예로 자동 이벤트 로깅, 특정 액션 가로채기 등이 있습니다.

React의 Context API는 상호 통신하는 컴포넌트 쌍을 다룹니다. 이는 관련 없는 데이터 간에 깔끔한 분리를 제공합니다. 또한 컴포넌트에 데이터를 사용하는 방식에 유연성을 제공합니다. 즉, 부모 컴포넌트의 상태를 제공하거나 래핑된 컴포넌트에 컨텍스트 데이터를 프롭으로 전달할 수 있습니다.

Redux와 React Context가 데이터를 처리하는 방식에는 근본적인 차이가 있습니다. Redux는 전체 애플리케이션의 데이터를 거대한 상태 저장 객체로 유지합니다. 제공된 리듀서 함수를 실행해 데이터 변경을 추론하며, 디스패치된 모든 액션에 대응하는 다음 상태를 반환합니다. React Redux는 컴포넌트 렌더링을 최적화하고 필요한 데이터가 변경될 때만 각 컴포넌트가 리렌더링되도록 보장합니다. 반면 Context는 상태를 보유하지 않습니다. 단지 데이터를 전달하는 통로 역할만 하며, 데이터 변경을 표현하려면 부모 컴포넌트의 상태에 의존해야 합니다.

추가 정보