A che livello di nidificazione i componenti devono leggere le quadro dai negozi in stream?

Sto riscrivendo la mia app per usare Flux e ho un problema con il recupero dei dati dai negozi. Ho molti componenti e nidificano molto. Alcuni di essi sono grandi ( Article ), alcuni sono piccoli e semplici ( UserAvatar , UserLink ).

Ho lottato con il punto in cui nella gerarchia dei componenti dovrei leggere i dati dai negozi.
Ho provato due approcci estremi, nessuno dei quali mi è piaciuto molto:

Tutti i componenti dell’ quadro leggono i propri dati

Ogni componente che necessita di alcuni dati da Store riceve solo l’ID quadro e recupera l’entity framework da sola.
Ad esempio, l’ Article viene passato articleId , UserAvatar e UserLink vengono passati userId .

Questo approccio ha diversi svantaggi significativi (discussi sotto il codice di esempio).

 var Article = React.createClass({ mixins: [createStoreMixin(ArticleStore)], propTypes: { articleId: PropTypes.number.isRequired }, getStateFromStores() { return { article: ArticleStore.get(this.props.articleId); } }, render() { var article = this.state.article, userId = article.userId; return ( 

{article.title}

{article.text}

    Read more by .

    ) } }); var UserAvatar = React.createClass({ mixins: [createStoreMixin(UserStore)], propTypes: { userId: PropTypes.number.isRequired }, getStateFromStores() { return { user: UserStore.get(this.props.userId); } }, render() { var user = this.state.user; return ( ) } }); var UserLink = React.createClass({ mixins: [createStoreMixin(UserStore)], propTypes: { userId: PropTypes.number.isRequired }, getStateFromStores() { return { user: UserStore.get(this.props.userId); } }, render() { var user = this.state.user; return ( {this.props.children || user.name} ) } });

    Aspetti negativi di questo approccio:

    • È frustrante avere componenti di 100 potenzialmente abbonati ai negozi;
    • È difficile tenere traccia di come vengono aggiornati i dati e in quale ordine, perché ogni componente recupera i dati in modo indipendente;
    • Anche se potresti già avere un’entity framework nello stato, sei obbligato a trasmettere il suo ID ai bambini, che lo recupereranno di nuovo (o altrimenti rompere la coerenza).

    Tutti i dati vengono letti una volta al livello più alto e trasmessi ai componenti

    Quando ero stanco di rintracciare i bug, ho cercato di mettere tutti i dati recuperati al livello più alto. Ciò, tuttavia, si è rivelato imansible perché per alcune quadro ho diversi livelli di nidificazione.

    Per esempio:

    • Una Category contiene UserAvatar di persone che contribuiscono a quella categoria;
    • Un Article può avere diverse Category .

    Pertanto, se volessi recuperare tutti i dati dai negozi a livello di Article , avrei bisogno di:

    • Recupera articolo da ArticleStore ;
    • Recupera tutte le categorie dell’articolo da CategoryStore ;
    • Recupera separatamente i contributori di ciascuna categoria da UserStore ;
    • In qualche modo, trasmetti tutti i dati ai componenti.

    Ancora più frustrante, ogni volta che ho bisogno di un’entity framework profondamente annidata, dovrei aggiungere del codice a ogni livello di nidificazione per poterlo anche trasferire.

    Riassumendo

    Entrambi gli approcci sembrano imperfetti. Come risolvo questo problema in modo più elegante?

    I miei obiettivi:

    • I negozi non dovrebbero avere un numero folle di abbonati. È stupido che ogni UserLink ascolti UserStore se i componenti padre lo fanno già.

    • Se il componente genitore ha recuperato qualche object dall’archivio (es. user ), non voglio che i componenti nidificati debbano recuperarlo di nuovo. Dovrei essere in grado di passarlo tramite oggetti di scena.

    • Non dovrei dover recuperare tutte le entity framework (comprese le relazioni) al livello più alto perché complicherebbe l’aggiunta o la rimozione delle relazioni. Non voglio introdurre nuovi oggetti di scena a tutti i livelli di nidificazione ogni volta che un’ quadro annidata ottiene una nuova relazione (ad esempio, la categoria ottiene un curator ).

    La maggior parte delle persone inizia ascoltando gli archivi pertinenti in un componente di visualizzazione del controller nella parte superiore della gerarchia.

    Più tardi, quando sembra che molti oggetti di scena irrilevanti vengano trasmessi attraverso la gerarchia ad alcuni componenti profondamente annidati, alcune persone decideranno che è una buona idea lasciare che un componente più profondo ascolti le modifiche nei negozi. Ciò offre un migliore incapsulamento del dominio del problema relativo a questo ramo più profondo dell’albero dei componenti. Ci sono buoni argomenti da fare per farlo con giudizio.

    Tuttavia, preferisco sempre ascoltare in alto e semplicemente trasmettere tutti i dati. A volte prenderò persino l’intero stato del negozio e lo trasmetterò attraverso la gerarchia come un singolo object, e lo farò per più negozi. Quindi avrei un supporto per lo stato UserStore , e un altro per lo stato di UserStore , ecc. Trovo che evitando controller-viste profondamente nidificate mantenga un singolo punto di ingresso per i dati e unisca il stream di dati. Altrimenti, ho più fonti di dati e questo può diventare difficile da eseguire il debug.

    Digitare il controllo è più difficile con questa strategia, ma è ansible impostare una “forma” o un modello di tipo, per l’object di grandi dimensioni come prop con i PropTypes di React. Vedi: https://github.com/facebook/react/blob/master/src/core/ReactPropTypes.js#L76-L91 http://facebook.github.io/react/docs/reusable-components.html#prop -Validazione

    Nota che potresti voler mettere la logica di associare i dati tra negozi nei negozi stessi. Quindi il tuo ArticleStore potrebbe waitFor() lo UserStore e includere gli utenti rilevanti con ogni record di Article fornito tramite getArticles() . Fare questo nelle tue visualizzazioni sembra spingere la logica nel livello vista, che è una pratica da evitare quando ansible.

    Potresti anche essere tentato di utilizzare transferPropsTo() , e molte persone amano farlo, ma preferisco mantenere tutto esplicito per la leggibilità e quindi la manutenibilità.

    FWIW, la mia comprensione è che David Nolen adotta un approccio simile con il suo framework Om (che è in qualche modo compatibile con Flux ) con un singolo entry point di dati sul nodo radice – l’equivalente in Flux sarebbe avere solo una vista controller ascoltando tutti i negozi. Ciò è reso efficiente utilizzando shouldComponentUpdate() e strutture di dati immutabili che possono essere confrontate per riferimento, con ===. Per strutture di dati immutabili, controlla i mori di David o immutable-js di Facebook. La mia conoscenza limitata di Om deriva principalmente da The Future of JavaScript MVC Frameworks

    L’approccio con cui sono arrivato è che ogni componente riceve i suoi dati (non ID) come supporto. Se un componente nidificato ha bisogno di un’entity framework correlata, è compito del componente principale recuperarlo.

    Nel nostro esempio, l’ Article dovrebbe avere un article object che è un object (presumibilmente recuperato da ArticleList o ArticleList ).

    Poiché l’ Article vuole anche rendere UserLink e UserAvatar per l’autore dell’articolo, si UserStore a UserStore e manterrà l’ author: UserStore.get(article.authorId) nel suo stato. UserAvatar quindi UserLink e UserAvatar con questo this.state.author . Se desiderano inoltrarlo ulteriormente, possono farlo. Nessun componente figlio dovrà recuperare nuovamente questo utente.

    Reiterare:

    • Nessun componente riceve mai l’ID come sostegno; tutti i componenti ricevono i rispettivi oggetti.
    • Se i componenti del bambino hanno bisogno di un’ quadro, è responsabilità del genitore recuperarla e passarla come sostegno.

    Questo risolve il mio problema abbastanza bene. Esempio di codice riscritto per utilizzare questo approccio:

     var Article = React.createClass({ mixins: [createStoreMixin(UserStore)], propTypes: { article: PropTypes.object.isRequired }, getStateFromStores() { return { author: UserStore.get(this.props.article.authorId); } }, render() { var article = this.props.article, author = this.state.author; return ( 

    {article.title}

    {article.text}

    Read more by .

    ) } }); var UserAvatar = React.createClass({ propTypes: { user: PropTypes.object.isRequired }, render() { var user = this.props.user; return ( ) } }); var UserLink = React.createClass({ propTypes: { user: PropTypes.object.isRequired }, render() { var user = this.props.user; return ( {this.props.children || user.name} ) } });

    Ciò mantiene stupidi i componenti più interni, ma non ci costringe a complicare l’inferno dei componenti di alto livello.

    La mia soluzione è molto più semplice. Ogni componente che ha il suo stato è autorizzato a parlare e ad ascoltare i negozi. Questi sono componenti molto simili a controller. I componenti nidificati più profondi che non mantengono lo stato ma solo il rendering non sono permessi. Ricevono solo oggetti di scena per il rendering puro, molto simile a una vista.

    In questo modo tutto passa da componenti stateful a componenti senza stato. Mantenere gli stateful bassi.

    Nel tuo caso, l’articolo sarebbe statico e quindi parla ai negozi e al Link utente ecc. Renderizzerebbe solo così riceverebbe article.user come supporto.

    I problemi descritti nelle tue 2 filosofie sono comuni a qualsiasi applicazione singola pagina.

    Sono discussi brevemente in questo video: https://www.youtube.com/watch?v=IrgHurBjQbg e Relay ( https://facebook.github.io/relay ) è stato sviluppato da Facebook per superare il compromesso che descrivi.

    L’approccio di Relay è molto incentrato sui dati. È una risposta alla domanda “Come ottengo solo i dati necessari per ogni componente in questa vista in una query al server?” E allo stesso tempo Relay si assicura di avere un piccolo accoppiamento attraverso il codice quando un componente viene utilizzato in più viste.

    Se Relay non è un’opzione , “Tutti i componenti dell’ quadro leggono i propri dati” sembra un approccio migliore per la situazione che descrivi. Penso che l’idea sbagliata di Flux sia ciò che è un negozio. Il concetto di negozio non esiste per essere il luogo in cui è conservato un modello o una collezione di oggetti. I negozi sono luoghi temporanei in cui l’applicazione inserisce i dati prima della visualizzazione della visualizzazione. La vera ragione per cui esistono è di risolvere il problema delle dipendenze tra i dati che vanno in negozi diversi.

    Quello che Flux non sta specificando è come un negozio si rapporta al concetto di modelli e collezione di oggetti (a la Backbone). In questo senso, alcune persone stanno trasformando un negozio di flussi in un posto dove mettere la collezione di oggetti di un tipo specifico che non è a filo per tutto il tempo in cui l’utente tiene aperto il browser ma, come capisco il stream, non è quello che un negozio dovrebbe essere.

    La soluzione consiste nell’avere un altro livello in cui le quadro necessarie per il rendering della vista (e potenzialmente di più) vengono archiviate e mantenute aggiornate. Se si tratta di un livello che include modelli e raccolte astratti, non è un problema se i sottocomponenti devono eseguire nuovamente una query per ottenere i propri dati.