Основные понятия
-
SSR: рендеринг на стороне сервера Традиционный рендеринг на стороне сервера может быть реализован с использованием таких языков разработки, как Java и php.Благодаря постоянному развитию Node.js и связанных с ним технологий внешнего интерфейса студенты, изучающие интерфейс, также могут выполнять независимый рендеринг на стороне сервера на основе этого.
-
Процесс: браузер отправляет запрос -> сервер запускает код реакции для создания страницы -> сервер возвращает страницу -> браузер загружает HTML-документ -> страница готова То есть содержимое текущей страницы генерируется сервером и отправляется в браузер.
- Соответствующий CSR: рендеринг на стороне клиента Процесс: браузер отправляет запрос -> сервер возвращает пустой HTML (HTML содержит корневой узел и файл js) -> браузер загружает файл js -> браузер запускает код реакции -> страница готова То есть: содержимое текущей страницы рендерится с помощью js
-
Как определить, отображается ли страница на стороне сервера: Щелкните правой кнопкой мыши -> показать исходный код веб-страницы, если содержимое страницы находится в HTML-документе, это рендеринг на стороне сервера, в противном случае — рендеринг на стороне клиента.
-
В сравнении
- CSR: время рендеринга первого экрана велико, а код реакции выполняется в браузере, что потребляет производительность браузера.
- SSR: время рендеринга первого экрана короткое, код React выполняется на сервере, потребляет производительность сервера.
Зачем использовать рендеринг на стороне сервера
-
Время загрузки первого экрана оптимизировано, потому что SSR напрямую возвращает HTML, который генерирует контент, в то время как обычный CSR сначала возвращает пустой HTML, а затем браузер динамически загружает сценарий JavaScript и отображает страницу до того, как контент будет завершен; поэтому загружается первый экран SSR. Быстрее, меньше белого экрана, лучший пользовательский интерфейс.
-
SEO (поисковая оптимизация), ранжирование при поиске по ключевым словам для большинства поисковых систем, не распознает содержимое JavaScript, распознает только содержимое HTML. (Примечание: В принципе лучше не использовать рендеринг на стороне сервера, поэтому если есть только требования SEO, можно использовать пререндеринг и другие технологии для замены)
Создание проекта с серверным рендерингом
(1) Используйте Node.js в качестве промежуточного уровня между сервером и клиентом, используйте прокси-сервер, обрабатывайте файлы cookie и другие операции.
(2) Использование гидрата: в случае рендеринга на стороне сервера вместо рендеринга используется гидрат, и его функция в основном состоит в том, чтобы вводить связанные события в HTML-страницу (т. е. позволить данным компонента React передаваться в браузера вместе с HTML-документом) серверная страница), что может обеспечить согласованность данных на стороне сервера с данными на стороне браузера, избежать заставок на экране и сделать первую загрузку более эффективной и плавной.
ReactDom.hydrate(<App />, document.getElementById('root'));
(3) Компиляция кода сервера WebPack: обычно создается файл WebPack.Server.js.В дополнение к обычной конфигурации параметров необходимо установить целевой параметр «Узел».
const serverConfig = {
target: 'node',
entry: './src/server/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '../dist')
},
externals: [nodeExternals()],
module: {
rules: [{
test: /\.js?$/,
loader: 'babel-loader',
exclude: [
path.join(__dirname, './node_modules')
]
}
...
]
}
(此处省略样式打包,代码压缩,运行坏境配置等等...)
...
};
(4) Используйте метод renderToString в react-dom/server для преобразования различных сложных компонентов и кодов в строки HTML на сервере и возврата их в браузер, а также для отправки тегов при первоначальном запросе, чтобы ускорить загрузку страницы и разрешить сканирование поисковых систем. страницы для целей SEO.
const render = (store, routes, req, context) => {
const content = renderToString((
<Provider store={store}>
<StaticRouter location={req.path} context={context}>
<div>
{renderRoutes(routes)}
</div>
</StaticRouter>
</Provider>
));
return `
<html>
<head>
<title>ssr</title>
</head>
<body>
<div id='root'>${content}</div>
<script src='/index.js'></script>
</body>
</html>
`;
}
app.get('*', function (req, res) {
...
const html = render(store, routes, req, context);
res.send(html);
});
Аналогичные функции для renderToString: I. renderToStaticMarkup: разница в том, что renderToStaticMarkup отображает чистый HTML без data-reactid.После загрузки JavaScript он повторно отображается, потому что он не распознает содержимое, отображаемое сервером ранее (возможно, страница начнет мигать).
ii. renderToNodeStream: визуализирует элемент React в исходный HTML, возвращая читаемый поток выходных строк HTML.
3. renderToStaticNodeStream: аналогично renderToNodeStream, за исключением того, что не создает дополнительных свойств DOM, которые React использует внутри, например, data-reactroot.
(5) Используйте redux для выполнения обязанностей по подготовке данных и поддержанию состояния, обычно с react-redux, redux-thunk (промежуточное ПО: отправка асинхронных запросов в действие). (В настоящее время эта обезьяна в основном использует Redux и Mobx, а Redux используется здесь в качестве примера). A. Создайте хранилище (серверу необходимо создать его один раз для каждого запроса, а клиент создает его только один раз):
const reducer = combineReducers({
home: homeReducer,
page1: page1Reducer,
page2: page2Reducer
});
export const getStore = (req) => {
return createStore(reducer, applyMiddleware(thunk.withExtraArgument(serverAxios(req))));
}
export const getClientStore = () => {
return createStore(reducer, window.STATE_FROM_SERVER, applyMiddleware(thunk.withExtraArgument(clientAxios)));
}
B. действие: отвечает за передачу данных из приложения в магазин и является единственным источником данных магазина.
export const getData = () => {
return (dispatch, getState, axiosInstance) => {
return axiosInstance.get('interfaceUrl/xxx')
.then((res) => {
dispatch({
type: 'HOME_LIST',
list: res.list
})
});
}
}
C. редуктор: получает старое состояние и действия, возвращает новое состояние, отвечает на действия и отправляет их в хранилище.
export default (state = { list: [] }, action) => {
switch(action.type) {
case 'HOME_LIST':
return {
...state,
list: action.list
}
default:
return state;
}
}
export default (state = { list: [] }, action) => {
switch(action.type) {
case 'HOME_LIST':
return {
...state,
list: action.list
}
default:
return state;
}
}
D. Используя соединение react-redux, поставщик подключает компонент к хранилищу.
- Провайдер передает ранее созданный магазин в качестве реквизита Провайдеру
const content = renderToString((
<Provider store={store}>
<StaticRouter location={req.path} context={context}>
<div>
{renderRoutes(routes)}
</div>
</StaticRouter>
</Provider>
));
- connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options]) получает четыре параметра Первые два свойства обычно используются Функция mapStateToProps позволяет нам привязать данные в хранилище в качестве реквизита к компоненту. mapDispatchToProps привязывает действие как реквизит к компоненту.
connect(mapStateToProps(),mapDispatchToProps())(MyComponent)
(6) Используйте react-router, чтобы взять на себя ответственность за маршрутизацию В отличие от маршрутизации на стороне клиента, маршрутизация на стороне сервера не имеет состояния. React предоставляет компонент без сохранения состояния StaticRouter, передает текущий URL-адрес StaticRouter и вызывает ReactDOMServer.renderToString() для соответствия представлению маршрута.
Сервер
import { StaticRouter } from 'react-router-dom';
import { renderRoutes } from 'react-router-config'
import routes from './router.js'
<StaticRouter location={req.path} context={{context}}>
{renderRoutes(routes)}
</StaticRouter>
сторона браузера
import { BrowserRouter } from 'react-router-dom';
import { renderRoutes } from 'react-router-config'
import routes from './router.js'
<BrowserRouter>
{renderRoutes(routes)}
</BrowserRouter>
При изменении адресной строки браузера внешний интерфейс будет соответствовать представлению маршрутизации, в то же время из-за изменения req.path сервер будет соответствовать представлению маршрутизации, что поддерживает согласованность внешнего интерфейса и внутренние представления маршрутизации. Когда страница обновляется, все еще возможно Отображать текущее представление в обычном режиме. Если используется только маршрутизация на стороне браузера и используется BrowserRouter, то при обновлении страницы после изменения адреса страницы страница не будет найдена, поскольку нет соответствующего HTML. Верните клиенту полный HTML-код, и страница все равно будет отображаться. как обычно. Рекомендуется использовать подключаемый модуль react-router-config, а затем добавить renderRoutes (маршруты) в дочерние элементы тегов StaticRouter и BrowserRouter, как указано выше: Создайте файл router.js
const routes = [{ component: Root,
routes: [
{ path: '/',
exact: true,
component: Home,
loadData: Home.loadData
},
{ path: '/child/:id',
component: Child,
loadData: Child.loadData
routes: [
path: '/child/:id/grand-child',
component: GrandChild,
loadData: GrandChild.loadData
]
}
]
}];
Когда браузер запрашивает адрес, server.js может использовать matchRouters для определения контента, который должен быть отрисован до фактического рендеринга, вызвать функцию loaderData для отправки действия, вернуть promise->promiseAll->renderToString и, наконец, сгенерировать возврат HTML-документа.
import { matchRoutes } from 'react-router-config'
const loadBranchData = (location) => {
const branch = matchRoutes(routes, location.pathname)
const promises = branch.map(({ route, match }) => {
return route.loadData
? route.loadData(match)
: Promise.resolve(null)
})
return Promise.all(promises)
}
(7) При написании компонентов обратите внимание на изоморфизм кода (т. е. набор кода React выполняется один раз на стороне сервера и один раз на стороне клиента). Поскольку событие привязки на стороне сервера недопустимо, сервер возвращает только стиль страницы (и данные, заполненные водой) и возвращает файл JavaScript. Событие может быть связано только тогда, когда JavaScript загружается и выполняется в браузере, и мы надеюсь, что этот процесс только вам нужно написать код один раз, в это время будет использоваться изоморфизм, сервер будет отображать стиль, и событие будет привязано, когда клиент его выполнит.
Преимущества: общий интерфейсный код, экономия времени разработки. Недостатки: из-за разницы между серверной и браузерной средами это может привести к некоторым проблемам, таким как невозможность найти документ и другие объекты, ошибка расчета DOM, внешний рендеринг и рендеринг содержимого на стороне сервера несовместимы и т. д. .; внешний интерфейс может выполнять очень сложное слияние запросов и отложенную обработку, но для изоморфизма все эти запросы рендерятся только при предварительной выборке результата.
об авторе
Дора, главный инженер-разработчик Tongbanjie, присоединилась к команде в августе 2015 года и в настоящее время в основном отвечает за разработку проектов на стороне приложений на стороне эксплуатации.
В этой статье в основном описывается, как реализовать SSR на основе React.js и Node.js в конкретных случаях. Чтобы получить больше информации, связанной с React, отсканируйте код, чтобы подписаться на общедоступную учетную запись WeChat «Tongbanjie Technology», и ответьте «React» в фон или «реагировать-нативный» для большего количества отличного контента.