데이터를 가져 오는 동안 React Redux 앱에서 로딩 표시기를 표시하는 방법은 무엇입니까? [닫은]


107

저는 React / Redux를 처음 사용합니다. API를 처리하기 위해 Redux 앱에서 fetch api 미들웨어를 사용합니다. ( redux-api-middleware )입니다. 비동기 API 작업을 처리하는 좋은 방법이라고 생각합니다. 하지만 혼자서 해결할 수없는 경우도 있습니다.

홈페이지 ( Lifecycle )에서 말했듯이 Fetch API 수명주기는 CALL_API 작업을 전달하는 것으로 시작하여 FSA 작업을 전달하는 것으로 끝납니다.

그래서 첫 번째 경우는 API를 가져올 때 프리 로더를 표시 / 숨기기입니다. 미들웨어는 처음에 FSA 작업을 전달하고 마지막에 FSA 작업을 전달합니다. 두 동작 모두 정상적인 데이터 처리 만 수행해야하는 감속기에 의해 수신됩니다. UI 작업도 더 이상 작업하지 않습니다. 처리 상태를 상태로 저장 한 다음 스토어 업데이트시 렌더링해야 할 수도 있습니다.

하지만 어떻게할까요? 전체 페이지에 대한 반응 구성 요소 흐름? 다른 작업에서 상점을 업데이트하면 어떻게됩니까? 내 말은 그들이 상태 라기보다는 사건에 가깝다는 뜻이다!

더 나쁜 경우에는 redux / react 앱에서 기본 확인 대화 상자 또는 경고 대화 상자를 사용해야 할 때 어떻게해야합니까? 그들은 어디에 넣어야합니까, 행동 또는 감속기?

최고의 소원! 답장을 원합니다.


1
아래 질문 및 답변의 전체 내용이 변경되었으므로이 질문에 대한 마지막 편집을 롤백했습니다.
Gregg B

이벤트는 상태의 변화입니다!
企业应用架构模式大师

퀘스트 라를보세요. github.com/orar/questrar
Orar 2018

답변:


152

내 말은 그들이 상태 라기보다는 사건에 가깝다는 뜻이다!

나는 그렇게 말하지 않을 것입니다. 로드 인디케이터는 상태의 함수로 쉽게 설명되는 UI의 훌륭한 사례라고 생각합니다.이 경우 부울 변수입니다. 이 대답 은 정확 하지만 함께 사용할 코드를 제공하고 싶습니다.

asyncRedux repo예제에서 reducer 는 다음 필드를 업데이트합니다isFetching .

case REQUEST_POSTS:
  return Object.assign({}, state, {
    isFetching: true,
    didInvalidate: false
  })
case RECEIVE_POSTS:
  return Object.assign({}, state, {
    isFetching: false,
    didInvalidate: false,
    items: action.posts,
    lastUpdated: action.receivedAt

구성 요소 용도 connect()돌아 오는 반작용에서이 가게의 상태에 가입하는 수익률 isFetching의 일부로 mapStateToProps()반환 값 은 연결된 기기의 소품에서 사용할 수 있도록 :

function mapStateToProps(state) {
  const { selectedReddit, postsByReddit } = state
  const {
    isFetching,
    lastUpdated,
    items: posts
  } = postsByReddit[selectedReddit] || {
    isFetching: true,
    items: []
  }

  return {
    selectedReddit,
    posts,
    isFetching,
    lastUpdated
  }
}

마지막으로, 컴포넌트 는 함수 isFetching에서 prop을 사용render() 하여 "Loading ..."레이블을 렌더링합니다 (대신 스피너 일 수 있음).

{isEmpty
  ? (isFetching ? <h2>Loading...</h2> : <h2>Empty.</h2>)
  : <div style={{ opacity: isFetching ? 0.5 : 1 }}>
      <Posts posts={posts} />
    </div>
}

더 나쁜 경우에는 redux / react 앱에서 기본 확인 대화 상자 또는 경고 대화 상자를 사용해야 할 때 어떻게해야합니까? 그들은 어디에 넣어야합니까, 행동 또는 감속기?

모든 부작용 (및 대화 상자 표시는 가장 확실한 부작용)은 리듀서에 속하지 않습니다. 감속기를 수동적 인 "상태 구축 자"라고 생각하십시오. 그들은 실제로 일을 "행"하지 않습니다.

경고를 표시하려면 작업을 디스패치하기 전에 구성 요소에서 수행하거나 작업 생성자에서 수행하십시오. 조치가 전달 될 때까지 이에 대응하여 부작용을 수행하기에는 너무 늦습니다.

모든 규칙에는 예외가 있습니다. 때로는 부작용 로직이 너무 복잡해서 실제로 특정 액션 유형이나 특정 리듀서에 연결 하고 싶을 때가 있습니다 . 이 경우 Redux SagaRedux Loop 와 같은 고급 프로젝트를 확인하십시오 . 바닐라 Redux에 익숙하고 더 관리하기 쉽게 만들고 싶은 흩어진 부작용의 실제 문제가있는 경우에만이 작업을 수행하십시오.


16
여러 가져 오기가 진행되는 경우 어떻게 되나요? 그러면 하나의 변수로는 충분하지 않습니다.
philk

1
여러 가져 오기가있는 경우 @philk를 Promise.all하나의 promise로 그룹화 한 다음 모든 가져 오기에 대해 단일 작업을 전달할 수 있습니다 . 또는 isFetching상태에서 여러 변수 를 유지해야합니다 .
Sebastien Lorber

2
링크 된 예제를주의 깊게 살펴보십시오. isFetching플래그 가 두 개 이상 있습니다 . 가져 오는 모든 개체 집합에 대해 설정됩니다. 감속기 구성을 사용하여 구현할 수 있습니다.
Dan Abramov

3
요청이 실패하고 RECEIVE_POSTS트리거되지 않은 경우 error loading메시지 를 표시하기 위해 일종의 시간 제한을 생성하지 않는 한 로딩 기호가 그대로 유지됩니다 .
James111

2
@TomiS-내가 사용하는 redux 지속성에서 모든 isFetching 속성을 명시 적으로 블랙리스트에 올립니다.
duhseekoh

22

Dan Abramov의 훌륭한 답변입니다! 내 앱 중 하나에서 (isFetching을 부울로 유지) 어느 정도 정확하게 수행하고 있으며 여러 동시를 지원하기 위해 정수 (미해결 요청 수로 읽음)로 만들어야한다는 것을 추가하고 싶습니다. 요청.

부울 사용 :

요청 1 시작-> 스피너 켜기-> 요청 2 시작-> 요청 1 종료-> 스피너 끄기-> 요청 2 종료

정수 포함 :

요청 1 시작-> 스피너 켜기-> 요청 2 시작-> 요청 1 종료-> 요청 2 끝-> 스피너 끄기

case REQUEST_POSTS:
  return Object.assign({}, state, {
    isFetching: state.isFetching + 1,
    didInvalidate: false
  })
case RECEIVE_POSTS:
  return Object.assign({}, state, {
    isFetching: state.isFetching - 1,
    didInvalidate: false,
    items: action.posts,
    lastUpdated: action.receivedAt

2
이것은 합리적입니다. 그러나 대부분의 경우 플래그와 함께 가져온 일부 데이터 를 저장하려고합니다 . 이 시점에서 isFetching플래그가있는 개체가 두 개 이상 있어야합니다 . 내가 링크 한 예제를 자세히 살펴보면 하나의 객체가 isFetched아니라 많은 객체가 있음을 알 수 있습니다. 서브 레 딧당 하나 (이 예제에서 가져온 것입니다).
Dan Abramov

2
오. 그래 나는 그것을 몰랐다. 그러나 제 경우에는 상태에 하나의 전역 isFetching 항목이 있고 가져온 데이터가 저장되는 캐시 항목이 있습니다. 제 목적을 위해 일부 네트워크 활동이 발생하고 있다는 사실 만 신경 쓰고 있습니다. 어떤 용도로든 상관 없습니다
Nuno Campos

4
네! 가져 오기 표시기를 UI의 하나 또는 여러 위치에 표시할지 여부에 따라 다릅니다. 실제로 두 가지 접근 방식을 결합하고 화면 상단의 일부 진행률 표시 줄에 대한 전역 과 목록 및 페이지에 대한 여러 특정 플래그를 모두 가질 수 있습니다 . fetchCounterisFetching
Dan Abramov

둘 이상의 파일에 POST 요청이있는 경우 현재 상태를 추적하기 위해 isFetching 상태를 어떻게 설정합니까?
user989988

13

뭔가를 추가하고 싶습니다. 실제 예에서는 isFetching상점 의 필드를 사용하여 항목 컬렉션 을 가져올 때를 나타냅니다 . 모든 컬렉션은 pagination상태를 추적하고 컬렉션이로드 중인지 표시하기 위해 구성 요소에 연결할 수 있는 감속기 로 일반화됩니다 .

페이지 매김 패턴에 맞지 않는 특정 엔티티에 대한 세부 정보를 가져오고 싶었습니다. 서버에서 세부 정보를 가져 오는지 여부를 나타내는 상태를 갖고 싶었지만이를위한 감속기를 갖고 싶지 않았습니다.

이 문제를 해결하기 위해라는 또 다른 일반 감속기를 추가했습니다 fetching. 페이지 매김 감속기와 비슷한 방식으로 작동 하며 일련의 작업 을 관찰 하고 쌍으로 새로운 상태를 생성하는 것이 책임입니다 [entity, isFetching]. 이를 connect통해 리듀서는 모든 구성 요소에 대해 앱이 현재 컬렉션뿐만 아니라 특정 엔터티에 대한 데이터를 가져 오는지 알 수 있습니다.


2
답변 해주셔서 감사합니다! 개별 항목의로드 및 상태 처리에 대해서는 거의 논의되지 않습니다!
Gilad Peleg 2016 년

다른 작업에 의존하는 하나의 구성 요소가있을 때 빠르고 더러운 방법은 mapStateToProps에 다음과 같이 결합됩니다. isFetching : posts.isFetching || comments.isFetching-이제 둘 중 하나가 업데이트 될 때 두 구성 요소에 대한 사용자 상호 작용을 차단할 수 있습니다.
Philip Murphy

5

나는 지금까지이 질문에 대해 일어나지 않았지만 대답이 받아 들여지지 않았기 때문에 나는 내 모자를 던질 것이다. 이 작업을위한 도구 인 react-loader-factory를 작성했습니다 . Abramov의 솔루션보다 약간 더 진행되지만, 작성한 후에 생각할 필요가 없었기 때문에 모듈화되고 편리합니다.

네 가지 큰 부분이 있습니다.

  • 공장 패턴 : 이렇게하면 동일한 함수를 빠르게 호출하여 구성 요소에 대해 "로드 중"을 의미하는 상태와 디스패치 할 작업을 설정할 수 있습니다. (이것은 구성 요소가 대기하는 작업을 시작하는 책임이 있다고 가정합니다.)const loaderWrapper = loaderFactory(actionsList, monitoredStates);
  • Wrapper : Factory가 생산하는 구성 요소 connect()는 Redux에서 반환되는 것과 같은 "고차 구성 요소" 이므로 기존 항목에 볼트로 고정 할 수 있습니다.const LoadingChild = loaderWrapper(ChildComponent);
  • 액션 / 리듀서 상호 작용 : 래퍼는 연결된 리듀서에 데이터가 필요한 구성 요소를 통과하지 않도록 지시하는 키워드가 포함되어 있는지 확인합니다. 액션은 랩퍼가 파견 관련 키워드 (길 REDUX-API-미들웨어 파견 생산할 것으로 예상된다 ACTION_SUCCESSACTION_REQUEST예를 들어,). (물론 원하는 경우 작업을 다른 곳으로 보내고 래퍼에서 모니터링 할 수 있습니다.)
  • Throbber : 구성 요소가 의존하는 데이터가 준비되지 않은 동안 표시하려는 구성 요소입니다. 거기에 약간의 div를 추가하여 장비하지 않고도 테스트 할 수 있습니다.

모듈 자체는 redux-api-middleware와 무관하지만 이것이 제가 함께 사용하는 것이므로 다음은 README의 샘플 코드입니다.

래핑하는 로더가있는 구성 요소 :

import React from 'react';
import { myAsyncAction } from '../actions';
import loaderFactory from 'react-loader-factory';
import ChildComponent from './ChildComponent';

const actionsList = [myAsyncAction()];
const monitoredStates = ['ASYNC_REQUEST'];
const loaderWrapper = loaderFactory(actionsList, monitoredStates);

const LoadingChild = loaderWrapper(ChildComponent);

const containingComponent = props => {
  // Do whatever you need to do with your usual containing component 

  const childProps = { someProps: 'props' };

  return <LoadingChild { ...childProps } />;
}

로더가 모니터링 할 감속기 ( 원하는 경우 다르게 연결할 수 있음 ) :

export function activeRequests(state = [], action) {
  const newState = state.slice();

  // regex that tests for an API action string ending with _REQUEST 
  const reqReg = new RegExp(/^[A-Z]+\_REQUEST$/g);
  // regex that tests for a API action string ending with _SUCCESS 
  const sucReg = new RegExp(/^[A-Z]+\_SUCCESS$/g);

  // if a _REQUEST comes in, add it to the activeRequests list 
  if (reqReg.test(action.type)) {
    newState.push(action.type);
  }

  // if a _SUCCESS comes in, delete its corresponding _REQUEST 
  if (sucReg.test(action.type)) {
    const reqType = action.type.split('_')[0].concat('_REQUEST');
    const deleteInd = state.indexOf(reqType);

    if (deleteInd !== -1) {
      newState.splice(deleteInd, 1);
    }
  }

  return newState;
}

가까운 장래에 모듈에 시간 초과 및 오류와 같은 것을 추가 할 것으로 예상하지만 패턴은 크게 다르지 않을 것입니다.


귀하의 질문에 대한 짧은 대답은 다음과 같습니다.

  1. 렌더링을 렌더링 코드에 연결합니다. 위에서 보여준 것과 같은 데이터로 렌더링해야하는 구성 요소 주변에 래퍼를 사용합니다.
  2. 관심있는 앱 주변의 요청 상태를 쉽게 소화 할 수 있도록하는 감속기를 추가하면 무슨 일이 일어나고 있는지 너무 열심히 생각할 필요가 없습니다.
  3. 이벤트와 상태는 실제로 다르지 않습니다.
  4. 나머지 직관은 나에게 맞는 것 같습니다.

4

로드 인디케이터가 Redux 스토어에 속하지 않는다고 생각하는 유일한 사람입니까? 내 말은, 응용 프로그램 상태 자체의 일부라고 생각하지 않습니다 ..

이제 Angular2로 작업하고 있는데 RxJS BehaviourSubjects를 통해 다른 로딩 표시기를 노출하는 "Loading"서비스가 있습니다. 메커니즘이 동일하다고 생각합니다. 저는 Redux에 정보를 저장하지 않습니다.

LoadingService 사용자는 듣고 싶은 이벤트를 구독하기 만하면됩니다.

내 Redux 액션 생성자는 변경이 필요할 때마다 LoadingService를 호출합니다. UX 구성 요소는 노출 된 Observable을 구독합니다 ...


이것이 내가 모든 작업이 폴링 될 수 있고 (ngrx 및 redux-logic) 서비스가 작동하지 않고, redux-logic-작동하는 저장소라는 아이디어를 좋아하는 이유입니다. 니스 읽기
srghma

20
안녕하세요, 1 년이 지난 후 다시 확인하여 제가 매우 틀렸다고 말하고 있습니다. 물론 UX 상태는 애플리케이션 상태에 속합니다. 내가 얼마나 멍청 할 수 있니?
Spock

3

connect()React Redux 또는 저수준 store.subscribe()방법을 사용하여 변경 리스너를 상점에 추가 할 수 있습니다 . 상점에로드 표시기가 있어야합니다. 그러면 상점 변경 핸들러가 컴포넌트 상태를 확인하고 업데이트 할 수 있습니다. 그런 다음 구성 요소는 필요한 경우 상태에 따라 프리 로더를 렌더링합니다.

alert그리고 confirm문제가되지 않습니다. 그들은 차단하고 있으며 경고는 사용자로부터 입력을받지 않습니다. 를 사용 confirm하면 사용자 선택이 구성 요소 렌더링에 영향을주는 경우 사용자가 클릭 한 항목을 기반으로 상태를 설정할 수 있습니다. 그렇지 않은 경우 나중에 사용하기 위해 선택 항목을 구성 요소 멤버 변수로 저장할 수 있습니다.


경고 / 확인 코드, 어디에 넣어야합니까?
企业 应用 架构 模式 大师

그것들로 무엇을하고 싶은지에 따라 다르지만 솔직히 데이터 레이어가 아니라 UI의 일부이기 때문에 대부분의 경우 컴포넌트 코드에 넣었습니다.
Miloš Rašić

일부 UI 구성 요소는 상태 자체 대신 이벤트 (상태 변경 이벤트)를 트리거하여 작동합니다. 애니메이션, 프리 로더 표시 / 숨기기 등. 어떻게 처리합니까?
企业 应用 架构 模式 大师

반응 앱에서 비 반응 구성 요소를 사용하려는 경우 일반적으로 사용되는 솔루션은 래퍼 반응 구성 요소를 만든 다음 해당 수명주기 메서드를 사용하여 비 반응 구성 요소의 인스턴스를 초기화, 업데이트 및 삭제하는 것입니다. 대부분의 이러한 구성 요소는 DOM의 자리 표시 자 요소를 사용하여 초기화하며 반응 구성 요소의 렌더링 메서드에서 해당 요소를 렌더링합니다. 라이프 사이클 메소드에 대한 자세한 내용은 여기에서 확인할 수 있습니다. facebook.github.io/react/docs/component-specs.html
Miloš Rašić

하나의 알림 메시지가 포함 된 오른쪽 상단의 알림 영역이 있습니다. 각 메시지는 5 초 후에 사라집니다. 이 구성 요소는 호스트 네이티브 앱에서 제공하는 웹보기 외부에 있습니다. .NET과 같은 일부 js 인터페이스를 제공합니다 addNofication(message). 또 다른 경우는 호스트 네이티브 앱에서 제공하고 자바 스크립트 API에 의해 트리거되는 프리 로더입니다. componentDidUpdateReact 구성 요소 에 해당 API에 대한 래퍼를 추가합니다 . 이 구성 요소의 소품 또는 상태를 어떻게 디자인합니까?
企业应用架构模式大师

3

앱에는 세 가지 유형의 알림이 있으며, 모두 측면으로 설계되었습니다.

  1. 로딩 표시기 (소품을 기반으로 한 모달 또는 비 모달)
  2. 오류 팝업 (모달)
  3. 알림 스낵바 (비 모달, 자동 폐쇄)

이 세 가지 모두 앱의 최상위 수준 (Main)에 있으며 아래 코드 스 니펫과 같이 Redux를 통해 연결됩니다. 이러한 소품은 해당 측면의 표시를 제어합니다.

모든 API 호출을 처리하는 프록시를 설계 했으므로 모든 isFetching 및 (api) 오류는 프록시에서 가져온 actionCreators로 조정됩니다. (제외로, 저는 또한 webpack을 사용하여 dev에 대한 지원 서비스의 모의를 주입하여 서버 종속성없이 작업 할 수 있습니다.)

모든 유형의 알림을 제공해야하는 앱의 다른 위치는 단순히 적절한 작업을 가져옵니다. Snackbar & Error에는 표시 할 메시지에 대한 매개 변수가 있습니다.

@connect(
// map state to props
state => ({
    isFetching      :state.main.get('isFetching'),   // ProgressIndicator
    notification    :state.main.get('notification'), // Snackbar
    error           :state.main.get('error')         // ErrorPopup
}),
// mapDispatchToProps
(dispatch) => { return {
    actions: bindActionCreators(actionCreators, dispatch)
}}

) 내보내기 기본 클래스 Main extends React.Component {


로더 / 알림을 표시하는 유사한 설정에서 작업 중입니다. 문제가 생겼습니다. 이러한 작업을 수행하는 방법에 대한 요점이나 예가 있습니까?
Aymen

2

다음과 같은 URL을 저장하고 있습니다.

isFetching: {
    /api/posts/1: true,
    api/posts/3: false,
    api/search?q=322: true,
}

그런 다음 (재 선택을 통해) 기억 된 선택기가 있습니다.

const getIsFetching = createSelector(
    state => state.isFetching,
    items => items => Object.keys(items).filter(item => items[item] === true).length > 0 ? true : false
);

POST의 경우 URL을 고유하게 만들기 위해 일부 변수를 쿼리로 전달합니다.

인디케이터를 표시하고 싶은 곳에 getFetchCount 변수를 사용합니다.


1
당신은 대체 할 수 있습니다 Object.keys(items).filter(item => items[item] === true).length > 0 ? true : false에 의한 Object.keys(items).every(item => items[item])방법으로.
Alexandre Annic 2018

1
some대신 에을 의미한다고 생각 every하지만 첫 번째 제안 된 솔루션에서 필요하지 않은 비교가 너무 많습니다. Object.entries(items).some(([url, fetching]) => fetching);
Rafael Porras Lucena
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.