Як відобразити показник завантаження у програмі React Redux під час отримання даних? [зачинено]


107

Я новачок у React / Redux. Я використовую завантажене програмне забезпечення api в додатку Redux для обробки API. Це ( redux-api-middleware ). Я думаю, що це хороший спосіб опрацювати дії асинхронізації api. Але я знаходжу деякі випадки, які неможливо вирішити самостійно.

Як кажуть на домашній сторінці ( життєвий цикл ), життєвий цикл API для початку починається з відправлення дії CALL_API і закінчується відправленням дії FSA.

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

Але як це зробити? Потік реакції компонентів на всю сторінку? що відбувається з оновленням магазину від інших дій? Я маю на увазі, вони більше схожі на події, ніж на державні!

Ще гірший випадок: що мені робити, коли мені доведеться використовувати нативне діалогове вікно підтвердження або діалогове вікно попередження у програмах з наддувом / реагуванням? Куди їх слід ставити, дії чи редуктори?

Найкращі побажання! Побажання відповісти.


1
Отримала останню редагування цього питання, оскільки вона змінила всю точку питання та відповіді нижче.
Грегг Б

Подія - це зміна штату!
企业 应用 架构 模式 大师

Погляньте на квестр. github.com/orar/questrar
ORAR

Відповіді:


152

Я маю на увазі, вони більше схожі на події, ніж на державні!

Я б не сказав цього. Я думаю, що показники завантаження є чудовим випадком користувальницького інтерфейсу, який легко описується як функція стану: в даному випадку булевої змінної. Хоча ця відповідь правильна, я хотів би надати якийсь код, який буде разом із нею.

У asyncприкладі в Redux repo , редуктор оновлює поле під назвою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опору у render()функції для відображення мітки "Завантаження ..." (яка може бути замість цього спінером):

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

Ще гірший випадок: що мені робити, коли мені доведеться використовувати нативне діалогове вікно підтвердження або діалогове вікно попередження у програмах з наддувом / реагуванням? Куди їх слід ставити, дії чи редуктори?

Будь-які побічні ефекти (а показ діалогового вікна, безумовно, побічний ефект) не відносяться до редукторів. Подумайте про редуценти як про пасивних "будівельників держави". Вони насправді не «роблять» речі.

Якщо ви хочете показати сповіщення, або зробіть це з компонента перед відправленням дії, або зробіть це у творця дії. На момент відправлення дії, у відповідь на неї вже пізно виконувати побічні ефекти.

Для кожного правила є виняток. Іноді логіка вашої побічної дії настільки складна, що ви насправді хочете з'єднати їх або з конкретними типами дій, або з певними редукторами. У цьому випадку ознайомтеся з передовими проектами, такими як Redux Saga та Redux Loop . Робіть це лише тоді, коли вам подобається ванільний Redux і у вас справжня проблема розсіяних побічних ефектів, які ви хочете зробити більш керованими.


16
Що робити, якщо у мене буде декілька результатів? Тоді однієї змінної було б недостатньо.
філк

1
@philk, якщо у вас кілька варіантів, ви можете згрупувати їх Promise.allв одну обіцянку, а потім відправити одну дію для всіх результатів. Або вам доведеться підтримувати кілька isFetchingзмінних у своєму стані.
Себастьян Лорбер

2
Будь ласка, уважно подивіться на приклад, на який я посилаюся. Там більше одного isFetchingпрапора. Він встановлюється для кожного набору об'єктів, які вибираються. Ви можете використовувати редукторний склад для його здійснення.
Дан Абрамов

3
Зауважте, що якщо запит не працює і RECEIVE_POSTSніколи не спрацьовує, знак завантаження залишиться на місці, якщо ви не створили якийсь час очікування для відображення error loadingповідомлення.
James111

2
@TomiS - Я чітко перекладаю чорний список всіх моїх властивостей isFetching з будь-якої наполегливої ​​наполегливості, яку я використовую.
duhseekoh

22

Чудова відповідь Дан Абрамов! Просто хочу додати, що я робив більш-менш точно те, що в одному з моїх додатків (зберігаючи 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а безлічі: один на subreddit (що є тим, що отримується в цьому прикладі).
Дан Абрамов

2
ой. так, я цього не помічав. однак у моєму випадку у мене є одна глобальна запис «Вибір» у стані та запис у кеш, де зберігаються отримані дані, і для моїх цілей мені дуже важливо лише те, що відбувається деяка мережева діяльність, це не дуже важливо, для чого
Nuno Campos

4
Так! Це залежить від того, чи хочете ви показати показник завантаження в одному чи багатьох місцях інтерфейсу користувача. Насправді ви можете комбінувати два підходи та мати як глобальну fetchCounterдля деякої смуги прогресу у верхній частині екрана, так і декілька конкретних isFetchingпрапорів для списків та сторінок.
Дан Абрамов

Якщо у мене є запити POST у більш ніж одному файлі, як я встановив би стан isFetching, щоб слідкувати за його поточним станом?
користувач989988

13

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

Мені сталося, що я хотів отримати деталі для конкретної сутності, яка не вписується в шаблон пагинації. Я хотів мати стан, що представляє, якщо дані надходять із сервера, але також я не хотів мати редуктор саме для цього.

Для вирішення цього питання я додав ще один загальний редуктор, який називається fetching. Він працює аналогічно редуктору сторінки, і його відповідальність полягає лише в тому, щоб спостерігати за набором дій і створювати новий стан за допомогою пар [entity, isFetching]. Це дозволяє connectскоротити будь-який компонент і дізнатися, чи додаток наразі отримує дані не лише для колекції, але для певного об'єкта.


2
Дякую за відповідь! Поводження з завантаженням окремих предметів та їх статусом рідко обговорюється!
Гілад Пелег

Коли у мене є один компонент, який залежить від дії іншого, швидкий і брудний вихід є у вашій картіStateToProps комбінуйте їх так: isFetching: posts.isFetching || comments.isFetching - тепер ви можете блокувати взаємодію користувачів обох компонентів, коли будь-який з них оновлюється.
Філіп Мерфі

5

З цим питанням до цього часу я не траплявся, але оскільки відповіді не прийнято, я кину в шапку. Я написав інструмент для цієї самої роботи: react-loader-factory . Це відбувається дещо більше, ніж рішення Абрамова, але є більш модульним і зручним, оскільки мені не хотілося думати після того, як я його написав.

Є чотири великі твори:

  • Фабрична схема: Це дозволяє швидко викликати ту саму функцію, щоб встановити, які стани означають "Завантаження" для вашого компонента та які дії для відправки. (Це передбачає, що компонент несе відповідальність за запуск дій, які він чекає.)const loaderWrapper = loaderFactory(actionsList, monitoredStates);
  • Обгортка: Компонент, який виробляє завод, - це "компонент вищого порядку" (наприклад, те, що connect()повертається в Redux), так що ви можете просто зафіксувати його на існуючих речах.const LoadingChild = loaderWrapper(ChildComponent);
  • Взаємодія дій / редукторів: обгортка перевіряє, чи вбудований у нього редуктор містить ключові слова, які дозволяють йому не переходити до компонента, який потребує даних. Дії відправлені в обгортці , як очікується , для отримання пов'язаних ключових слів (спосіб перевождь-ІСН проміжної депеші ACTION_SUCCESSі ACTION_REQUEST, наприклад). (Ви можете, звичайно, відправляти дії в іншому місці та просто стежити за обгорткою, якщо хочете.)
  • Throbber: Компонент, який ви хочете відобразити, поки дані, від яких залежить ваш компонент, ще не готові. Я додав туди маленьку діву, щоб ви могли її протестувати, не маючи її.

Сам модуль не залежить від 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, і те, що я роблю, це те, що у мене є послуга "Loading", яка виставляє різні показники завантаження через RxJS BehaviourSubjects. Я думаю, механізм такий же, я просто не зберігаю інформацію в Redux.

Користувачі LoadingService просто підписалися на ті події, які вони хочуть слухати ..

Мої творці дій Redux викликають LoadingService, коли щось потрібно змінити. Компоненти UX підписалися на відкриті спостережні дані ...


саме тому мені подобається ідея магазину, де всі дії можна опитувати (ngrx та redux-логіка), сервіс не функціональний, redux-логіка - функціональний. Приємне читання
srghma

20
Привіт, перевіряю ще через рік після, просто сказати, що я дуже помилявся. Звичайно, стан UX належить до стану додатка. Яким дурним я міг бути?
Спок

3

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

alertі confirmце не повинно бути проблемою. Вони блокують, і попередження навіть не приймає користувачеві жодного вкладу. З confirm, ви можете встановити стан на основі того, що користувач натиснув, якщо вибір користувача повинен вплинути на надання компонентів. Якщо ні, ви можете зберегти вибір як змінну члена компонента для подальшого використання.


про код оповіщення / підтвердження, де їх слід вводити, дії чи редуктори?
企业 应用 架构 模式 大师

Залежить від того, що ви хочете зробити з ними, але, чесно кажучи, я б розміщував їх у компонентному коді у більшості випадків, оскільки вони є частиною інтерфейсу, а не рівня даних.
Мілош Рашич

деякі компоненти інтерфейсу дії діють шляхом ініціювання події (події зміни статусу) замість самого статусу. Такі як анімація, показ / приховування попереднього завантажувача. Як ви їх обробляєте?
企业 应用 架构 模式 大师

Якщо ви хочете використовувати компонент, що не реагує, у вашій програмі реагування, загальноприйнятим рішенням є виготовлення компонента реагування на обгортку, а потім використовувати методи життєвого циклу для ініціалізації, оновлення та знищення екземпляра компонента, що не реагує. Більшість таких компонентів використовують елементи-заповнювачі в DOM для ініціалізації, а ви будете виводити їх у методі візуалізації компонента-реагувача. Детальніше про методи життєвого циклу ви можете прочитати тут: facebook.github.io/react/docs/component-specs.html
Мілош Рашич

У мене є випадок: область сповіщень у правому верхньому куті, яка містить одне повідомлення-повідомлення, кожне повідомлення з’являється, а потім зникає через 5 секунд. Цей компонент перебуває поза веб-переглядом, який надається рідним додатком хоста. Він надає деякі js-інтерфейси, такі як addNofication(message). Інший випадок - це попередні завантажувачі, які також надаються власним додатком хоста і запускаються його JavaScript API. Я додаю обгортку для тих api, в componentDidUpdateкомпонент React. Як створити реквізит або стан цього компонента?
企业 应用 架构 模式 大师

3

У нашому додатку є три типи сповіщень, усі вони розроблені як аспекти:

  1. Індикатор завантаження (модальний або немодальний на основі опори)
  2. Помилка спливаючого вікна (модально)
  3. Снек-бар сповіщень (немодальний, самозакриваючись)

Усі три з них перебувають на найвищому рівні нашого додатка (Main) та проводяться через Redux, як показано на фрагменті коду нижче. Ці реквізити керують відображенням відповідних аспектів.

Я створив проксі-сервер, який обробляє всі наші виклики API, таким чином, всі помилки isFetching та (api) опосередковуються за допомогою actionCreators, які я імпортую в проксі. (В сторону я також використовую webpack, щоб ввести макет служби резервного копіювання для розробника, щоб ми могли працювати без серверних залежностей.)

Будь-яке інше місце в додатку, якому потрібно надати будь-який тип сповіщення, просто імпортує відповідні дії. У 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 розширює React.Component {


Я працюю над аналогічною установкою, показуючи завантажувач / сповіщення. Я стикаюся з проблемами; чи є у вас суть або приклад того, як ви виконуєте ці завдання?
Аймен

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
);

Щоб зробити URL унікальним у випадку POST, я передаю деяку змінну як запит.

І там, де я хочу показати показник, я просто використовую змінну getFetchCount


1
Ви можете замінити Object.keys(items).filter(item => items[item] === true).length > 0 ? true : falseна Object.keys(items).every(item => items[item]), до речі.
Олександр Еннік

1
Я думаю, що ви мали на увазі someзамість цього every, але так, занадто багато непотрібних порівнянь у першому запропонованому рішенні. Object.entries(items).some(([url, fetching]) => fetching);
Рафаель Поррас Лусена
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.