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?