2016-07-18 20 views
2

Ich habe eine universelle React App, die Redux und React Router verwendet. Einige meiner Routen enthalten Parameter, die auf dem Client eine AJAX-Anforderung auslösen, um die Daten für die Anzeige zu hydratisieren. Auf dem Server können diese Anforderungen synchron erfüllt und bei der ersten Anforderung gerendert werden.Wie man serverseitige Parameter mit React + Redux hydratisiert

Das Problem, auf das ich stoße, ist folgendes: Wenn eine Lebenszyklusmethode (z. B. componentWillMount) für eine geroutete Komponente aufgerufen wird, ist es zu spät, eine Reduxaktion zu senden, die beim ersten Rendern berücksichtigt wird. Hier

ist eine vereinfachte Ansicht meiner serverseitige Rendering-Code:

routes.js

export default getRoutes (store) { 
    return (
    <Route path='/' component={App}> 
     <Route path='foo' component={FooLayout}> 
     <Route path='view/:id' component={FooViewContainer} /> 
     </Route> 
    </Route> 
) 
} 

server.js

let store = configureStore() 
let routes = getRoutes() 
let history = createMemoryHistory(req.path) 
let location = req.originalUrl 
match({ history, routes, location }, (err, redirectLocation, renderProps) => { 
    if (redirectLocation) { 
    // redirect 
    } else if (err) { 
    // 500 
    } else if (!renderProps) { 
    // 404 
    } else { 
    let bodyMarkup = ReactDOMServer.renderToString(
     <Provider store={store}> 
     <RouterContext {...renderProps} /> 
     </Provider>) 
    res.status(200).send('<!DOCTYPE html>' + 
     ReactDOMServer.renderToStaticMarkup(<Html body={bodyMarkup} />)) 
    } 
}) 

Wenn die FooViewContainer Komponente erstellt auf dem Server, seine Requisiten für den ersten Render werden bereits fi sein fest. Jede Aktion, die ich an den Store absende, wird beim ersten Aufruf von render() nicht berücksichtigt, was bedeutet, dass sie nicht in den Angeboten der Seitenanforderung enthalten sind.

Der id Parameter, den React Router weitergibt, ist für diesen ersten Rendervorgang nicht nützlich. Ich muss diesen Wert synchron in ein geeignetes Objekt hydratisieren. Wo sollte ich diese Hydratation stellen?

Eine Lösung wäre, es inline innerhalb der render()-Methode für Instanzen zu setzen, wo es auf dem Server aufgerufen wird. Dies scheint mir offensichtlich falsch zu sein, weil 1) es semantisch keinen Sinn ergibt, und 2) welche Daten auch immer gesammelt werden, würde nicht richtig in den Laden geschickt werden.

Eine andere Lösung, die ich gesehen habe, ist das Hinzufügen einer statischen fetchData-Methode zu jeder der Container-Komponenten in der Router-Kette. z.B. so etwas wie dieses:

FooViewContainer.js

class FooViewContainer extends React.Component { 

    static fetchData (query, params, store, history) { 
    store.dispatch(hydrateFoo(loadFooByIdSync(params.id))) 
    } 

    ... 

} 

server.js

let { query, params } = renderProps 
renderProps.components.forEach(comp => 
    if (comp.WrappedComponent && comp.WrappedComponent.fetchData) { 
    comp.WrappedComponent.fetchData(query, params, store, history) 
    } 
}) 

Ich fühle mich muss es besser als dieser Ansatz sein. Es scheint nicht nur ziemlich unelegant (ist .WrappedComponent eine zuverlässige Schnittstelle?), Aber es funktioniert auch nicht mit Komponenten höherer Ordnung. Wenn eine der gerouteten Komponentenklassen von etwas anderem als connect() umschlossen ist, wird dies nicht mehr funktionieren.

Was fehlt mir hier?

Antwort

0

Es scheint nicht eine idiomatische Weise, dies zu tun als die fetchData Ansatz, die ich in meiner ursprünglichen Frage enthalten. Obwohl es noch unelegant mir scheint, hat es weniger Probleme, als ich anfangs realisiert:

  • .WrappedComponent eine stabile Schnittstelle, aber der Bezug ist ohnehin nicht benötigt. Die Funktion Redux connect hebt automatisch alle statischen Methoden aus der ursprünglichen Klasse in ihren Wrapper auf.
  • Jede andere Komponente höherer Ordnung, die einen Redux-gebundenen Container umschließt auch muss alle statischen Methoden hissen (oder durchgehen).

Es kann auch andere Überlegungen, die ich nicht sehe, aber ich habe auf einem Hilfsmethode wie dies in meiner server.js Datei angesiedelt:

function prefetchComponentData (renderProps, store) { 
    let { params, components, location } = renderProps 
    components.forEach(componentClass => { 
    if (componentClass && typeof componentClass.prefetchData === 'function') { 
     componentClass.prefetchData({ store, params, location }) 
    } 
    }) 
}