2014-09-16 14 views
13

Il dispatcher di flusso di Facebook explicitly prohibits ActionCreators from dispatching other ActionCreators. Questo restrizione è probabilmente una buona idea poiché impedisce alla tua applicazione di creare catene di eventi.Evitare catene di eventi con dipendenze asincrone dei dati

Questo tuttavia diventa un problema non appena si dispone di archivi contenenti dati da ActionCreators asincroni che dipendono l'uno dall'altro. Se CategoryProductsStore dipende da CategoryStore, non sembra essere un modo per evitare le catene di eventi quando non si ricorre a differire l'azione di follow-up.

Scenario 1: Un archivio che contiene un elenco di prodotti in una categoria ha bisogno di sapere da quale ID categoria dovrebbe prendere prodotti.

var CategoryProductActions = { 
    get: function(categoryId) { 
    Dispatcher.handleViewAction({ 
     type: ActionTypes.LOAD_CATEGORY_PRODUCTS, 
     categoryId: categoryId 
    }) 

    ProductAPIUtils 
     .getByCategoryId(categoryId) 
     .then(CategoryProductActions.getComplete) 
    }, 

    getComplete: function(products) { 
    Dispatcher.handleServerAction({ 
     type: ActionTypes.LOAD_CATEGORY_PRODUCTS_COMPLETE, 
     products: products 
    }) 
    } 
} 

CategoryStore.dispatchToken = Dispatcher.register(function(payload) { 
    var action = payload.action 

    switch (action.type) { 
    case ActionTypes.LOAD_CATEGORIES_COMPLETE: 
     var category = action.categories[0] 

     // Attempt to asynchronously fetch products in the given category, this causes an invariant to be thrown. 
     CategoryProductActions.get(category.id) 

     ... 

Scenario 2: Un altro scenario è quando un componente figlio è montato come il risultato di un cambiamento Store e la sua componentWillMount/componentWillReceivePropsattempts to fetch data via an asynchronous ActionCreator:

var Categories = React.createClass({ 
    componentWillMount() { 
    CategoryStore.addChangeListener(this.onStoreChange) 
    }, 

    onStoreChange: function() { 
    this.setState({ 
     category: CategoryStore.getCurrent() 
    }) 
    }, 

    render: function() { 
    var category = this.state.category 

    if (category) { 
     var products = <CategoryProducts categoryId={category.id} /> 
    } 

    return (
     <div> 
     {products} 
     </div> 
    ) 
    } 
}) 

var CategoryProducts = React.createClass({ 
    componentWillMount: function() { 
    if (!CategoryProductStore.contains(this.props.categoryId)) { 
     // Attempt to asynchronously fetch products in the given category, this causes an invariant to be thrown. 
     CategoryProductActions.get(this.props.categoryId) 
    } 
    } 
}) 

ci sono modi per evitare questo senza ricorrendo a differire?

+0

Per lo scenario n. 1, inserisco questo tipo di logica nei creatori di azioni, in modo che i negozi rispondano solo alle modifiche nei dati. Nel caso in cui esista una logica asincrona, a volte un action creator invia più azioni ai negozi. Mi sono imbattuto nello scenario n. 2 e ho passato a 'DidMount' (nel caso di caricamento asincrono dei dati), o, occasionalmente, rimandato a' setTimeout'. –

+0

@BrandonTilley Ho chiarito entrambi gli esempi, in entrambi i casi l'ActionCreator per recuperare i prodotti in una categoria attiva un'operazione API asincrona. –

+0

@SimenBrekken hai risolto il tuo problema? Puoi guardare qui http://stackoverflow.com/questions/32537568/flux-waitfor-specific-event? –

risposta

3

Ogni volta che si recupera lo stato dell'applicazione, si desidera recuperare tale stato direttamente dai negozi, con metodi getter. Le azioni sono oggetti che informano i Negozi. Potresti pensare a loro come a una richiesta di cambiamento di stato. Non dovrebbero restituire alcun dato. Non sono un meccanismo con cui si dovrebbe recuperare lo stato dell'applicazione, ma piuttosto semplicemente cambiandolo.

Quindi, nello scenario 1, getCurrent(category.id) è qualcosa che deve essere definito su un negozio.

Nello scenario 2, sembra che si stia riscontrando un problema con l'inizializzazione dei dati dello Store. Solitamente gestisco ciò (idealmente) trasferendo i dati nei negozi prima di rendere il componente root. Lo faccio in un modulo bootstrap. In alternativa, se questo deve essere assolutamente asincrono, è possibile creare tutto per lavorare con una lavagna vuota, quindi eseguire nuovamente il rendering dopo che gli archivi hanno risposto a un'azione INITIAL_LOAD.

+2

Ho chiarito entrambi gli esempi, in entrambi i casi l'ActionCreator per recuperare i prodotti in una categoria attiva un'operazione API asincrona. Quindi, nello scenario n. 1, prima raccolgo un elenco di categorie dalla mia API e poi i prodotti in quella categoria, anche tramite l'API. Per quanto riguarda lo scenario n. 2, faccio la stessa cosa ma solo quando il componente è montato e richiede dati. Non so quando il componente verrà montato, quindi non è possibile ottenere dati nel componente root. –

+0

Scenario 1: dov'è il creatore dell'azione che produce l'azione di tipo LOAD_CATEGORIES_COMPLETE? Sembrerebbe che la chiamata all'API debba passare a quel creatore di azioni. Non è chiaro per me che LOAD_CATEGORY_PRODUCTS sia utile. – fisherwebdev

+0

Scenario 2: la tua vista sta gestendo i dati del negozio. Lascia che il negozio gestisca i propri dati. – fisherwebdev

0

Per lo scenario 1:

Vorrei inviare nuovo l'azione dal punto di vista stesso, in modo un nuovo azione -> dispatcher -> negozio -> ciclo di vista si innescherà.

Posso immaginare che la tua vista debba recuperare l'elenco delle categorie e deve anche mostrare, di default, l'elenco dei prodotti della prima categoria.

In modo che la vista reagisca ai cambiamenti con CategoryStore prima. Una volta caricato l'elenco delle categorie, attivare la nuova azione per ottenere i prodotti della prima categoria.

Ora, questa è la parte difficile. Se lo fai nel listener di modifiche della vista, otterrai un'eccezione invariante, quindi qui devi attendere che il carico utile della prima azione sia completamente elaborato.

Un modo per risolvere questo è utilizzare il timeout sull'ascoltatore di modifiche della vista. Qualcosa di simile a ciò che viene spiegato qui: https://groups.google.com/forum/#!topic/reactjs/1xR9esXX1X4 ma invece di inviare l'azione dal negozio, lo si farebbe dalla vista.

function getCategoryProducts(id) { 
setTimeout(() => { 
    if (!AppDispatcher.isDispatching()) { 
     CategoryProductActions.get(id); 
    } else { 
     getCategoryProducts(id); 
    } 
}, 3); 
} 

Lo so, è orribile, ma almeno non avrà negozi concatenamento azioni o logica di dominio che perde ai creatori di azione. Con questo approccio, le azioni sono "richieste" dalle opinioni che effettivamente le richiedono.

L'altra opzione, che non ho provato onestamente, è quella di ascoltare l'evento DOM una volta popolato il componente con l'elenco delle categorie. In quel momento, invii la nuova azione che attiverà una nuova catena "Flux". In realtà penso che questo sia più ordinato, ma come detto, non ho ancora provato.

Problemi correlati