2016-04-01 24 views
5

Sono davvero nuovo su React e non riesco a capire come eseguire il rendering di una schermata di "caricamento in corso ..." quando un percorso viene caricato con getComponent. La chiamata getComponent funziona correttamente e visualizza il componente, ma non c'è alcuna indicazione sull'interfaccia utente che qualcosa stia accadendo tra la richiesta e la risposta. Questo è quello che sto cercando di capire.Come mostrare l'interfaccia utente di caricamento quando si chiama getComponent in react-router?

import Main from './pages/Main.jsx'; 
import Test from './pages/Test.jsx'; 
import Home from './pages/Home.jsx'; 


var Routes = { 
    path: "/", 
    component: Main, 
    indexRoute: { 
    component: Home 
    }, 
    childRoutes: [ 
    { 
     path: "test", 
     component: Test 
    }, 
    { 
     path: "about", 
     getComponent: function(path, cb) { 
     require.ensure([], (require) => { 
      cb(null, require("./pages/about/About.jsx")); 
     }); 
     } 
    } 
    ] 
}; 

export default Routes; 

Dopo aver cercato di forzare, senza successo, un componente di "carico" per visualizzare usando onEnter o all'interno della funzione getComponent, ho pensato che forse avrei dovuto provare utilizzando Redux per impostare uno stato di caricamento di vero/falso e ottenere il mio componente vista principale per visualizzare una schermata di caricamento:

import React from 'react'; 
import {connect} from 'react-redux'; 

import NavBar from '../components/Navigation/NavBar.jsx'; 
import Footer from '../components/Footer.jsx'; 
import Loading from './Loading.jsx'; 
import navItems from '../config/navItems.jsx'; 
import setLoading from '../actions/Loading.jsx'; 

var Main = React.createClass({ 
    renderPage: function() { 
    if (this.props.loading) { 
     return (
     <Loading/> 
    ); 
    } else { 
     return this.props.children; 
    } 
    }, 
    render: function() { 
    return (
     <div> 
     <header id="main-header"> 
      <NavBar navigation={navItems}/> 
     </header> 
     <section id="main-section"> 
      {this.renderPage()} 
     </section> 
     <Footer id="main-footer" /> 
     </div> 
    ); 
    } 
}); 

function mapStateToProps(state) { 
    return { 
    loading: state.loading 
    } 
} 

export default connect(mapStateToProps)(Main); 

Questo sembra funzionare se ho impostato manualmente lo stato di caricamento con un'azione, che è quello che stavo cercando di fare. Ma (e penso che questa sarà una vera domanda di noob) non riesco a capire come accedere al negozio/dispatcher dal router.

Non sono sicuro se sto usando i termini di ricerca sbagliati o qualsiasi altra cosa, ma sono completamente privo di idee e ogni tutorial di react-router/redux sembra saltare quello che mi sembra debba essere un comune problema.

Qualcuno può indicarmi la direzione giusta (e anche farmi sapere se quello che sto facendo è la migliore pratica?)?

EDIT: Proverò a chiarire un po 'di più. Nel primo blocco di codice, è possibile notare che se faccio clic su un elemento <Link to="/about">, la funzione getComponent verrà attivata, il che caricherà il componente About.jsx. Il problema che sto avendo è che non riesco a capire come mostrare una sorta di indicatore di caricamento/spinner che apparirà immediatamente dopo aver cliccato sul link e poi averlo sostituito una volta caricato il componente.

ALTRE MODIFICHE: Ho provato a creare un componente wrapper per il caricamento di percorsi asincroni e sembra funzionare, tuttavia si sente davvero hacky e sono sicuro che non è il modo giusto per farlo. Codice Itinerari ora assomiglia a questo:

import Main from './pages/Main.jsx'; 
import Test from './pages/Test.jsx'; 
import Home from './pages/Home.jsx'; 
import AsyncRoute from './pages/AsyncRoute.jsx'; 


var Routes = { 
    path: "/", 
    component: Main, 
    indexRoute: { 
    component: Home 
    }, 
    childRoutes: [ 
    { 
     path: "test", 
     component: Test 
    }, 
    { 
     path: "about", 
     component: AsyncRoute("about") 
    } 
    ] 
}; 

export default Routes; 

La pagina AsyncRoute.jsx si presenta così:

import React from 'react'; 

function getRoute(route, component) { 
    switch(route) { 
    // add each route in here 
    case "about": 
     require.ensure([], (require) => { 
     component.Page = require("./about/About.jsx"); 
     component.setState({loading: false}); 
     }); 
    break; 
    } 
} 

var AsyncRoute = function(route) { 
    return React.createClass({ 
    getInitialState: function() { 
     return { 
     loading: true 
     } 
    }, 
    componentWillMount: function() { 
     getRoute(route, this); 
    }, 
    render: function() { 
     if (this.state.loading) { 
     return (
      <div>Loading...</div> 
     ); 
     } else { 
     return (
      <this.Page/> 
     ); 
     } 
    } 
    }); 
}; 

export default AsyncRoute; 

Se qualcuno ha un'idea migliore, per favore fatemelo sapere.

risposta

0

OK, vediamo se riesco a far luce su questo qui:

io non riesco a capire come accedere al negozio/dispatcher all'interno del router

Non c'è è necessario farlo AFAIK. È possibile specificare tutti i percorsi, elencando i componenti che dovrebbero rispondere a ciascuna rotta (come è stato fatto in precedenza) e quindi collegare ciascuno dei componenti all'archivio di ridondanza. Per il collegamento, la funzione mapStateToProps può essere scritta in un modo molto più semplice, in questo modo:

export default connect(state => state)(Main); 

quanto riguarda lo stato loading: Penso che sia un passo nella direzione sbagliata di avere una componente lento caricamento e per visualizzare un indicatore di attesa mentre sta caricando.Preferirei avere un componente a caricamento veloce che carichi tutti i suoi dati in modo asincrono dal back-end, e mentre i dati non sono ancora disponibili, il componente restituisce un indicatore di attesa. Una volta che i dati sono disponibili, possono essere visualizzati. Questo è fondamentalmente ciò che hai abbozzato nella tua seconda modifica.

Sarebbe ancora meglio se fosse possibile eliminare questo dai dati attuali, ovvero nessun dato presente -> mostra lo schermo di caricamento/dati presenti -> mostra la schermata reale. In questo modo, si evitano problemi nel caso in cui il flag di caricamento non sia sincronizzato. (Più tecnicamente parlando: evitare la ridondanza.)

Quindi, invece di rendere generico il wrapper, preferirei creare un componente autonomo per la schermata di caricamento e visualizzarlo ogni volta che ogni singolo componente ne sente la necessità. (Queste esigenze sono diverse, in modo che sembra essere difficile da gestire questo in modo generico.) Qualcosa di simile a questo:

var Page = function(route) { 
    return React.createClass({ 
    getInitialState: function() { 
     // kick off async loading here 
    }, 
    render: function() { 
     if (!this.props.myRequiredData) { 
     return (
      <Loading /> 
     ); 
     } else { 
     return (
      // display this.props.myRequiredData 
     ); 
     } 
    } 
    }); 
}; 
+0

Grazie, è entrambi, ma in questo momento è più un problema con il mio codice e mi sento io potrebbe essere andato nella direzione sbagliata cercando di risolverlo. Modificherò la mia domanda per cercare di chiarire. –

+0

Perché il componente About.jsx richiederebbe così tanto tempo per essere caricato? Stai caricando qualcosa in modo sincrono? Potresti aggiungere uno schizzo di quel componente? – Nicole

+0

About.jsx non impiegherebbe molto tempo a caricarsi dato che tutto ciò che fa è rendere una pagina di testo (è solo un componente con una funzione di rendering), tuttavia sto pensando di creare un'app enorme con oltre 70 percorsi - alcuni dei quali essere pagine semplici e altre sarebbero cose più complesse e gigantesche che tirano dentro un mucchio di dipendenze, quindi voglio capire prima questa cosa asincrona prima di andare oltre. Stavo pensando che forse potrei creare un componente wrapper che invece esegue il caricamento asincrono, ma non sembra il modo "giusto" per farlo. –

1

penso di avere capito questo. Può o non può essere il modo corretto di fare le cose, ma sembra funzionare. Inoltre non so perché non ci ho pensato prima.

Il primo, spostare il mio codice createstore di un proprio file (store.jsx) così posso importarlo nel punto di ingresso principale così come nel mio file Routes.jsx:

import {createStore} from 'redux'; 
import rootReducer from '../reducers/Root.jsx'; 

var store = createStore(rootReducer); 

export default store; 

Root.jsx si presenta così (si tratta di un brutto pasticcio, ma sto solo cercando di ottenere qualcosa che funziona su un livello di base e poi ti ripulirlo):

import {combineReducers} from 'redux'; 
import user from './User.jsx'; 
import test from './Test.jsx'; 

var loading = function(state = false, action) { 
    switch (action.type) { 
    case "load": 
     return true; 
    case "stop": 
     return false; 
    default: 
     return state; 
    } 
}; 


export default combineReducers({ 
    user, 
    test, 
    loading 
}); 

ho fatto una componente di base che mostra Caricamento/Caricamento in base al valore di "caricamento" del negozio di Redux:

import React from 'react'; 
import {connect} from 'react-redux'; 

var Loading = React.createClass({ 
    render: function() { 
    if (this.props.loading) { 
     return (
     <h1>Loading</h1> 
    ); 
    } else { 
     return (
     <h1>Loaded</h1> 
    ); 
    } 
    } 
}); 

export default connect(state => state)(Loading); 

E ora il mio file Routes.jsx assomiglia a questo (nota ho importato il negozio Redux):

import Main from './pages/Main.jsx'; 
import Test from './pages/Test.jsx'; 
import Home from './pages/Home.jsx'; 
import store from './config/store.jsx'; 

var Routes = { 
    path: "/", 
    component: Main, 
    indexRoute: { 
    component: Home 
    }, 
    childRoutes: [ 
    { 
     path: "test", 
     component: Test 
    }, 
    { 
     path: "about", 
     getComponent: function(path, cb) { 
     store.dispatch({type: "load"}) 
     require.ensure([], (require) => { 
      store.dispatch({type: "stop"}); 
      cb(null, require("./pages/about/About.jsx")); 
     }); 
     } 
    } 
    ] 
}; 

export default Routes; 

Questo sembra funzionare. Non appena si fa clic su <Link/> per andare al percorso/about, viene inviata un'azione per impostare lo stato "loading" su true nel negozio principale. Ciò fa sì che il componente <Loading/> si aggiorni da solo (immagino che alla fine eseguirà il rendering di uno spinner nell'angolo della finestra o qualcosa del genere). Quella strana funzione di require.ensure([]) viene eseguita per fare in modo che Webpack divida il proprio codice e una volta caricato il componente, viene inviata un'altra azione per impostare lo stato di caricamento su falso e il componente viene sottoposto a rendering.

Sono ancora veramente nuovo per React e mentre questo sembra funzionare, non sono sicuro se sia il modo giusto per farlo. Se qualcuno ha un modo migliore, perfavore!

1

Seguendo lo stesso approccio di @David M ho implementato un riduttore di carico e una funzione per avvolgere i dispacci.

Escludendo la creazione archiviare e gestire, sono fondamentalmente i seguenti:

loadingReducer:

// ------------------------------------ 
// Constants 
// ------------------------------------ 
export const LOADING = 'LOADING' 

// ------------------------------------ 
// Actions 
// ------------------------------------ 
const loadQueue = [] 
export const loading = loading => { 
    if (loading) { 
     loadQueue.push(true) 
    } else { 
     loadQueue.pop() 
    } 

    return { 
     type: LOADING, 
     payload: loadQueue.length > 0 
    } 
} 

export const actions = { 
    loading 
} 

// ------------------------------------ 
// Action Handlers 
// ------------------------------------ 

const ACTION_HANDLERS = { 
    [LOADING]: (state, action) => (action.payload) 
} 

// ------------------------------------ 
// Reducer 
// ------------------------------------ 
const initialState = false 
export default function reducer (state = initialState, action) { 
    const handler = ACTION_HANDLERS[action.type] 
    return handler ? handler(state, action) : state 
} 

Notate come loadingQueue mantiene il messaggio di caricamento attivo, mentre ha a disposizione altri moduli per andare a prendere, per nidificato itinerari.

withLoader funzione:

import { loading } from 'loadingReducer' 

const withLoader = (fn, store) => { 
    return (nextState, cb) => { 
     store.dispatch(loading(true)) 

     fn(nextState, (err, cmp) => { 
      store.dispatch(loading(false)) 
      cb(err, cmp) 
     }) 
    } 
} 

export default withLoader 

Ora, quando si definiscono nuove rotte possiamo spedire l'azione di carico implicito utilizzando withLoader:

someRoute:

import withLoader from 'withLoader' 
import store from 'store' 

const route = { 
    path: 'mypath', 
    getComponent: withLoader((nextState, cb) => { 
     require.ensure([], require => { 
      cb(null, require('something').default) 
     }, 'NamedBundle') 
    }, store) 
} 
export default route 
+0

Puoi anche condividere il tuo codice in 'store.js'? Come faresti ad avere il negozio globale? – Tieme

0

di carico dinamico router asincrone stanno usando require.ensure, che usano jsonp per scaricare gli script dalla rete. a causa del lento collegamento in rete, a volte, i blocchi dell'interfaccia utente, lo schermo mostra ancora le componenti delle anteprime che reagiscono.

@Nicole, il molto lento non è il caricamento dei dati all'interno dei componenti, ma è l'auto dei componenti, a causa della jsonp