Веб-приложение получает доступ к определенной HTML-странице через URL-адрес, и каждый URL-адрес соответствует ресурсу. В традиционных веб-приложениях браузер отправляет запрос на сервер через URL-адрес, а сервер считывает ресурс и отправляет обработанное содержимое страницы в браузер, тогда как в одностраничном приложении все изменения URL-адреса обрабатываются на стороне браузера. , При изменении URL-адреса браузер заменяет контент через js. Для серверных приложений, когда запрашивается URL-ресурс, сервер отправляет содержимое страницы, соответствующее URL-адресу, в браузер. Браузер загружает js, на который ссылается страница, и выполняет инициализацию маршрутизации на стороне клиента, а затем выполняются последующие переходы маршрутизации. на стороне браузера серверная сторона отвечает только за первый рендеринг запроса, отправленного из браузера
Первый в ранее построенном проектеsrc
Создайте 4 компонента страницы в каталоге
Затем установите веб-зависимость React react-router-dom
Примечание: react-router-dom версии 4.x
Предыдущий раздел:Строительство проекта
См. адрес исходного кода в конце статьи.
Код сервера в этом разделе был переписан, нажмите, чтобы узнать подробностиздесь
Внешняя маршрутизация
При написании маршрутизации React мы сначала используем самый простой подход, вApp.jsx
используется вBrowserRouter
Компонент оборачивает корневой узелNavLink
Компонент оборачивает текст в тег li
import {
BrowserRouter as Router,
Route,
Switch,
Redirect,
NavLink
} from "react-router-dom";
import Bar from "./views/Bar";
import Baz from "./views/Baz";
import Foo from "./views/Foo";
import TopList from "./views/TopList";
render() {
return (
<Router>
<div>
<div className="title">This is a react ssr demo</div>
<ul className="nav">
<li><NavLink to="/bar">Bar</NavLink></li>
<li><NavLink to="/baz">Baz</NavLink></li>
<li><NavLink to="/foo">Foo</NavLink></li>
<li><NavLink to="/top-list">TopList</NavLink></li>
</ul>
<div className="view">
<Switch>
<Route path="/bar" component={Bar} />
<Route path="/baz" component={Baz} />
<Route path="/foo" component={Foo} />
<Route path="/top-list" component={TopList} />
<Redirect from="/" to="/bar" exact />
</Switch>
</div>
</div>
</Router>
);
}
Каждое представление маршрута в приведенном выше коде используетRoute
Заполнитель и компоненты, соответствующие представлению маршрутизации, необходимы в текущем компоненте.import
Заходи, если есть вложенность роутинга, то компоненты представления будут разбросаны по разным компонентам, которые будутimport
, когда компоненты слишком вложены друг в друга, их будет сложно поддерживать
Затем измените вышеуказанные проблемы, все компоненты представления находятся в файле js.import
, экспортировать список объектов конфигурации маршрутизации, соответственно использоватьpath
указать путь маршрутизации,component
Укажите компонент просмотра маршрута
src/router/index.js
import Bar from "../views/Bar";
import Baz from "../views/Baz";
import Foo from "../views/Foo";
import TopList from "../views/TopList";
const router = [
{
path: "/bar",
component: Bar
},
{
path: "/baz",
component: Baz
},
{
path: "/foo",
component: Foo
},
{
path: "/top-list",
component: TopList,
exact: true
}
];
export default router;
существуетApp.jsx
Импорт настроенного объекта маршрутизации в , цикл назадRoute
<div className="view">
<Switch>
{
router.map((route, i) => (
<Route key={i} path={route.path} component={route.component}
exact={route.exact} />
))
}
<Redirect from="/" to="/bar" exact />
</Switch>
</div>
В сложных приложениях вложение компонентов неизбежно.Route
изcomponent
Атрибуты могут передавать не только тип компонента, но и функцию обратного вызова, через которую через функцию обратного вызова передается подмаршрут текущего компонента.props
пройти, затем продолжить цикл
Для поддержки вложенности компонентов мы используемRoute
инкапсулироватьNestedRoute
компоненты
src/router/NestedRoute.jsx
import React from "react";
import { Route } from "react-router-dom";
const NestedRoute = (route) => (
<Route path={route.path} exact={route.exact}
/*渲染路由对应的视图组件,将路由组件的props传递给视图组件*/
render={(props) => <route.component {...props} router={route.routes}/>}
/>
);
export default NestedRoute;
затем изsrc/router/index.js
экспорт в
import NestedRoute from "./NestedRoute";
...
export {
router,
NestedRoute
}
App.jsx
import { router, NestedRoute } from "./router";
<div className="view">
<Switch>
{
router.map((route, i) => (
<NestedRoute key={i} {...route} />
))
}
<Redirect from="/" to="/bar" exact />
</Switch>
</div>
Используйте вложенные маршруты, как показано ниже.
const router = [
{
path: "/a",
component: A
},
{
path: "/b",
component: B
},
{
path: "/parent",
component: Parent,
routes: [
{
path: "/child",
component: Child,
}
]
}
];
Parent.jsx
this.props.router.map((route, i) => (
<NestedRoute key={i} {...route} />
))
Внутренняя маршрутизация
В отличие от маршрутизации на стороне клиента, маршрутизация на стороне сервера не имеет состояния. React предоставляет компонент без состоянияStaticRouter,В направленииStaticRouter
передайте адрес, позвонитеReactDOMServer.renderToString()
может соответствовать представлению маршрутизации
существуетApp.jsx
различать клиент и сервер, тогдаexport
различные корневые компоненты
let App;
if (process.env.REACT_ENV === "server") {
// 服务端导出Root组件
App = Root;
} else {
App = () => {
return (
<Router>
<Root />
</Router>
);
};
}
export default App;
следующий заentry-server.js
модифицировать, использоватьStaticRouter
Оберните корневой компонент, передайте в контекстеcontext
иlocation
, при использовании функции для создания нового компонента
import React from "react";
import { StaticRouter } from "react-router-dom";
import Root from "./App";
const createApp = (context, url) => {
const App = () => {
return (
<StaticRouter context={context} location={url}>
<Root/>
</StaticRouter>
)
}
return <App />;
}
module.exports = {
createApp
};
server.js
получено вcreateApp
функция
let createApp;
let template;
let readyPromise;
if (isProd) {
let serverEntry = require("../dist/entry-server");
createApp = serverEntry.createApp;
template = fs.readFileSync("./dist/index.html", "utf-8");
// 静态资源映射到dist路径下
app.use("/dist", express.static(path.join(__dirname, "../dist")));
} else {
readyPromise = require("./setup-dev-server")(app, (serverEntry, htmlTemplate) => {
createApp = serverEntry.createApp;
template = htmlTemplate;
});
}
Когда сервер обрабатывает запрос, передается текущий URL-адрес, и сервер будет сопоставлять компонент представления, соответствующий текущему URL-адресу.
const render = (req, res) => {
console.log("======enter server======");
console.log("visit url: " + req.url);
let context = {};
let component = createApp(context, req.url);
let html = ReactDOMServer.renderToString(component);
let htmlStr = template.replace("<!--react-ssr-outlet-->", `<div id='app'>${html}</div>`);
// 将渲染后的html字符串发送给客户端
res.send(htmlStr);
}
404 и редиректы
Когда запрошенный ресурс сервера не существует, сервер должен отправить ответ 404, маршрут перенаправляется, и серверу также необходимо перенаправить на указанный URL-адрес.StaticRouter
Предоставляет реквизит для передачи объекта контекстаcontext
, передается при рендеринге маршрутизируемых компонентовstaticContext
Получите и установите код состояния. Когда сервер выполняет рендеринг, код состояния оценивается для обработки ответа. Если перенаправление происходит при отображении маршрута на стороне сервера, передайтеcontext
Атрибуты с информацией, связанной с перенаправлением, добавляются автоматически, напримерurl
Для обработки статуса 404 мы инкапсулируем компонент статусаStatusRoute
src/router/StatusRoute.jsx
import React from "react";
import { Route } from "react-router-dom";
const StatusRoute = (props) => (
<Route render={({staticContext}) => {
// 客户端无staticContext对象
if (staticContext) {
// 设置状态码
staticContext.status = props.code;
}
return props.children;
}} />
);
export default StatusRoute;
отsrc/router/index.js
экспорт в
import StatusRoute from "./StatusRoute";
...
export {
router,
NestedRoute,
StatusRoute
}
существуетApp.jsx
используется вStatusRoute
компоненты
<div className="view">
<Switch>
{
router.map((route, i) => (
<NestedRoute key={i} {...route} />
))
}
<Redirect from="/" to="/bar" exact />
<StatusRoute code={404}>
<div>
<h1>Not Found</h1>
</div>
</StatusRoute>
</Switch>
</div>
render
Функция изменена следующим образом
let context = {};
let component = createApp(context, req.url);
let html = ReactDOMServer.renderToString(component);
if (!context.status) { // 无status字段表示路由匹配成功
let htmlStr = template.replace("<!--react-ssr-outlet-->", `<div id='app'>${html}</div>`);
// 将渲染后的html字符串发送给客户端
res.send(htmlStr);
} else {
res.status(context.status).send("error code:" + context.status);
}
Рендеринг на стороне сервераcontext.status
,не существуетstatus
Атрибут указывает, что маршрут совпал, если он существует, установить код состояния и отреагировать на результат.
App.jsx
Маршрут перенаправления используется в<Redirect from="/" to="/bar" exact />
,доступhttp://localhost:3000
перенаправит наhttp://localhost:3000/bar
, пока вStaticRouter
Маршрут в середине не имеет состояния и не может быть перенаправлен.http://localhost:3000
Сервер возвращаетсяApp.jsx
Фрагмент html, отображаемый в формате , за исключениемBar.jsx
что делает компонент
Bar.jsx
изrender
Методы, как показано ниже
render() {
return (
<div>
<div>Bar</div>
</div>
);
}
Из-за роутинга клиента адресная строка браузера сталаhttp://localhost:3000/bar
, и выводитBar.jsx
, но рендеринг на стороне клиента и на стороне сервера несовместимы
существуетserver.jsx
добавить строку кода вconsole.log(context)
let context = {};
let component = createApp(context, req.url);
let html = ReactDOMServer.renderToString(component);
console.log(context);
...
затем посетитеhttp://loclahost:3000
, вы можете увидеть следующую информацию о выводе в терминале
======enter server======
visit url: /
{ action: 'REPLACE',
location: { pathname: '/bar', search: '', hash: '', state: undefined },
url: '/bar' }
пройти черезcontext
Получатьurl
Выполнение обработки перенаправления на стороне сервера
if (context.url) { // 当发生重定向时,静态路由会设置url
res.redirect(context.url);
return;
}
посетить в это времяhttp://loclahost:3000
, браузер отправляет два запроса, первый запрос/
, второе перенаправление на/bar
Главное управление
Каждая страница имеет соответствующую информацию о заголовке, такую как заголовок, метаданные и ссылка, которые используются здесь.react-helmetПлагин для управления головой, он также поддерживает рендеринг на стороне сервера
Установить первымreact-helmet
npm install react-helmet
затем вApp.jsx
серединаimport
, добавить пользовательский заголовок
import { Helmet } from "react-helmet";
<div>
<Helmet>
<title>This is App page</title>
<meta name="keywords" content="React SSR"></meta>
</Helmet>
<div className="title">This is a react ssr demo</div>
...
</div>
При рендеринге на сервере вызовитеReactDOMServer.renderToString()
нужно позвонить послеHelmet.renderStatic()
Чтобы получить информацию, относящуюся к голове, чтобыserver.js
используется вApp.jsx
серединаHelmet
, нужно быть у входаentry-server.js
иApp.jsx
внести некоторые изменения
entry-server.js
const createApp = (context, url) => {
const App = () => {
return (
<StaticRouter context={context} location={url}>
<Root setHead={(head) => App.head = head}/>
</StaticRouter>
)
}
return <App />;
}
App.jsx
class Root extends React.Component {
constructor(props) {
super(props);
if (process.env.REACT_ENV === "server") {
// 当前如果是服务端渲染时将Helmet设置给外层组件的head属性中
this.props.setHead(Helmet);
}
}
...
}
даватьRoot
Компонент передается в функцию реквизитаsetHead
,существуетRoot
Вызывается при инициализации компонентаsetHead
функция к новомуApp
добавить компонентhead
Атрибуты
Изменить шаблонindex.html
,Добавить к<!--react-ssr-head-->
Заполнитель для информации о голове
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel="shortcut icon" href="/public/favicon.ico">
<title>React SSR</title>
<!--react-ssr-head-->
</head>
существуетserver.js
заменить в
if (!context.status) { // 无status字段表示路由匹配成功
// 获取组件内的head对象,必须在组件renderToString后获取
let head = component.type.head.renderStatic();
// 替换注释节点为渲染后的html字符串
let htmlStr = template
.replace(/<title>.*<\/title>/, `${head.title.toString()}`)
.replace("<!--react-ssr-head-->", `${head.meta.toString()}\n${head.link.toString()})`)
.replace("<!--react-ssr-outlet-->", `<div id='app'>${html}</div>`);
// 将渲染后的html字符串发送给客户端
res.send(htmlStr);
} else {
res.status(context.status).send("error code:" + context.status);
}
component
да<App />
Объект, преобразованный синтаксисом jsx,component.type
тип компонента, который получает объект, вотentry-server.js
серединаApp
Примечание: необходимо пройти здесь
App.jsx
серединаimport
ВойдитеHelmet
перечислитьrenderStatic()
информация заголовка
доступhttp://localhost:3000
Когда информация заголовка была отображена
Каждый маршрут соответствует представлению, и каждое представление имеет свою собственную информацию о голове. Компонент представления вложен в корневой компонент. Когда компонент вложен и используется реактивный шлем, та же информация будет автоматически заменена
существуетBar.jsx
,Baz.jsx
,Foo.jsx
иTopList.jsx
Используйте реактивный шлем, чтобы настроить заголовок соответственно. как
class Bar extends React.Component {
render() {
return (
<div>
<Helmet>
<title>Bar</title>
</Helmet>
<div>Bar</div>
</div>
);
}
}
ввод в браузереhttp://localhost:3000/bar
когда заголовок отображается как<title data-react-helmet="true">Bar</title>
входитьhttp://localhost:3000/baz
когда заголовок отображается как<title data-react-helmet="true">Baz</title>
Суммировать
Этот раздел настраивает и управляет базовой маршрутизацией React, что упрощает обслуживание и закладывает основу для последующей предварительной выборки данных. Используется при рендеринге маршрутизации на стороне сервера.StaticRouter
компонент, этот компонент имеетcontext
иlocation
Два реквизита, вы можете дать их сами при рендерингеcontext
Дайте настраиваемые свойства, такие как установка кода состояния,location
используется для сопоставления маршрутов. Информация о голове важна для рендеринга на стороне сервера.Плагин react-helmet обеспечивает простое использование для определения информации о голове и поддерживает как клиент, так и сервер.
Следующий раздел:Разделение кода и предварительная выборка данных