Как показать индикатор загрузки в приложении React Redux при извлечении данных?

Я новичок в React/Redux. Я использую промежуточное ПО API fetch в приложении Redux для обработки API. Это (redux-api-middleware). Я думаю, что это хороший способ для обработки асинхронных действий API. Но я нахожу некоторые случаи, которые я не могу решить самостоятельно.

Как говорится на домашней странице (Жизненный цикл), жизненный цикл API выборки начинается с отправки действия CALL_API и заканчивается отправкой действия FSA.

Итак, мой первый случай - показ/скрытие прелоадера при загрузке API. Промежуточное программное обеспечение отправит действие FSA в начале и отправит действие FSA в конце. Оба действия принимаются редукторами, которые должны выполнять только обычную обработку данных. Нет операций пользовательского интерфейса, нет больше операций. Возможно, мне следует сохранить состояние обработки в состоянии, а затем отобразить их при обновлении магазина.

Но как это сделать? Реакция компонента на всю страницу? что происходит с обновлением магазина от других действий? Я имею в виду, что они больше похожи на события, чем на государства!

Даже в худшем случае, что мне делать, если мне нужно использовать собственный диалог подтверждения или диалог предупреждений в приложениях "Редукс/Реакция"? Куда их положить, действия или редукторы?

С наилучшими пожеланиями! Желаю ответить.

Ответ 1

Я имею в виду, что они больше похожи на события, чем состояние!

Я бы так не сказал. Я думаю, что индикаторы загрузки - отличный пример UI, который легко описывается как функция состояния: в данном случае - логическая переменная. В то время как этот ответ правильный, я хотел бы предоставить некоторый код, чтобы согласиться с ним.

В примере async в репозитории Redux редуктор обновляет поле под названием 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() из React Redux для подписки на состояние магазинов и возвращает 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() для отображения метки "Загрузка..." (которая может возможно, вместо этого будет прядильником):

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

Даже худший случай, что мне делать, когда мне нужно использовать встроенный диалог подтверждения или диалоговое окно предупреждения в приложениях redux/react? Где они должны быть помещены, действия или редукторы?

Любые побочные эффекты (и, по-видимому, диалог, безусловно, побочный эффект) не относятся к редукторам. Подумайте о редукторах как о пассивных "строителях государства". Они действительно не "делают" вещи.

Если вы хотите показать предупреждение, либо сделайте это от компонента перед отправкой какого-либо действия, либо сделайте это от создателя действия. К тому времени, когда действие отправлено, слишком поздно выполнять побочные эффекты в ответ на него.

Для каждого правила существует исключение. Иногда ваша логика побочных эффектов настолько сложна, что вы действительно хотите соединить их либо с конкретными типами действий, либо с конкретными редукторами. В этом случае проверьте расширенные проекты, такие как Redux Saga и Redux Loop. Только сделайте это, когда вам будет удобно с ванильным Redux и у вас есть настоящая проблема рассеянных побочных эффектов, которые вы хотели бы сделать более управляемыми.

Ответ 2

Отличный ответ Дэн Абрамов! Просто хочу добавить, что я делал все более или менее точно в одном из своих приложений (сохраняя 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

Ответ 3

Я хотел бы добавить что-то. Пример реального мира использует поле isFetching в хранилище для представления при сборе коллекции элементов. Любая коллекция обобщается на редуктор pagination, который может быть подключен к вашим компонентам, чтобы отслеживать состояние и показывать, загружается ли коллекция.

Случилось со мной, что я хотел получить детали для конкретного объекта, который не соответствует шаблону разбивки на страницы. Я хотел иметь состояние, представляющее, если детали извлекаются с сервера, но также я не хотел иметь редуктор только для этого.

Чтобы решить эту проблему, я добавил еще один общий редуктор под названием fetching. Он работает аналогично редуктору разбиения на страницы, и его обязанностью является просто наблюдать множество действий и генерировать новое состояние с парами [entity, isFetching]. Это позволяет connect редуктору к любому компоненту и знать, действительно ли приложение извлекает данные не только для коллекции, но и для определенного объекта.

Ответ 4

Я до сих пор не встречался с этим вопросом, но, поскольку ответа не принято, я брошу свою шляпу. Я написал инструмент для этой самой работы: react-loader-factory. Это немного больше, чем решение Абрамова, но более модульное и удобное, так как я не хотел думать, когда я его написал.

Есть четыре больших куска:

  • Factory pattern: Это позволяет быстро вызвать ту же функцию, чтобы установить, какие состояния означают "Загрузка" для вашего компонента и какие действия нужно отправить. (Предполагается, что компонент отвечает за запуск ожидаемых действий.) const loaderWrapper = loaderFactory(actionsList, monitoredStates);
  • Wrapper: компонент, созданный Factory, является "компонентом более высокого порядка" (например, что connect() возвращается в Redux), так что вы можете просто закрепить его на существующие вещи. const LoadingChild = loaderWrapper(ChildComponent);
  • Взаимодействие Action/Reducer: обертка проверяет, содержит ли редуктор, в который он подключен, ключевые слова, которые говорят, что они не передают компонент, который нуждается в данных. Ожидается, что действия, отправленные оберткой, будут создавать связанные ключевые слова (например, как dispx-api-middleware отправляет ACTION_SUCCESS и ACTION_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;
}

Я ожидаю, что в ближайшем будущем я добавлю такие моменты, как тайм-аут и ошибку, но шаблон не будет сильно отличаться.


Короткий ответ на ваш вопрос:

  • Отрисовка рендеринга на код рендеринга - используйте обертку вокруг компонента, который нужно отобразить, с данными, подобными тому, который я показал выше.
  • Добавьте редуктор, который делает статус запросов вокруг приложения, о котором вы, возможно, заботитесь легко усваиваемым, поэтому вам не нужно слишком много думать о том, что происходит.
  • События и состояние на самом деле не отличаются.
  • Остальные ваши интуиции кажутся мне верными.

Ответ 5

Вы можете добавить слушателей изменений в свои магазины, используя либо connect() из React Redux, либо низкоуровневый метод store.subscribe(). В вашем магазине должен быть указатель загрузки, который обработчик изменения хранилища может проверить и обновить состояние компонента. Затем компонент добавляет прелоадер в зависимости от состояния.

alert и confirm не должны быть проблемой. Они блокируют и предупреждают, даже не вводят никаких данных от пользователя. С помощью confirm вы можете установить состояние на основе того, что пользователь нажал, если выбор пользователя повлияет на рендеринг компонента. Если нет, вы можете сохранить выбор в качестве переменной-члена компонента для последующего использования.

Ответ 6

У нас есть три типа уведомлений в нашем приложении, все из которых разработаны как аспекты:

  • Индикатор загрузки (модальный или немодальный на основе опоры)
  • Всплывающее окно ошибки (модальное)
  • Закусочная информация (немодальная, самозакрывающаяся)

Все три из них находятся на верхнем уровне нашего приложения (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)
}}

) export default class Main extends React.Component {

Ответ 7

Я единственный, кто думает, что индикаторы загрузки не входят в магазин Redux? Я имею в виду, я не думаю, что это часть состояния приложения как таковое.

Теперь я работаю с Angular2, и то, что я делаю, это то, что у меня есть служба "Загрузка", которая предоставляет различные индикаторы загрузки через RxJS BehaviourSubjects. Я предполагаю, что механизм тот же, я просто не хранил информация в Redux.

Пользователи LoadingService просто подписываются на те события, которые они хотят прослушать.

Мои создатели Action Redux вызывают функцию LoadingService, когда что-то нужно изменить. Компоненты UX подписываются на открытые наблюдаемые...

Ответ 8

Я сохраняю 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
);

Чтобы сделать url уникальным в случае POST, я передаю некоторую переменную в качестве запроса.

И где я хочу показать индикатор, я просто использую переменную getFetchCount