Rendering on the server is a bit different since it’s all stateless. The basic idea is that we wrap the app in a stateless <StaticRouter>
instead of a <BrowserRouter>
. We pass in the requested url from the server so the routes can match and a context
prop we’ll discuss next.
服务器渲染有点不同,因为它是无状态的,最初的想法是将app包裹在一个无状态的<StaticRouter>用来替代<BrowserRouter>.我们通过从服务器传入的请求Url匹配路径,就能够获取到内容属性了。
// client
<BrowserRouter>
<App/>
</BrowserRouter>
// server (not the complete story)
<StaticRouter
location={req.url}
context={context}
>
<App/>
</StaticRouter>
When you render a <Redirect> on the client, the browser history changes state and we get the new screen. In a static server environment we can’t change the app state. Instead, we use the context prop to find out what the result of rendering was. If we find a context.url, then we know the app redirected. This allows us to send a proper redirect from the server.
当你在客户端渲染重定向时,浏览器的历史记录也发生改变,并且我们能看到新的页面,在静态服务器环境中我们不能改变APP状态,当时我们可以使用上下文属性来了解渲染结果,如果我们找到context.url,那么我们就知道APP被重定向了,这就允许我们从服务器发送一个合适的重定向。
const context = {};
const markup = ReactDOMServer.renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
if (context.url) {
// Somewhere a `<Redirect>` was rendered
redirect(301, context.url);
} else {
// we're good, send the response
}
Adding app specific context information
添加特定于应用程序的上下文信息
The router only ever adds context.url. But you may want some redirects to be 301 and others 302. Or maybe you’d like to send a 404 response if some specific branch of UI is rendered, or a 401 if they aren’t authorized. The context prop is yours, so you can mutate it. Here’s a way to distinguish between 301 and 302 redirects:
路由器只添加context.url。但是你可能希望一些重定向是301其他是302,或者你想发送404响应来显示某个特定的UI,或者没有被授权发送401响应。你拥有上下文属性,所以你可以修改它。以下是区分301和302重定向的方法:
function RedirectWithStatus({ from, to, status }) {
return (
<Route
render={({ staticContext }) => {
// there is no `staticContext` on the client, so
// we need to guard against that here
if (staticContext) staticContext.status = status;
return <Redirect from={from} to={to} />;
}}
/>
);
}
// somewhere in your app
function App() {
return (
<Switch>
{/* some other routes */}
<RedirectWithStatus status={301} from="/users" to="/profiles" />
<RedirectWithStatus
status={302}
from="/courses"
to="/dashboard"
/>
</Switch>
);
}
// on the server
const context = {};
const markup = ReactDOMServer.renderToString(
<StaticRouter context={context}>
<App />
</StaticRouter>
);
if (context.url) {
// can use the `context.status` that
// we added in RedirectWithStatus
redirect(context.status, context.url);
}
404, 401, or any other status
404,401 或者其他状态
We can do the same thing as above. Create a component that adds some context and render it anywhere in the app to get a different status code.
我们可以做和上面一样的事情。创建一个添加一些上下文的组件,并在应用程序中的任何位置呈现它,以获得不同的状态代码。
function Status({ code, children }) {
return (
<Route
render={({ staticContext }) => {
if (staticContext) staticContext.status = code;
return children;
}}
/>
);
}
Now you can render a Status
anywhere in the app that you want to add the code to staticContext
.
现在,您可以在app中的任何位置渲染状态,然后将代码添加到staticContext。
function NotFound() {
return (
<Status code={404}>
<div>
<h1>Sorry, can’t find that.</h1>
</div>
</Status>
);
}
function App() {
return (
<Switch>
<Route path="/about" component={About} />
<Route path="/dashboard" component={Dashboard} />
<Route component={NotFound} />
</Switch>
);
}
Putting it all together
把它们放在一起
This isn’t a real app, but it shows all of the general pieces you’ll need to put it all together.
这不是真正的APP,但是他显示了所有的碎片,你可以将它们拼接到一起
import http from "http";
import React from "react";
import ReactDOMServer from "react-dom/server";
import { StaticRouter } from "react-router-dom";
import App from "./App.js";
http
.createServer((req, res) => {
const context = {};
const html = ReactDOMServer.renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
if (context.url) {
res.writeHead(301, {
Location: context.url
});
res.end();
} else {
res.write(`
<!doctype html>
<div id="app">${html}</div>
`);
res.end();
}
})
.listen(3000);
And then the client:
然后是客户端
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App.js";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("app")
);
Data Loading
数据加载
There are so many different approaches to this, and there’s no clear best practice yet, so we seek to be composable with any approach, and not prescribe or lean toward one or the other. We’re confident the router can fit inside the constraints of your application.
有这么多不同的方法来解决这个问题,目前还没有明确的最佳实践,所以我们寻求与任何方法相结合,而不是规定或倾向于其中一种方法。我们有信心路由器可以满足您的应用程序的限制。
The primary constraint is that you want to load data before you render. React Router exports the matchPath
static function that it uses internally to match locations to routes. You can use this function on the server to help determine what your data dependencies will be before rendering
主要限制你再渲染之前加载数据。React Router导出matchPath静态函数,它在内部实现是函数将位置与路由匹配。您可以在服务器上使用此函数来帮助渲染之前的数据依赖关系
The gist of this approach relies on a static route config used to both render your routes and match against before rendering to determine data dependencies.
这种方法的要点依赖于一个静态路由配置,该配置用于呈现路由并在呈现之前与之匹配,以确定数据依赖关系。
const routes = [
{
path: "/",
component: Root,
loadData: () => getSomeData()
}
// etc.
];
Then use this config to render your routes in the app:
然后使用此配置在应用程序中呈现路由:
import { routes } from "./routes.js";
function App() {
return (
<Switch>
{routes.map(route => (
<Route {...route} />
))}
</Switch>
);
}
Then on the server you’d have something like:
然后在服务器上,您将有如下内容:
import { matchPath } from "react-router-dom";
// inside a request
const promises = [];
// use `some` to imitate `<Switch>` behavior of selecting only
// the first to match
routes.some(route => {
// use `matchPath` here
const match = matchPath(req.path, route);
if (match) promises.push(route.loadData(match));
return match;
});
Promise.all(promises).then(data => {
// do something w/ the data so the client
// can access it then render the app
});
And finally, the client will need to pick up the data. Again, we aren’t in the business of prescribing a data loading pattern for your app, but these are the touch points you’ll need to implement
最后,客户端需要获取数据。再说一次,我们不是在为你的应用程序规定数据加载模式,但这些是你需要实现的接触点
You might be interested in our React Router Config package to assist with data loading and server rendering with static route configs.
您可能会对我们的React Router Config包感兴趣,它可以帮助您使用静态路由配置加载数据和服务器渲染