Redux'da eşzamansız akış için neden ara katman yazılımına ihtiyacımız var?


686

Dokümanlara göre, "Ara yazılım olmadan, Redux deposu yalnızca senkronize veri akışını destekler" . Neden böyle olduğunu anlamıyorum. Kapsayıcı bileşeni neden zaman uyumsuz API'yı ve ardından dispatchişlemleri çağıramıyor?

Örneğin, basit bir kullanıcı arayüzü hayal edin: bir alan ve bir düğme. Kullanıcı düğmeye bastığında, alan uzak bir sunucudan alınan verilerle doldurulur.

Bir alan ve bir düğme

import * as React from 'react';
import * as Redux from 'redux';
import { Provider, connect } from 'react-redux';

const ActionTypes = {
    STARTED_UPDATING: 'STARTED_UPDATING',
    UPDATED: 'UPDATED'
};

class AsyncApi {
    static getFieldValue() {
        const promise = new Promise((resolve) => {
            setTimeout(() => {
                resolve(Math.floor(Math.random() * 100));
            }, 1000);
        });
        return promise;
    }
}

class App extends React.Component {
    render() {
        return (
            <div>
                <input value={this.props.field}/>
                <button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button>
                {this.props.isWaiting && <div>Waiting...</div>}
            </div>
        );
    }
}
App.propTypes = {
    dispatch: React.PropTypes.func,
    field: React.PropTypes.any,
    isWaiting: React.PropTypes.bool
};

const reducer = (state = { field: 'No data', isWaiting: false }, action) => {
    switch (action.type) {
        case ActionTypes.STARTED_UPDATING:
            return { ...state, isWaiting: true };
        case ActionTypes.UPDATED:
            return { ...state, isWaiting: false, field: action.payload };
        default:
            return state;
    }
};
const store = Redux.createStore(reducer);
const ConnectedApp = connect(
    (state) => {
        return { ...state };
    },
    (dispatch) => {
        return {
            update: () => {
                dispatch({
                    type: ActionTypes.STARTED_UPDATING
                });
                AsyncApi.getFieldValue()
                    .then(result => dispatch({
                        type: ActionTypes.UPDATED,
                        payload: result
                    }));
            }
        };
    })(App);
export default class extends React.Component {
    render() {
        return <Provider store={store}><ConnectedApp/></Provider>;
    }
}

Dışa aktarılan bileşen oluşturulduğunda, düğmeye tıklayabilirim ve giriş doğru şekilde güncellenir.

Aramadaki updateişlevi not edin connect. Uygulamaya güncellenmekte olduğunu bildiren bir eylem gönderir ve ardından zaman uyumsuz çağrı gerçekleştirir. Çağrı bittikten sonra, sağlanan değer başka bir işlemin yükü olarak gönderilir.

Bu yaklaşımda sorun nedir? Belgelerin önerdiği gibi neden Redux Thunk veya Redux Promise kullanmak istiyorum?

EDIT: Redux repo'yu ipuçları aradım ve Action Creators'ın geçmişte saf işlevler olması gerektiğini buldum. Örneğin, zaman uyumsuz veri akışı için daha iyi bir açıklama sağlamaya çalışan bir kullanıcı:

Eylem oluşturucunun kendisi hala saf bir işlevdir, ancak döndürdüğü thunk işlevinin olması gerekmez ve async çağrılarımızı yapabilir

Aksiyon oluşturucuların artık saf olması gerekmez. Yani, geçmişte kesinlikle thunk / promise ara katman yazılımı kesinlikle gerekliydi, ama artık böyle değil mi?


53
Eylem oluşturucuların hiçbir zaman saf işlevler olması gerekmedi. Dokümanlardaki bir hataydı, değişen bir karar değildi.
Dan Abramov

1
@DanAbramov, test edilebilirlik açısından iyi bir pratik olabilir. Redux-saga buna izin verir: stackoverflow.com/a/34623840/82609
Sebastien Lorber

Yanıtlar:


702

Bu yaklaşımda sorun nedir? Belgelerin önerdiği gibi neden Redux Thunk veya Redux Promise kullanmak istiyorum?

Bu yaklaşımda yanlış bir şey yok. Aynı eylemleri gerçekleştiren farklı bileşenleriniz olacak, bazı eylemleri geri almak veya eylem yaratıcılarına otomatik olarak artan kimlikler gibi bazı yerel durumları vb. eylem oluşturucuları ayrı işlevlere çıkarmak için bakım bakış açısı.

Daha ayrıntılı bir izlenim için “Zaman aşımı ile bir Redux eylemi nasıl gönderilir” cevabımı okuyabilirsiniz .

Redux Thunk veya Redux Promise gibi Katman az önce Thunks ve vaatlerde sevk için “sözdizimi şeker” verir, ancak yok zorunda kullanın.

Yani, herhangi bir ara katman yazılımı olmadan, eylem oluşturucunuz

// action creator
function loadData(dispatch, userId) { // needs to dispatch, so it is first argument
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch
}

Ancak Thunk Middleware ile şöyle yazabilirsiniz:

// action creator
function loadData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`) // Redux Thunk handles these
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  this.props.dispatch(loadData(this.props.userId)); // dispatch like you usually do
}

Yani büyük bir fark yok. İkinci yaklaşımla ilgili sevdiğim bir şey, bileşenin eylem yaratıcısının zaman uyumsuz olmasını umursamamasıdır. Sadece dispatchnormal olarak çağırır , bu mapDispatchToPropstür eylem oluşturucuyu kısa bir sözdizimi ile bağlamak için de kullanılabilir . Bileşenler, eylem oluşturucuların nasıl uygulandığını bilmez ve farklı asenkron yaklaşımlar arasında geçiş yapabilirsiniz (Redux Thunk, Redux Promise, Redux Saga ) bileşenleri değiştirmeden. Öte yandan, önceki, açık yaklaşımla, bileşenleriniz tam olarak belirli bir çağrının eşzamansız olduğunu ve dispatchbazı kurallar tarafından geçirilmesi gerektiğini bilir (örneğin, bir senkronizasyon parametresi olarak).

Ayrıca bu kodun nasıl değişeceğini de düşünün. Diyelim ki ikinci bir veri yükleme fonksiyonuna sahip olmak ve bunları tek bir eylem oluşturucuda birleştirmek istiyoruz.

İlk yaklaşımla, ne tür bir eylem yaratıcısı aradığımızı bilmemiz gerekir:

// action creators
function loadSomeData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(dispatch, userId) {
  return Promise.all(
    loadSomeData(dispatch, userId), // pass dispatch first: it's async
    loadOtherData(dispatch, userId) // pass dispatch first: it's async
  );
}


// component
componentWillMount() {
  loadAllData(this.props.dispatch, this.props.userId); // pass dispatch first
}

Redux Thunk ile aksiyon yaratıcıları dispatchdiğer aksiyon yaratıcılarının sonucu olabilir ve bunların senkron veya asenkron olup olmadığını düşünemez:

// action creators
function loadSomeData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(userId) {
  return dispatch => Promise.all(
    dispatch(loadSomeData(userId)), // just dispatch normally!
    dispatch(loadOtherData(userId)) // just dispatch normally!
  );
}


// component
componentWillMount() {
  this.props.dispatch(loadAllData(this.props.userId)); // just dispatch normally!
}

Bu yaklaşımla, daha sonra eylem içerik oluşturucularınızın mevcut Redux durumuna bakmasını istiyorsanız getState, arama kodunu değiştirmeden thunks'a aktarılan ikinci argümanı kullanabilirsiniz :

function loadSomeData(userId) {
  // Thanks to Redux Thunk I can use getState() here without changing callers
  return (dispatch, getState) => {
    if (getState().data[userId].isLoaded) {
      return Promise.resolve();
    }

    fetch(`http://data.com/${userId}`)
      .then(res => res.json())
      .then(
        data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
        err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
      );
  }
}

Senkronize olması için değiştirmeniz gerekirse, bunu herhangi bir arama kodunu değiştirmeden de yapabilirsiniz:

// I can change it to be a regular action creator without touching callers
function loadSomeData(userId) {
  return {
    type: 'LOAD_SOME_DATA_SUCCESS',
    data: localStorage.getItem('my-data')
  }
}

Bu nedenle, Redux Thunk veya Redux Promise gibi ara katman yazılımlarını kullanmanın yararı, bileşenlerin eylem oluşturucuların nasıl uygulandığının ve Redux durumunu umursadıklarının, eşzamanlı veya eşzamansız olup olmadıklarının ve diğer eylem yaratıcılarını çağırıp çağırmadıklarının farkında olmamasıdır. . Dezavantajı biraz dolaylı, ancak gerçek uygulamalarda buna değer olduğuna inanıyoruz.

Son olarak, Redux Thunk ve arkadaşları Redux uygulamalarındaki asenkron isteklere olası bir yaklaşımdır. Bir başka ilginç yaklaşım, eylemleri geldikçe alan uzun eylemli cinleri (“sagas”) tanımlamanızı ve eylemler çıkmadan önce istekleri dönüştürmenizi veya gerçekleştirmenizi sağlayan Redux Saga'dır. Bu, mantığı aksiyon yaratıcılarından sagasa taşır. Kontrol etmek ve daha sonra size en uygun olanı seçmek isteyebilirsiniz.

Redux repo'yu ipuçları için araştırdım ve Action Creators'ın geçmişte saf işlevler olması gerektiğini buldum.

Bu yanlış. Dokümanlar bunu söyledi, ancak dokümanlar yanlıştı.
Eylem oluşturucuların hiçbir zaman saf işlevler olması gerekmedi.
Belgeleri bunu yansıtacak şekilde düzelttik.


57
Belki Dan'ın düşüncesini söylemenin kısa yolu: ara katman yazılımı merkezi bir yaklaşımdır, bu şekilde bileşenlerinizi daha basit ve genel tutmanıza ve veri akışını tek bir yerde kontrol etmenize olanak tanır. Eğer büyük uygulamayı sürdürürseniz keyfine varacaksınız =)
Sergey Lapin

3
@asdfasdfads Neden işe yaramadığını anlamıyorum. Aynı şekilde çalışır; harekete alertgeçtikten sonra koymak dispatch().
Dan Abramov

9
Senin ilk kod örneğinde sondan bir önceki satır: loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch. Neden gönderilmem gerekiyor? Eğer konvansiyona göre sadece tek bir küresel mağaza varsa, neden sadece doğrudan referans yapmıyorum ve ne store.dispatchzaman ihtiyacım olursa yapmıyorum loadData?
Søren Debois

10
@ SørenDebois Uygulamanız istemci tarafındaysa yalnızca bu işe yarar. Sunucuda oluşturulmuşsa, storedaha önce tanımlayamamanız için her istek için farklı bir örneğe sahip olmak istersiniz .
Dan Abramov

3
Bu cevabın, 14 satırdan oluşan redux-thunk kaynak kodundan 9.92 kat daha fazla 139 satırı olduğunu belirtmek isteriz
Guy

447

Yapmazsın.

Ama ... redux-saga kullanmalısın :)

Dan Abramov'un cevabı doğru redux-thunkama redux-saga hakkında biraz daha konuşacağım benzer ama daha güçlü .

Zorunlu VS beyanı

  • DOM : jQuery zorunlu / Tepki bildiriyor
  • monads : IO zorunlu / Ücretsiz beyan edici
  • Redux etkileri : redux-thunkzorunludur / redux-sagabeyan edicidir

Bir IO monad veya bir söz gibi ellerinizde bir thunk olduğunda, yürüttükten sonra ne yapacağını kolayca bilemezsiniz. Bir gövdeyi test etmenin tek yolu, onu çalıştırmak ve dağıtıcıyı (veya daha fazla şeyle etkileşime girerse tüm dış dünyayı) alay etmektir.

Alaycı kullanıyorsanız, fonksiyonel programlama yapmıyorsunuzdur.

Yan etkiler merceğinden görülen alaylar, kodunuzun saf olmadığı bir işarettir ve fonksiyonel programcının gözünde bir şeyin yanlış olduğunu kanıtlar. Buzdağının sağlam olduğunu kontrol etmemize yardımcı olacak bir kütüphane indirmek yerine, etrafına yelken açmalıyız. Sert bir TDD / Java adamı bana bir keresinde Clojure'da alay etme şeklini sordu. Cevap, genellikle bilmiyoruz. Genellikle kodumuzu yeniden düzenlememiz gereken bir işaret olarak görürüz.

Kaynak

Sagalar (uygulandıkları gibi) redux-saga ) bildirimlidir ve Free monad veya React bileşenleri gibi, herhangi bir alay olmadan test etmek çok daha kolaydır.

Ayrıca bu makaleye bakın :

modern FP'de, programlar yazmamalıyız - programların tanımlarını yazmalıyız.

(Aslında, Redux-saga bir melez gibidir: akış zorunludur, ancak etkileri açıklayıcıdır)

Karışıklık: eylemler / olaylar / komutlar ...

Ön uç dünyasında, CQRS / EventSourcing ve Flux / Redux gibi bazı arka uç kavramlarının nasıl ilişkili olabileceği konusunda çok fazla karışıklık var, çünkü çoğunlukla Flux'ta bazen hem zorunlu kodu ( LOAD_USER) hem de olayları () ve olayları (USER_LOADED ). Olay satın alma gibi, yalnızca olayları göndermeniz gerektiğine inanıyorum.

Pratikte destan kullanma

Kullanıcı profiline bağlantı içeren bir uygulama düşünün. Bunu her ara katman yazılımı ile ele almanın deyimsel yolu:

redux-thunk

<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>

function loadUserProfile(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
      err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
    );
}

redux-saga

<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>


function* loadUserProfileOnNameClick() {
  yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}

function* fetchUser(action) {
  try {
    const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
    yield put({ type: 'USER_PROFILE_LOADED', userProfile })
  } 
  catch(err) {
    yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
  }
}

Bu destan şu anlama gelir:

bir kullanıcı adı her tıklandığında, kullanıcı profilini getirir ve yüklenen profille bir olay gönderir.

Gördüğünüz gibi, bazı avantajları var redux-saga.

takeLatestYalnızca son kullanıcı adının verilerini tıklatmakla ilgilendiğinizi ifade etmek için izinlerin kullanılması ( kullanıcının çok sayıda kullanıcı adına çok hızlı tıklaması durumunda eşzamanlılık sorunlarını ele alın). Bu tür şeyler thunks ile zordur. KullanabilirdintakeEvery bu davranışı istemiyorsanız.

Aksiyon oluşturucuları saf tutarsınız. ActionCreators'ı (sagas putve bileşenlerde dispatch) tutmanın hala yararlı olduğunu unutmayın, çünkü ileride eylem doğrulaması (onaylar / akış / daktilo) eklemenize yardımcı olabilir.

Efektler açıklandığı için kodunuz çok daha test edilebilir hale geliyor

Artık rpc benzeri çağrıları tetiklemek zorunda değilsiniz actions.loadUser(). Kullanıcı arayüzünüzün OLAN şeyleri göndermesi yeterlidir. Artık olayları değil (her zaman geçmiş zamanda!) Ateş ediyoruz . Bu, ayrıştırılmış "ördekler" veya Sınırlı Bağlamlar oluşturabileceğiniz ve destanın bu modüler bileşenler arasındaki bağlantı noktası olarak işlev görebileceği anlamına gelir .

Bu, görünümlerinizin yönetilmesi daha kolay olduğu anlamına gelir çünkü artık olanlar ile sonuç olarak ne olması gerektiği arasında bir çeviri katmanı içermelerine gerek yoktur.

Örneğin sonsuz bir kaydırma görünümü hayal edin. CONTAINER_SCROLLEDyol açabilir NEXT_PAGE_LOADED, ama gerçekten biz başka bir sayfa yüklemek gerekip gerekmediğini karar vermek kaydırılabilir konteyner sorumluluğundadır? Daha sonra, son sayfanın başarılı bir şekilde yüklenip yüklenmediği veya yüklenmeye çalışan bir sayfa olup olmadığı veya yüklenecek başka bir öğe olup olmadığı gibi daha karmaşık şeylerin farkında olmalı? Ben öyle düşünmüyorum: maksimum yeniden kullanılabilirlik için kaydırılabilir kap sadece kaydırıldığını açıklamalıdır. Bir sayfanın yüklenmesi o kaydırmanın "iş etkisi" dir

Bazıları, jeneratörlerin doğası gereği yerel değişkenlerle redux mağazasının dışındaki durumu gizleyebileceğini iddia edebilir, ancak zamanlayıcılar vb. Ve selectşimdi Redux mağazanızdan bir durum almanıza izin veren bir efekt var.

Sagas zaman yolculuğu yapabilir ve aynı zamanda üzerinde çalışmakta olan karmaşık akış günlüğü ve geliştirme araçlarına da olanak tanır. Zaten uygulanmış olan bazı basit zaman uyumsuz akış günlüğü:

destan akış günlüğü

Ayrışma

Sagalar sadece redux gövdelerinin yerini almaz. Arka uç / dağıtılmış sistemlerden / olay kaynaklarından gelirler.

Sagas'ın redux gövdelerinizi daha iyi test edilebilirlikle değiştirmek için burada olduğu çok yaygın bir yanlış anlamadır. Aslında bu sadece redux-saga'nın bir uygulama detayı. Beyan edilebilir etkiler kullanmak test edilebilirlik için gövdeden daha iyidir, ancak destan modeli zorunlu veya bildirici kodun üstüne uygulanabilir.

İlk olarak, destan, uzun süren işlemleri (nihai tutarlılık) ve farklı sınırlı bağlamlardaki işlemleri (etki alanına dayalı tasarım jargonu) koordine etmeyi sağlayan bir yazılım parçasıdır.

Bunu ön uç dünyası için basitleştirmek için widget1 ve widget2 olduğunu hayal edin. Widget1 üzerindeki bazı düğmeler tıklatıldığında, widget2 üzerinde bir etkisi olmalıdır. 2 widget'ı birbirine bağlamak yerine (yani widget1, widget2'yi hedefleyen bir eylem gönderir), widget1 yalnızca düğmesinin tıklatıldığını gönderir. Ardından destan bu düğmeyi dinleyin ve widget2'nin farkında olduğu yeni bir olay göndererek widget2'yi güncelleyin.

Bu, basit uygulamalar için gerekli olmayan bir dolaylı yükleme düzeyi ekler, ancak karmaşık uygulamaları ölçeklendirmeyi daha kolay hale getirir. Artık widget1 ve widget2'yi farklı npm depolarına yayınlayabilirsiniz, böylece genel eylem kayıtlarını paylaşmalarına gerek kalmadan birbirlerini asla bilmek zorunda kalmazlar. 2 widget artık ayrı yaşayabilen sınırlı bağlamlardır. Birbirlerinin tutarlı olması gerekmez ve diğer uygulamalarda da tekrar kullanılabilirler. Destan, iki widget arasındaki bunları, işletmeniz için anlamlı bir şekilde koordine eden bağlantı noktasıdır.

Redux uygulamanızı nasıl yapılandıracağınızla ilgili, Redux-saga'yı ayırma nedenleriyle kullanabileceğiniz bazı güzel makaleler:

Somut bir kullanıcı tabanı: bildirim sistemi

Bileşenlerimin uygulama içi bildirimlerin görüntülenmesini tetikleyebilmesini istiyorum. Ancak, bileşenlerimin kendi iş kurallarına sahip bildirim sistemine (aynı anda görüntülenen maksimum 3 bildirim, bildirim kuyruğu, 4 saniye görüntüleme süresi vb.) Yüksek düzeyde birleşmesini istemiyorum.

JSX bileşenlerimin bir bildirimin ne zaman gösterileceğini / gizleneceğine karar vermesini istemiyorum. Sadece bir bildirim isteme ve karmaşık kuralları destanın içinde bırakma yeteneği veriyorum. Bu tür şeylerin thunks veya vaatlerle uygulanması oldukça zordur.

bildirimler

Burada bunun destan ile nasıl yapılabileceğini anlattım

Neden destan deniyor?

Destan terimi arka uç dünyadan geliyor. Başlangıçta Yassine'yi (Redux-saga'nın yazarı) uzun bir tartışmada tanıttım .

Başlangıçta, bu terim bir kâğıt ile tanıtıldı , destan modelinin dağıtılmış işlemlerde nihai tutarlılığı işlemek için kullanılması gerekiyordu, ancak kullanımı artık arka plan geliştiricileri tarafından daha geniş bir tanımlamaya genişletildi, böylece artık "süreç yöneticisi" (bir şekilde orijinal destan modeli, işlem yöneticisinin özel bir şeklidir).

Bugün "destan" terimi 2 farklı şeyi tanımlayabileceğinden kafa karıştırıcı. Redux-saga'da kullanıldığından, dağıtılmış işlemleri ele almanın bir yolunu değil, uygulamanızdaki eylemleri koordine etmenin bir yolunu tanımlar. redux-sagade denilebilirdi redux-process-manager.

Ayrıca bakınız:

Alternatifler

Jeneratör kullanma fikrinden hoşlanmıyorsanız ancak destan deseni ve ayırma özellikleri ile ilgileniyorsanız, aynısını aynı modeli tanımlamak için adı kullanan redux-gözlemlenebilirepic , ancak RxJS ile de elde edebilirsiniz. Rx'i zaten biliyorsanız, kendinizi evinizde gibi hissedeceksiniz.

const loadUserProfileOnNameClickEpic = action$ =>
  action$.ofType('USER_NAME_CLICKED')
    .switchMap(action =>
      Observable.ajax(`http://data.com/${action.payload.userId}`)
        .map(userProfile => ({
          type: 'USER_PROFILE_LOADED',
          userProfile
        }))
        .catch(err => Observable.of({
          type: 'USER_PROFILE_LOAD_FAILED',
          err
        }))
    );

Bazı redux-saga yararlı kaynakları

2017 tavsiyeleri

  • Redux-saga'yı sadece kullanmak uğruna aşırı kullanmayın. Yalnızca test edilebilir API çağrıları buna değmez.
  • En basit durumlar için gövdelerinizi projenizden çıkarmayın.
  • yield put(someActionThunk)Mantıklıysa, gövdelerinizi göndermekten çekinmeyin .

Redux-saga (veya Redux-gözlemlenebilir) kullanmaktan korkuyorsanız, ancak ayırma düzenine ihtiyacınız varsa, redux-dispatch-abone olup olmadığını kontrol edin : gönderileri dinlemeye ve dinleyicide yeni gönderileri tetiklemeye izin verir.

const unsubscribe = store.addDispatchListener(action => {
  if (action.type === 'ping') {
    store.dispatch({ type: 'pong' });
  }
});

64
Her tekrar ziyaret ettiğimde bu daha iyi oluyor. Bir blog gönderisine dönüştürmeyi düşünün :).
RainerAtSpirit

4
İyi bir yazı için teşekkürler. Ancak bazı yönleri kabul etmiyorum. LOAD_USER nasıl zorunludur? Bana göre, sadece beyan edici değil, aynı zamanda okunabilir harika bir kod da veriyor. Örneğin. "Bu düğmeye bastığımda ADD_ITEM istiyorum". Koda bakıp neler olduğunu tam olarak anlayabilirim. Bunun yerine "BUTTON_CLICK" etkisi için bir şey çağrılmış olsaydı, bunu aramam gerekirdi.
16:48

4
Güzel cevap. Şimdi başka bir alternatif var: github.com/blesh/redux-observable
swennemen

4
@swelet geç cevap verdiğim için üzgünüm. Gönderdiğinizde ADD_ITEM, bu zorunludur, çünkü mağazanız üzerinde bir etki yaratmayı amaçlayan bir eylem gönderirsiniz: eylemin bir şey yapmasını beklersiniz. Açıklayıcı olmak, olay kaynağı oluşturma felsefesini kucaklar: uygulamalarınızdaki değişiklikleri tetiklemek için eylemler göndermezsiniz, ancak uygulamanıza ne olduğunu açıklamak için geçmiş olayları gönderirsiniz. Bir olayın gönderilmesi, başvurunun durumunun değiştiğini değerlendirmek için yeterli olmalıdır. Etkinliğe tepki veren bir Redux mağazası olması isteğe bağlı bir uygulama detayıdır
Sebastien Lorber

3
Bu yanıtı sevmiyorum çünkü kendi kütüphanesini pazarlamak için asıl sorudan uzaklaşıyor. Bu cevap, sorunun amacı olmayan iki kütüphanenin karşılaştırmasını sağlar. Asıl soru, kabul edilen cevapla açıklanan ara katman yazılımı kullanıp kullanmayacağınızı sormaktır.
Abhinav Manchanda

31

Kısa cevap : Asenkron sorununa benim için tamamen makul bir yaklaşım gibi görünüyor. Birkaç uyarı ile.

İşime yeni başladığımız yeni bir proje üzerinde çalışırken benzer bir düşünce tarzım vardı. Vanilla Redux'un mağazayı güncellemek ve bileşenleri React bileşen ağacının bağırsaklarından uzak kalacak şekilde yeniden sunmak için zarif sisteminin büyük bir hayranıydım. dispatchEşzamansızlığı ele almak için bu zarif mekanizmaya bağlanmak bana garip geldi .

Projemizden çıkardığım bir tepkiyi redt-redux-kontrolörü olarak adlandırdığımız bir kütüphanede gerçekten benzer bir yaklaşımla bitirdim .

Birkaç nedenden ötürü yukarıdaki yaklaşımla devam etmemeye başladım:

  1. Yazma şekliniz, bu gönderme işlevlerinin mağazaya erişimi yoktur. UI bileşenlerinizin, dağıtım işlevinin ihtiyaç duyduğu tüm bilgileri geçirmesini sağlayarak biraz etrafta dolaşabilirsiniz. Ama bunun bu kullanıcı arayüzü bileşenlerini gereksiz yere gönderme mantığına bağladığını iddia ediyorum. Ve daha problemli olarak, gönderme işlevinin zaman uyumsuz devamlarda güncellenmiş duruma erişmesinin açık bir yolu yoktur.
  2. Dağıtım işlevleri dispatchsözcüksel kapsam aracılığıyla kendine erişebilir. Bu, bu connectifade elden çıktığında yeniden düzenleme seçeneklerini sınırlar - ve sadece bu updateyöntemle oldukça hantal görünüyor . Bu nedenle, bu dağıtıcı işlevlerini ayrı modüllere böldüğünüzde oluşturmanıza izin vermek için bir sisteme ihtiyacınız vardır.

Birlikte alın dispatch, olayın parametreleri ile birlikte, sevkiyat işlevlerinize depolanmasına izin vermek için bazı sistemi donatmanız gerekir. Bu bağımlılık enjeksiyonuna üç makul yaklaşım biliyorum:

  • redux-thunk , bunları gövdelerinize geçirerek (kubbe tanımlarıyla tam olarak thunks yapmalarını değil) fonksiyonel bir şekilde yapar. Diğer dispatchara katman yazılımı yaklaşımları ile çalışmadım , ama temelde aynı olduklarını varsayıyorum.
  • reat-redux-controller bunu bir koroutin ile yapar. Bir bonus olarak, connectdoğrudan ham, normalleştirilmiş mağaza ile çalışmak yerine , ilk argüman olarak geçmiş olabileceğiniz işlevler olan "seçicilere" erişmenizi sağlar .
  • Nesneye yönelik bir şekilde, onları thisçeşitli olası mekanizmalar aracılığıyla bağlam içine enjekte ederek de yapabilirsiniz .

Güncelleme

Bana göre bu muammanın bir kısmı tepki redux'un bir sınırlamasıdır . Durum connectanlık görüntüsü almak için ilk argüman gönderilir, ancak gönderilmez. İkinci argüman gönderilir ancak devlet almaz. Her iki argüman da, devam / geri çağırma sırasında güncellenmiş durumu görebilmeleri nedeniyle, mevcut durumu kapatan bir thunk elde etmez.


22

Abramov'un hedefi - ve herkesin ideali - basitçe en uygun olan yerde karmaşıklığı (ve asenkron çağrıları) kapsüllemektir .

Bunu standart Redux veri akışında yapabileceğiniz en iyi yer neresi? Nasıl olur:

  • Redüktörler ? Olmaz. Yan etkileri olmayan saf fonksiyonlar olmalıdırlar. Mağazayı güncellemek ciddi, karmaşık bir iştir. Kontamine etmeyin.
  • Aptal Görünüm Bileşenleri? Kesinlikle hayır. Tek bir endişeleri var: sunum ve kullanıcı etkileşimi ve mümkün olduğunca basit olmalıdır.
  • Konteyner Bileşenleri? Mümkün, ancak en uygun olmayan. Konteynerin, görüşle ilgili bazı karmaşıklıkları kapsadığımız ve mağaza ile etkileşime girdiğimiz bir yer olması mantıklıdır, ancak:
    • Kapların aptal bileşenlerden daha karmaşık olması gerekir, ancak yine de tek bir sorumluluktur: görüş ve durum / mağaza arasında bağlar sağlamak. Zaman uyumsuz mantığınız bundan tamamen ayrı bir kaygıdır.
    • Bir kaba yerleştirerek, eşzamansız mantığınızı tek bir görünüm / rota için tek bir bağlama kilitlersiniz. Kötü bir fikir. İdeal olarak hepsi tekrar kullanılabilir ve tamamen ayrıştırılmıştır.
  • S ome diğer Servisi Modülü? Kötü fikir: mağazaya erişimi enjekte etmeniz gerekir, bu da bir sürdürülebilirlik / test edilebilirlik kabusu. Redux'un tahılıyla gitmek ve sadece sağlanan API'leri / modelleri kullanarak mağazaya erişmek daha iyidir.
  • Eylemleri ve onları yorumlayan Middlewares? Neden olmasın?! Yeni başlayanlar için, geride bıraktığımız tek büyük seçenek bu. :-) Daha mantıklı olarak, eylem sistemi her yerden kullanabileceğiniz ayrıştırılmış yürütme mantığıdır. Mağazaya erişebilir ve daha fazla eylem gönderebilir. Uygulama çevresindeki kontrol ve veri akışını organize etmek gibi tek bir sorumluluğu vardır ve çoğu zaman uyumsuzluk buna tam olarak uyar.
    • Eylem Yaratıcıları ne olacak? Neden sadece eylemlerin kendisinde ve Middleware'de değil, orada asenkron yapmıyorsunuz?
      • İlk ve en önemlisi, içerik oluşturucuların ara katman yazılımı gibi mağazaya erişimi yok. Bu, yeni koşullu eylemler gönderemeyeceğiniz, zaman uyumsuzluğunuzu oluşturmak için mağazadan okuyamayacağınız vb. Anlamına gelir.
      • Bu nedenle, karmaşıklığı gereklilik kompleksi olan bir yerde tutun ve diğer her şeyi basit tutun. Yaratıcılar daha sonra test edilmesi kolay basit, nispeten saf fonksiyonlar olabilir.

Konteyner Bileşenleri - neden olmasın? React'te rol oynayan rol bileşenleri nedeniyle, bir kap hizmet sınıfı olarak hareket edebilir ve zaten DI (sahne) aracılığıyla bir mağaza alır. Bir kaba yerleştirerek, eşzamansız mantığınızı tek bir bağlamda, tek bir görünüm / rota için kilitliyorsunuz - nasıl? Bir bileşenin birden çok örneği olabilir. Sunumdan ayrılabilir, örneğin render prop ile. Sanırım cevap, konuyu kanıtlayan kısa örneklerden daha fazla yararlanabilir.
Estus Flask

Bu cevabı seviyorum!
Mauricio Avendaño

13

Başlangıçta sorulan soruyu cevaplamak için:

Kapsayıcı bileşeni neden zaman uyumsuz API'yı çağıramıyor ve ardından işlemleri gönderemiyor?

Bu belgelerin Redux artı React için değil, Redux için olduğunu unutmayın. React bileşenlerine bağlanan Redux mağazaları tam olarak söylediklerinizi yapabilir, ancak ara katmanı olmayan bir Plain Jane Redux mağazası, dispatchdüz ol 'nesneleri dışında argümanları kabul etmez .

Ara katman yazılımı olmadan elbette

const store = createStore(reducer);
MyAPI.doThing().then(resp => store.dispatch(...));

Ama asenkroni sarılır benzer bir durum etrafında Redux ziyade ele tarafından Redux. Böylece, ara katman yazılımı, doğrudan neye aktarılabileceğini değiştirerek eşzamansızlığa izin verir dispatch.


Bununla birlikte, önerilerinizin ruhu bence geçerli. Redux + React uygulamasında eşzamansızlığı işlemenin başka yolları da vardır.

Ara katman yazılımını kullanmanın bir yararı, tam olarak nasıl bağlandıklarından endişe etmeden eylem oluşturucularını normal olarak kullanmaya devam edebilmenizdir. Örneğin, redux-thunkyazdığınız kod çok benzer

function updateThing() {
  return dispatch => {
    dispatch({
      type: ActionTypes.STARTED_UPDATING
    });
    AsyncApi.getFieldValue()
      .then(result => dispatch({
        type: ActionTypes.UPDATED,
        payload: result
      }));
  }
}

const ConnectedApp = connect(
  (state) => { ...state },
  { update: updateThing }
)(App);

orijinalinden farklı görünmüyor - sadece biraz karıştırılıyor - ve eşzamansız olduğunu (veya olması gerektiğini) connectbilmiyor updateThing.

Ayrıca destek isteseydi vaatler , gözlenebilirlerin , Saga veya deli özel ve son derece bildirim eylem yaratıcıları, sonra Redux sadece geçmek neyi değiştirerek bunu yapabilirsiniz dispatch(aka sen eylem yaratıcıları gelen ne dönüş). React bileşenleri (veya connectçağrıları) ile mucking gerekmez.


İşlemin tamamlanmasıyla ilgili başka bir etkinlik daha göndermenizi öneririz. İşlem tamamlandıktan sonra bir uyarı () göstermeniz gerektiğinde bu çalışmaz. React bileşenlerinin içindeki vaatler işe yarıyor. Şu anda Promises yaklaşımını öneriyorum.
catamphetamine

8

Tamam, önce ara katman yazılımının nasıl çalıştığını görmeye başlayalım, bu soruya oldukça cevap veriyor , bu Redux'daki pplyMiddleWare işlevinin kaynak kodudur :

function applyMiddleware() {
  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  return function (createStore) {
    return function (reducer, preloadedState, enhancer) {
      var store = createStore(reducer, preloadedState, enhancer);
      var _dispatch = store.dispatch;
      var chain = [];

      var middlewareAPI = {
        getState: store.getState,
        dispatch: function dispatch(action) {
          return _dispatch(action);
        }
      };
      chain = middlewares.map(function (middleware) {
        return middleware(middlewareAPI);
      });
      _dispatch = compose.apply(undefined, chain)(store.dispatch);

      return _extends({}, store, {
        dispatch: _dispatch
      });
    };
  };
}

Bu kısma bakın, sevkıyatımızın nasıl bir fonksiyon haline geldiğini görün .

  ...
  getState: store.getState,
  dispatch: function dispatch(action) {
  return _dispatch(action);
}
  • Her ara katman yazılımına dispatchve getStateişlevlerinin adlandırılmış argümanlar olarak verileceğini unutmayın .

Tamam, Redux için en çok kullanılan orta yollardan biri olarak Redux-thunk kendini tanıtıyor:

Redux Thunk ara katman yazılımı, bir eylem yerine bir işlev döndüren eylem yaratıcıları yazmanıza olanak tanır. Thunk, bir eylemin gönderilmesini geciktirmek veya yalnızca belirli bir koşul karşılandığında gönderilmek için kullanılabilir. İç işlev, depatch metodları dispatch ve getState parametrelerini alır.

Gördüğünüz gibi, bir eylem yerine bir işlev döndürecek, bir işlev olarak istediğiniz zaman bekleyip arayabileceğiniz anlamına gelir ...

Öyleyse ne saçmalık? Wikipedia'da bu şekilde tanıtıldı:

Bilgisayar programlamasında, bir thunk başka bir alt rutine ek bir hesaplama enjekte etmek için kullanılan bir alt rutindir. Thunks öncelikle bir hesaplamayı gerekene kadar geciktirmek veya diğer alt rutinin başına veya sonuna işlem eklemek için kullanılır. Derleyici kod üretimi ve modüler programlama için çeşitli başka uygulamaları vardır.

Terim, "düşün" ün bir joküler türevi olarak ortaya çıkmıştır.

Thunk, değerlendirmesini geciktirmek için bir ifadeyi saran bir işlevdir.

//calculation of 1 + 2 is immediate 
//x === 3 
let x = 1 + 2;

//calculation of 1 + 2 is delayed 
//foo can be called later to perform the calculation 
//foo is a thunk! 
let foo = () => 1 + 2;

Konseptin ne kadar kolay olduğunu ve zaman uyumsuz eylemlerinizi yönetmenize nasıl yardımcı olabileceğini görün ...

Bu onsuz yaşayabileceğiniz bir şeydir, ancak programlamada her zaman daha iyi, daha düzenli ve işleri yapmanın uygun yolları olduğunu unutmayın ...

Ara katman yazılımı Redux uygulayın


1
İlk kez SO, bir şey okumadım. Ama sadece resmi bakan yazı sevdim. Şaşırtıcı, ipucu ve hatırlatma.
Bhojendra Rauniyar

2

Redux-saga kullanmak React-redux uygulamasında en iyi ara katman yazılımıdır.

Örn: store.js

  import createSagaMiddleware from 'redux-saga';
  import { createStore, applyMiddleware } from 'redux';
  import allReducer from '../reducer/allReducer';
  import rootSaga from '../saga';

  const sagaMiddleware = createSagaMiddleware();
  const store = createStore(
     allReducer,
     applyMiddleware(sagaMiddleware)
   )

   sagaMiddleware.run(rootSaga);

 export default store;

Ve sonra saga.js

import {takeLatest,delay} from 'redux-saga';
import {call, put, take, select} from 'redux-saga/effects';
import { push } from 'react-router-redux';
import data from './data.json';

export function* updateLesson(){
   try{
       yield put({type:'INITIAL_DATA',payload:data}) // initial data from json
       yield* takeLatest('UPDATE_DETAIL',updateDetail) // listen to your action.js 
   }
   catch(e){
      console.log("error",e)
     }
  }

export function* updateDetail(action) {
  try{
       //To write store update details
   }  
    catch(e){
       console.log("error",e)
    } 
 }

export default function* rootSaga(){
    yield [
        updateLesson()
       ]
    }

Ve sonra action.js

 export default function updateFruit(props,fruit) {
    return (
       {
         type:"UPDATE_DETAIL",
         payload:fruit,
         props:props
       }
     )
  }

Ve sonra reducer.js

import {combineReducers} from 'redux';

const fetchInitialData = (state=[],action) => {
    switch(action.type){
      case "INITIAL_DATA":
          return ({type:action.type, payload:action.payload});
          break;
      }
     return state;
  }
 const updateDetailsData = (state=[],action) => {
    switch(action.type){
      case "INITIAL_DATA":
          return ({type:action.type, payload:action.payload});
          break;
      }
     return state;
  }
const allReducers =combineReducers({
   data:fetchInitialData,
   updateDetailsData
 })
export default allReducers; 

Ve sonra main.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './app/components/App.jsx';
import {Provider} from 'react-redux';
import store from './app/store';
import createRoutes from './app/routes';

const initialState = {};
const store = configureStore(initialState, browserHistory);

ReactDOM.render(
       <Provider store={store}>
          <App />  /*is your Component*/
       </Provider>, 
document.getElementById('app'));

bunu dene .. çalışıyor


3
Bu, bir varlığı veya varlık listesini döndürmek için API uç noktasını çağırmak isteyen biri için ciddi bir şeydir. "Sadece bunu yap ... sonra bu, sonra bu, sonra bu başka şey, sonra bu, sonra bu diğer şeyler, sonra devam et, sonra yap ..". Ama dostum, bu ÖNCE, bize ön uçta kullanılmaya hazır veriler vermek için GERİ ARAYI çağırmamız gerekiyor. Bu yol ise, bir şeyler yanlış, bir şeyler gerçekten yanlış ve birisi bugün KISS
uygulamıyor

Merhaba, API Çağrıları için try ve catch bloğunu kullanın. API yanıt verdikten sonra, Redüktör eylem türlerini çağırın.
SM Chinna

1
@zameb Haklı olabilirsiniz, ancak o zaman şikayetiniz Redux'un kendisiyle ve karmaşıklığı azaltmaya çalışırken getirdiği tüm kulak misafiri ile ilgilidir.
jorisw

1

Senkron eylem yaratıcıları var ve ardından asenkron eylem yaratıcıları var.

Eşzamanlı bir eylem yaratıcısı, onu çağırdığımızda, o nesneye bağlı tüm ilgili verileri derhal bir Action nesnesi döndürür ve redüktörlerimiz tarafından işlenmeye hazırdır.

Eşzamansız eylem oluşturucular, bir eylemi göndermeye hazır hale gelmeden önce biraz zaman gerektireceği bir içeriktir.

Tanım olarak, bir ağ isteği yapan bir eylem oluşturucunuz olduğunda, her zaman bir zaman uyumsuz eylem oluşturucu olarak nitelendirilir.

Bir Redux uygulamasının içinde eşzamansız eylem oluşturucularına sahip olmak istiyorsanız, bu eşzamansız eylem yaratıcılarıyla başa çıkmanıza izin verecek bir ara katman yazılımı adı verilen bir şey yüklemeniz gerekir.

Bunu, zaman uyumsuz eylemler için özel ara katman yazılımı kullanmamızı söyleyen hata iletisinde doğrulayabilirsiniz.

Peki bir ara katman yazılımı nedir ve neden Redux'daki asenkron akış için buna ihtiyacımız var?

Redux-thunk gibi redux ara katman yazılımı bağlamında, bir ara katman yazılımı, Redux'un kutudan çıkamayacağı bir şey olduğu için asenkron eylem yaratıcılarıyla başa çıkmamıza yardımcı olur.

Redux döngüsüne entegre edilmiş bir ara katman yazılımı ile, hala eylem oluşturucuları çağırıyoruz, bu gönderilecek bir eylem döndürecek, ancak şimdi bir eylemi gönderdiğimizde, doğrudan tüm redüktörlerimize göndermek yerine, bir eylemin uygulama içindeki tüm farklı ara katman yazılımları aracılığıyla gönderileceğini söylemek.

Tek bir Redux uygulamasının içinde, istediğimiz kadar çok veya az ara katman yazılımına sahip olabiliriz. Çoğunlukla üzerinde çalıştığımız projelerde, Redux mağazamıza bağlanan bir veya iki ara katmana sahip olacağız.

Ara katman yazılımı, gönderdiğimiz her eylemle çağrılacak düz bir JavaScript işlevidir. Bu fonksiyonun içinde bir ara katman yazılımı, bir eylemin redüktörlerden herhangi birine gönderilmesini durdurma şansına sahiptir, bir eylemi değiştirebilir veya herhangi bir şekilde bir eylemle uğraşabilir; örneğin, günlükleri konsollayan bir ara katman yazılımı oluşturabiliriz görüntüleme keyfiniz için gönderdiğiniz her eylem.

Projenize bağımlılık olarak yükleyebileceğiniz çok sayıda açık kaynaklı ara katman yazılımı var.

Sadece açık kaynaklı ara katman yazılımını kullanmak veya bağımlılık olarak kurmakla sınırlı değilsiniz. Kendi özel ara katman yazılımınızı yazabilir ve Redux mağazanızın içinde kullanabilirsiniz.

Ara katman yazılımlarının daha popüler kullanımlarından biri (ve yanıtınıza ulaşmak), zaman uyumsuz eylem yaratıcıları ile uğraşmaktır, muhtemelen en popüler ara yazılım redux-thunk'tır ve asenkron eylem yaratıcılarıyla başa çıkmanıza yardımcı olmaktır.

Eşzamansız eylem oluşturucularla uğraşmanıza yardımcı olan diğer birçok ara katman yazılımı da vardır.


1

Soruyu cevaplamak için:

Kapsayıcı bileşeni neden zaman uyumsuz API'yı çağıramıyor ve ardından işlemleri gönderemiyor?

En az iki nedenden dolayı söyleyebilirim:

Birinci nedeni bunun iş değil, endişeleri ayrılması olduğunu action creatoraramak apive veri geri almak, sen senin iki argüman geçmek zorunda zorunda action creator function, action typeve bir payload.

İkinci neden, redux storezorunlu eylem türüne ve isteğe bağlı olarak a'ya sahip düz bir nesne beklemesidir payload(ancak burada da yükü geçmeniz gerekir).

Eylem oluşturucu aşağıdaki gibi düz bir nesne olmalıdır:

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

Ve işi Redux-Thunk midlewareiçin dispache, aramalarınızdan sonucu api calluygun için action.


0

Bir kurumsal projede çalışırken, orta eşyada basit asenkron akışta mevcut olmayan (destan) gibi birçok gereksinim vardır, aşağıda bazıları şunlardır:

  • Paralel çalışma isteği
  • Beklemeye gerek kalmadan gelecekteki eylemleri çekme
  • Engellemeyen çağrılar Yarış efekti, önce örnek alma
  • işlemi başlatmak için yanıt Görevlerinizi sıralama (ilk çağrıda ilk)
  • Beste yapmak
  • Görev iptali Görevi dinamik olarak çatallamak.
  • Destek Eşzamanlılık Saga redux ara katman yazılımı dışında çalışıyor.
  • Kanalları kullanma

Liste uzun zamandır destan belgelerindeki gelişmiş bölümü gözden geçiriyor

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.