服务端渲染有点不同,因为它都是无状态的。
基本想法是用无状态的<StaticRouter>替换<BrowserRouter>来包裹app
我们从服务器请求URL,以便路由可以匹配,然后我们将讨论上下文支持。
// client
<BrowserRouter>
<App/>
</BrowserRouter>
// server (not the complete story)
<StaticRouter
location={req.url}
context={context}
>
<App/>
</StaticRouter>
当你在客户端使用<Redirect/>渲染时,会改变浏览器的历史记录状态并打开一个新的页面。
在静态服务器环境中,我们无法改变App的状态,而是使用context属性寻找渲染的结果。如果我们找到了context.url这个属性,说明app进行了重定向,这允许我们从服务器发送适当的重定向。
const context = {};
{* ReactDOMServer.renderToString(component) 将react组件渲染成html字符串,然后在客户端渲染 *}
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
}
添加app特定上下文信息
路由只添加了context.url属性。但您可能希望某些重定向为301和其他302.或者您可能希望在呈现UI的某个特定分支时发送404响应,或者如果未授权则发送401。
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);
}
我们可以做同样的事情。创建一个组件,添加一些上下文并在应用程序中的任何位置呈现它以获取不同的状态代码。
function Status({ code, children }) {
return (
<Route
render={({ staticContext }) => {
if (staticContext) staticContext.status = code;
return children;
}}
/>
);
}
现在你可以通过渲染Status组件,在app的任何位置呈现不同的状态码。
function NotFound() {
return (
<Status code={404}>
<div>
<h1>Sorry, can’t find that.</h1>
</div>
</Status>
);
}
// somewhere else
<Switch>
<Route path="/about" component={About} />
<Route path="/dashboard" component={Dashboard} />
<Route component={NotFound} />
</Switch>;
将服务端与客户端代码放在一起
// 服务端
import { createServer } from "http";
import React from "react";
import ReactDOMServer from "react-dom/server";
import { StaticRouter } from "react-router";
import App from "./App";
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);
// 客户端
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("app")
);
数据加载
有很多不同的方法进行数据加载,但没有明确的最佳实践,所以我们寻求可以用任何方法组合,而不是规定或倾向于一个或另一个。
假定路由可以适应您的应用程序的限制。
主要的限制是您希望在渲染之前加载数据。React Router导出它在内部使用的matchPath
静态函数,以匹配路由的位置。您可以在服务器上使用该函数来确定渲染之前的数据依赖项。这种方法依赖于静态路由配置,用于在呈现之前呈现路由并匹配以确定数据依赖性。
const routes = [
{
path: "/",
component: Root,
loadData: () => getSomeData()
}
// etc.
];
然后使用此配置在app中渲染路由:
import { routes } from "./routes";
function App() {
return (
<Switch>
{routes.map(route => (
<Route {...route} />
))}
</Switch>
);
}
然后在服务器上
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
});
最后,客户端需要获取数据。
同样,我们不是为您的应用程序规定数据加载模式的业务,但这些是您需要实现的接触点。
您可能对我们的React Router Config包感兴趣,以协助使用静态路由配置进行数据加载和服务器渲染。
网友评论