Verileri alırken React Redux uygulamasında bir yükleme göstergesi nasıl gösterilir? [kapalı]


107

React / Redux'te yeniyim. API'leri işlemek için Redux uygulamasında bir getirme api ara yazılımı kullanıyorum. Bu ( redux-api-middleware ). Async API eylemlerini işlemenin iyi bir yolu olduğunu düşünüyorum. Ama kendi başıma çözemeyecek bazı durumlar buluyorum.

Ana sayfanın ( Yaşam Döngüsü ) dediği gibi, bir getirme API yaşam döngüsü bir CALL_API eyleminin gönderilmesiyle başlar, bir FSA eylemi göndererek sona erer.

Bu yüzden benim ilk durumum API'leri getirirken bir önyükleyici gösteriyor / saklıyor. Ara yazılım, başlangıçta bir FSA eylemi gönderir ve sonunda bir FSA eylemi gönderir. Her iki eylem de, yalnızca bazı normal veri işlemlerini gerçekleştirmesi gereken indirgeyiciler tarafından alınır. UI işlemi yok, daha fazla işlem yok. Belki de işlem durumunu durumda kaydetmeli ve ardından mağaza güncellemesi sırasında bunları oluşturmalıyım.

Ama bunu nasıl yapmalı? Tüm sayfa boyunca bir tepki bileşeni akışı mı? diğer işlemlerden mağaza güncellemesine ne olur? Demek istediğim, bunlar eyaletten çok olaylar gibidir!

Daha da kötü bir durum, redux / react uygulamalarında yerel onaylama iletişim kutusunu veya uyarı iletişim kutusunu kullanmam gerektiğinde ne yapmalıyım? Nereye koyulmalı, eylem mi yoksa azaltıcı mı?

En iyi dileklerimle! Cevap diliyorum.


1
Sorunun tüm noktasını ve aşağıdaki yanıtları değiştirdiği için bu soruya yapılan son düzenleme geri alındı.
Gregg B

Olay, durum değişikliğidir!
企业 应用 架构 模式 大师

Questrar'a bir göz atın. github.com/orar/questrar
Orar

Yanıtlar:


152

Demek istediğim, bunlar eyaletten çok olaylar gibidir!

Ben öyle demezdim. Bence yükleme göstergeleri, bir durum işlevi olarak kolayca tanımlanabilecek harika bir UI durumu: bu durumda, bir boole değişkeni. Bu cevap doğru olsa da , onunla birlikte gidebileceğim bazı kodlar sağlamak istiyorum.

In asyncRedux repo Örneğin , redüktör olarak adlandırılan bir alan güncellerisFetching :

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

Bileşen connect(), mağazanın durumuna abone olmak için React Redux'dan kullanır ve geri dönüş değerinin bir isFetchingparçası olarak geri döner ,mapStateToProps() böylece bağlı bileşenin props'larında kullanılabilir:

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

  return {
    selectedReddit,
    posts,
    isFetching,
    lastUpdated
  }
}

Son olarak, bileşen bir "Yükleniyor ..." etiketi oluşturmak için işlevde prop kullanırisFetchingrender() (bunun yerine bir döndürücü de olabilir):

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

Daha da kötü bir durum, redux / react uygulamalarında yerel onaylama iletişim kutusunu veya uyarı iletişim kutusunu kullanmam gerektiğinde ne yapmalıyım? Nereye koyulmalı, eylem mi yoksa azaltıcı mı?

Herhangi bir yan etki (ve bir diyalog göstermek kesinlikle bir yan etkidir) azaltıcılara ait değildir. İndirgeyicileri pasif “devlet kurucuları” olarak düşünün. Gerçekten bir şeyler "yapmıyorlar".

Bir uyarı göstermek isterseniz, bunu ya bir eylemi göndermeden önce bir bileşenden yapın ya da bir eylem oluşturucudan yapın. Bir eylem gönderildiğinde, buna yanıt olarak yan etkiler gerçekleştirmek için çok geç kalmıştır.

Her kuralın bir istisnası vardır. Bazen yan etki mantığınız o kadar karmaşıktır ki, onları ya belirli eylem türleriyle ya da belirli azaltıcılarla birleştirmek istersiniz . Bu durumda Redux Saga ve Redux Loop gibi gelişmiş projelere göz atın . Bunu yalnızca vanilya Redux konusunda rahat olduğunuzda ve daha yönetilebilir hale getirmek istediğiniz gerçek bir dağınık yan etkiler probleminiz olduğunda yapın.


16
Birden fazla getirme işlemim varsa ne olur? O zaman bir değişken yeterli olmayacaktır.
philk

1
@philk birden fazla getirme işleminiz varsa bunları Promise.alltek bir sözde gruplayabilir ve ardından tüm getirmeler için tek bir işlem gönderebilirsiniz. Veya isFetchingeyaletinizde birden çok değişken bulundurmanız gerekir.
Sebastien Lorber

2
Lütfen bağlantı verdiğim örneğe dikkatlice bakın. Birden fazla isFetchingbayrak var. Getirilen her nesne kümesi için ayarlanır. Bunu uygulamak için indirgeyici bileşimi kullanabilirsiniz.
Dan Abramov

3
İstek başarısız olursa ve RECEIVE_POSTShiçbir zaman tetiklenmezse, bir error loadingmesajı göstermek için bir tür zaman aşımı oluşturmadığınız sürece yükleme işaretinin yerinde kalacağını unutmayın .
James111

2
@TomiS - Kullandığım redux kalıcılığından tüm isFetching özelliklerimi açıkça kara listeye alıyorum.
duhseekoh

22

Harika cevap Dan Abramov! Uygulamalarımdan birinde (isFetching'i bir boole olarak tutarak) aşağı yukarı aynen yaptığımı ve birden fazla eşzamanlılığı desteklemek için onu bir tam sayı yapmak zorunda kaldığımı (bu da bekleyen isteklerin sayısı olarak okur) eklemek istiyorum. istekleri.

boole ile:

istek 1 başlatma -> döndürücü açık -> 2 başlatma isteği -> 1 son isteği -> döndürücü kapalı -> istek 2 son

tamsayı ile:

istek 1 başlatma -> döndürücü açık -> 2 başlatma isteği -> 1 son isteği -> 2 uç isteği -> döndürücü kapalı

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
Bu makul. Ancak çoğu zaman , bayrağa ek olarak getirdiğiniz bazı verileri de saklamak istersiniz . Bu noktada birden fazla isFetchingbayraklı nesneye ihtiyacınız olacak . Size bağladığım örneğe yakından bakarsanız, tek bir nesne olmadığını göreceksiniz isFetched: alt düzenleme başına bir nesne (bu örnekte getirilen şey budur).
Dan Abramov

2
oh. evet bunu fark etmedim. ancak benim durumumda eyalette bir global isFetching girişim ve getirilen verilerin depolandığı bir önbellek girişim var ve amaçlarım için sadece bazı ağ aktivitelerinin gerçekleşmesini gerçekten önemsiyorum, gerçekten ne için olduğu önemli değil
Nuno Campos

4
Evet! Getirme göstergesini kullanıcı arayüzünde bir veya daha fazla yerde göstermek isteyip istemediğinize bağlıdır. Aslında, iki yaklaşımı birleştirebilir ve hemfetchCounter ekranın üst kısmındaki bazı ilerleme çubuğu için bir global hem deisFetching listeler ve sayfalar için birkaç özel bayrağa sahip olabilirsiniz.
Dan Abramov

Birden fazla dosyada POST İsteklerim varsa, mevcut durumunu takip etmek için isFetching durumunu nasıl ayarlayabilirim?
user989988

13

Bir şey eklemek istiyorum. Gerçek dünya örneği, bir öğe koleksiyonununisFetching ne zaman getirildiğini göstermek için mağazadaki bir alanı kullanır . Herhangi bir koleksiyon, durumu izlemek ve bir koleksiyonun yüklenip yüklenmediğini göstermek için bileşenlerinize bağlanabilen bir azaltıcı olarak genelleştirilir .pagination

Sayfalandırma düzenine uymayan belirli bir varlığın ayrıntılarını almak istediğim aklıma geldi. Ayrıntıların sunucudan alınıp alınmadığını gösteren bir durum olmasını istedim, ancak bunun için bir redüktör olmasını da istemedim.

Bunu çözmek için adında başka bir genel indirgeyici ekledim fetching. Sayfalandırma azaltıcıya benzer şekilde çalışır ve sorumluluğu sadece bir dizi eylemi izlemek ve çiftlerle yeni durum oluşturmaktır [entity, isFetching]. Bu connect, indirgeyiciye herhangi bir bileşene ve uygulamanın şu anda yalnızca bir koleksiyon için değil, belirli bir varlık için veri alıp almadığını bilmesine olanak tanır .


2
Cevap için teşekkürler! Tek tek öğelerin yüklenmesi ve durumları nadiren tartışılır!
Gilad Peleg

Başka bir bileşenin eylemine bağlı olan bir bileşene sahip olduğumda, hızlı ve kirli bir çıkış yolu mapStateToProps'unuzda bunları şu şekilde birleştirir: isFetching: posts.isFetching || comments.isFetching - artık biri güncellenirken her iki bileşen için de kullanıcı etkileşimini engelleyebilirsiniz.
Philip Murphy

5

Şimdiye kadar bu soruya rastlamadım ama hiçbir cevap kabul edilmediğinden şapkamı atacağım. Tam da bu iş için bir araç yazdım: react-loader-factory . Abramov'un çözümünden biraz daha fazlası var ama daha modüler ve kullanışlıdır, çünkü yazdıktan sonra düşünmek zorunda kalmak istemedim.

Dört büyük parça var:

  • Fabrika düzeni: Bu, bileşeniniz için hangi durumların "Yükleniyor" anlamına geldiğini ve hangi eylemlerin gönderileceğini ayarlamak için aynı işlevi hızla çağırmanıza olanak tanır. (Bu, bileşenin beklediği eylemleri başlatmaktan sorumlu olduğunu varsayar.)const loaderWrapper = loaderFactory(actionsList, monitoredStates);
  • Sarmalayıcı: Fabrikanın ürettiği bileşen "daha yüksek seviyeli bir bileşendir" ( connect()Redux'ta dönen gibi ), böylece onu mevcut malzemelerinize cıvatalayabilirsiniz.const LoadingChild = loaderWrapper(ChildComponent);
  • Eylem / İndirgeyici etkileşimi: Sarıcı, takılı olduğu bir indirgeyicinin veriye ihtiyaç duyan bileşene geçmemesini söyleyen anahtar sözcükler içerip içermediğini kontrol eder. Sarmalayıcı tarafından gönderilen eylemlerin ilişkili anahtar sözcükleri üretmesi beklenir ( örneğin, redux-api-middleware'in gönderme şekli ACTION_SUCCESSve ACTION_REQUESTörneğin). (Eylemleri başka bir yere gönderebilir ve isterseniz elbette sadece paketleyiciden izleyebilirsiniz.)
  • Throbber: Bileşeninizin bağlı olduğu veriler hazır değilken görünmesini istediğiniz bileşen. Oraya küçük bir div ekledim, böylece onu ayarlamak zorunda kalmadan test edebilirsiniz.

Modülün kendisi redux-api-middleware'den bağımsızdır, ancak onu kullandığım şey budur, işte README'den bazı örnek kodlar:

Yükleyicinin onu saran bir bileşen:

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 } />;
}

Yükleyicinin izlemesi için bir redüktör (ancak isterseniz onu farklı şekilde bağlayabilirsiniz ):

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

Yakın gelecekte modüle zaman aşımı ve hata gibi şeyler ekleyeceğimi umuyorum, ancak model çok farklı olmayacak.


Sorunuzun kısa cevabı:

  1. Oluşturmayı kod oluşturmaya bağlayın - yukarıda gösterdiğim gibi verilerle oluşturmanız gereken bileşenin etrafında bir sarmalayıcı kullanın.
  2. İlgilendiğiniz uygulama etrafındaki isteklerin durumunu kolayca sindirilebilir hale getiren bir azaltıcı ekleyin, böylece neler olduğu hakkında çok fazla düşünmek zorunda kalmazsınız.
  3. Olaylar ve durum gerçekten farklı değil.
  4. Sezgilerinizin geri kalanı bana doğru görünüyor.

4

Yükleme göstergelerinin bir Redux mağazasına ait olmadığını düşünen tek kişi ben miyim? Demek istediğim, bunun bir uygulamanın durumunun bir parçası olduğunu sanmıyorum ..

Şimdi, Angular2 ile çalışıyorum ve yaptığım şey, RxJS BehaviourSubjects aracılığıyla farklı yükleme göstergelerini ortaya çıkaran bir "Loading" servisim var .. Sanırım mekanizma aynı, bilgiyi Redux'da saklamıyorum.

LoadingService kullanıcıları sadece dinlemek istedikleri olaylara abone olurlar ..

Redux eylem yaratıcılarım, bir şeylerin değişmesi gerektiğinde LoadingService'i çağırıyor. UX bileşenleri açığa çıkan gözlemlenebilirlere abone olur ...


bu yüzden tüm eylemlerin sorgulanabildiği (ngrx ve redux-logic), hizmetin işlevsel olmadığı, redux-logic - işlevsel olmadığı mağaza fikrini seviyorum. Güzel okuma
srghma

20
Merhaba, çok yanıldığımı söylemek için bir yıldan fazla bir süre sonra tekrar kontrol ettim. Elbette UX durumu uygulama durumuna aittir. Ne kadar aptal olabilirim?
Spock

3

connect()React Redux veya düşük seviye store.subscribe()yöntemini kullanarak mağazalarınıza değişiklik dinleyicileri ekleyebilirsiniz . Mağazanızda, mağaza değişiklik işleyicisinin bileşen durumunu kontrol edip güncelleyebileceği yükleme göstergesine sahip olmalısınız. Bileşen, duruma bağlı olarak gerekirse ön yükleyiciyi oluşturur.

alertve confirmsorun olmamalı. Engelliyorlar ve uyarı, kullanıcıdan herhangi bir girdi bile almıyor. İle confirm, kullanıcı seçiminin bileşen oluşturmayı etkilemesi gerekiyorsa kullanıcının neye tıkladığına bağlı olarak durumu ayarlayabilirsiniz. Değilse, seçimi daha sonra kullanmak üzere bileşen üye değişkeni olarak saklayabilirsiniz.


uyarı / onay kodu hakkında, nereye koyulmalı, eylemler veya azaltıcılar?
企业 应用 架构 模式 大师

Onlarla ne yapmak istediğinize bağlıdır, ancak dürüst olmak gerekirse, veri katmanının değil, kullanıcı arayüzünün bir parçası oldukları için çoğu durumda bunları bileşen koduna koyardım.
Miloš Rašić

bazı UI bileşenleri, durumun kendisi yerine bir olayı (durum değiştirme olayı) tetikleyerek hareket eder. Ön yükleyiciyi gösteren / gizleyen bir animasyon gibi. Onları nasıl işlersiniz?
企业 应用 架构 模式 大师

React uygulamanızda reaksiyona girmeyen bir bileşen kullanmak istiyorsanız, genel olarak kullanılan çözüm, bir sarmalayıcı tepki bileşeni yapmaktır, ardından tepki vermeyen bileşenin bir örneğini başlatmak, güncellemek ve yok etmek için yaşam döngüsü yöntemlerini kullanın. Bu tür bileşenlerin çoğu, başlatmak için DOM'da yer tutucu öğeleri kullanır ve bunları react bileşeninin oluşturma yönteminde işlersiniz.
Yaşam

Bir vakam var: sağ üst köşede bir bildirim mesajı içeren bir bildirim alanı, her mesaj belirir ve 5 saniye sonra kaybolur. Bu bileşen, ana bilgisayar yerel uygulaması tarafından sağlanan web görünümünün dışındadır. Gibi bazı js arabirimi sağlar addNofication(message). Diğer bir durum, ana yerel uygulama tarafından sağlanan ve javascript API'si tarafından tetiklenen ön yükleyicilerdir. componentDidUpdateBir React bileşeninin içindeki bu api'ler için bir sarmalayıcı ekliyorum . Bu bileşenin sahne veya durumunu nasıl tasarlarım?
企业 应用 架构 模式 大师

3

Uygulamamızda, her biri farklı özellikler olarak tasarlanmış üç tür bildirim bulunmaktadır:

  1. Yükleme göstergesi (pervaneye dayalı modal veya modal olmayan)
  2. Popup Hatası (modal)
  3. Bildirim büfe (kipsiz, kendi kendine kapanan)

Bunların üçü de uygulamamızın en üst düzeyindedir (Ana) ve aşağıdaki kod parçacığında gösterildiği gibi Redux aracılığıyla bağlanmıştır. Bu aksesuarlar, karşılık gelen yönlerinin ekranını kontrol eder.

Tüm API çağrılarımızı işleyen bir proxy tasarladım, bu nedenle tüm isFetching ve (api) hataları proxy'de içe aktardığım actionCreators aracılığı ile sağlanıyor. (Bir kenara, dev için destek hizmetinin bir taklidini enjekte etmek için webpack kullanıyorum, böylece sunucu bağımlılıkları olmadan çalışabiliriz.)

Uygulamada herhangi bir tür bildirim sağlaması gereken diğer herhangi bir yer, uygun eylemi içe aktarır. Snackbar & Error, görüntülenecek mesajlar için parametrelere sahiptir.

@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)
}}

) varsayılan sınıfı dışa aktar Main, React.Component'i genişletir {


Yükleyici / bildirimler göstererek benzer bir kurulum üzerinde çalışıyorum. Sorunlarla karşılaşıyorum; Bu görevleri nasıl gerçekleştireceğinize dair bir özünüz veya örneğiniz var mı?
Aymen

2

Aşağıdaki gibi url'leri kaydediyorum:

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

Ve sonra ezberlenmiş bir seçicim var (yeniden seçim yoluyla).

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

POST durumunda url'yi benzersiz kılmak için bazı değişkenleri sorgu olarak iletiyorum.

Ve bir gösterge göstermek istediğim yerde, sadece getFetchCount değişkenini kullanıyorum


1
Sen yerini alabilir Object.keys(items).filter(item => items[item] === true).length > 0 ? true : falsetarafından Object.keys(items).every(item => items[item])bu arada.
Alexandre Annic

1
Sanırım bunun someyerine demek istediniz every, ama evet, önerilen ilk çözümde çok fazla karşılaştırmaya gerek yoktu. Object.entries(items).some(([url, fetching]) => fetching);
Rafael Porras Lucena
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.