2015-08-28 25 views
6

Chciałbym, aby moje oparte na React SPA renderowało po stronie serwera (kto nie jest w tych dniach). Dlatego chcę połączyć React z react-router, redux i niektórymi warstwami kompilacji, takimi jak isomorphic starterkit.Jak logicznie połączyć router reagujący i redux do renderowania po stronie klienta i po stronie serwera?

Istnieje hapi universal redux, który łączy wszystko razem, ale ja zmagaję się z tym, jak zorganizować mój przepływ. Moje dane pochodzą z wielu punktów końcowych interfejsu REST API. Różne komponenty mają różne potrzeby w zakresie danych i powinny ładować dane dokładnie w czasie na kliencie. Zamiast tego na serwerze należy pobrać wszystkie dane dla określonej trasy (zestawu komponentów) i niezbędne komponenty wyrenderowane na łańcuchy.

W moim pierwszym podejściu użyłem oprogramowania pośredniego redux do tworzenia asynchronicznych akcji, które ładują dane, zwracają obietnicę i uruchamiają akcję SOME_DATA_ARRIVED, gdy obietnica się rozwiązuje. Reducers następnie aktualizuje mój sklep, komponenty ponownie renderują, wszystko dobrze. Zasadniczo to działa. Ale potem uświadomiłem sobie, że przepływ staje się niezręczny, w momencie rozpoczęcia rutowania.

Niektóre komponenty, które wymieniają wiele rekordów danych, mają wiele linków do filtrowania rekordów. Każdy filtrowany zestaw danych powinien być dostępny pod własnym adresem URL, takim jak /filter-by/:filter. Używam różnych komponentów <Link to={...}>, aby zmienić adres URL kliknięcia i uruchomić router. Router powinien zaktualizować sklep zgodnie ze stanem reprezentowanym przez bieżący adres URL, co z kolei powoduje ponowne renderowanie odpowiedniego komponentu.

To nie jest łatwe do osiągnięcia. Najpierw wypróbowałem componentWillUpdate, aby uruchomić akcję, która asynchronicznie wczytała moje dane, zapełniła magazyn i spowodowała kolejny cykl ponownego renderowania dla mojego komponentu. Ale to nie działa na serwerze, ponieważ obsługiwane są tylko trzy metody cyklu życia.

Poszukuję odpowiedniego sposobu na uporządkowanie tego. Interakcje użytkowników z aplikacją, które zmieniają stan aplikacji z perspektywy użytkowników, powinny aktualizować adres URL. IMO powinno to spowodować, że router w jakiś sposób załaduje niezbędne dane, zaktualizuje sklep i rozpocznie proces uzgadniania.

Tak interaction -> URL change -> data fetching -> store update -> re-render.

To podejście powinno działać również na serwerze, ponieważ z żądanego adresu URL powinno być możliwe określenie danych do załadowania, wygenerowanie initial state i przekazanie tego stanu do generowania redux store. Ale nie znajduję sposobu, aby to właściwie zrobić. Tak więc dla mnie powstają następujące pytania:

  1. Czy moje podejście jest złe, ponieważ istnieje coś, czego jeszcze nie rozumiem/nie wiem?
  2. Czy dobrze jest zachować dane załadowane z REST API's w redux's store?
  3. Czy to nie jest niezręczne mieć komponenty, które utrzymują state w reduxie store i innych zarządzających swoimi state przez siebie?
  4. Czy pomysł, aby interaction -> URL change -> data fetching -> store update -> re-render był po prostu zły?

Jestem otwarty na wszelkiego rodzaju sugestie.

Odpowiedz

3

Zrobiłem dokładnie to samo dzisiaj. To, co już mieliśmy, to router reagujący i redux. Zmodularyzowaliśmy niektóre moduły, aby wprowadzić do nich rzeczy - i altówkę - to działa. Jako odniesienie użyłem https://github.com/erikras/react-redux-universal-hot-example.

Części:

1. router.js

Wracamy do function (location, history, store) aby skonfigurować router za pomocą obietnic. routes to definicja trasy dla routera reagowania zawierającego wszystkie komponenty.

module.exports = function (location, history, store) { 
    return new Bluebird((resolve, reject) => { 
     Router.run(routes, location, (Handler, state) => { 
      const HandlerConnected = connect(_.identity)(Handler); 
      const component = (
       <Provider store={store}> 
        {() => <HandlerConnected />} 
       </Provider> 
      ); 
      resolve(component); 
     }).catch(console.error.bind(console)); 
    }); 
}; 

2. store.js

Wystarczy przejść do stanu początkowego createStore(reducer, initialState). Po prostu robisz to na serwerze i na kliencie. Dla klienta powinieneś udostępnić stan za pomocą znacznika skryptu (np. window.__initialstate__). Aby uzyskać więcej informacji, patrz http://rackt.github.io/redux/docs/recipes/ServerRendering.html.

3. renderowania na serwerze

Pobierz swoje dane, ustawić stan początkowy z tymi danymi (...data). createRouter = router.js z góry. res.render jest express renderowania szablon jade z następującym

script. 
    window.csvistate.__initialstate__=!{initialState ? JSON.stringify(initialState) : 'null'}; 
... 
#react-start 
    != html 

var initialState = { ...data }; 
var store = createStore(reducer, initialState); 
createRouter(req.url, null, store).then(function (component) { 
    var html = React.renderToString(component); 
    res.render('community/neighbourhood', { html: html, initialState: initialState }); 
}); 

4. dostosowania klientowi

Twój klient może następnie wykonać w zasadzie to samo. location może być HistoryLocation z React-Router

const initialState = window.csvistate.__initialstate__; 
const store = require('./store')(initialState); 

router(location, null, store).then(component => { 
    React.render(component, document.getElementsByClassName('jsx-community-bulletinboard')[0]); 
}); 

Aby odpowiedzieć na Twoje pytania:

  1. Twoje podejście wydaje się słuszna. Robimy to samo. Można nawet dołączyć adres URL jako część stanu.
  2. Wszystkie stan wewnątrz sklepu redux jest dobrą rzeczą. W ten sposób masz jedno źródło prawdy.
  3. Wciąż pracujemy nad tym, co powinno iść w tym momencie. Aktualnie żądamy danych na componentDidMount na serwerze, który powinien już tam być.