С появлением все новых и новых интерфейсных фреймворков концепция SSR становится все более популярной в сфере фронтенд-разработки, и все больше и больше проектов реализуют это техническое решение. Какова предыстория образования SSR? Каковы применимые сценарии? Каков принцип реализации? Надеюсь, вы сможете найти ответ, который вы ищете в этой статье.
Когда дело доходит до SSR, первая реакция многих людей — «рендеринг на стороне сервера», но я предпочитаю называть это «изоморфизмом», поэтому сначала давайте поговорим о «рендеринге на стороне клиента», «рендеринге на стороне сервера», «изоморфизме». «Эти три концепции просто анализируются:
рендеринг на стороне клиента: рендеринг на стороне клиента. На изначально загруженной HTML-странице нет отображаемого содержимого веб-страницы. Код React в файле JavaScript должен быть загружен и выполнен, а страница создается с помощью рендеринга JavaScript. В то же время JavaScript код завершит привязку событий взаимодействия со страницей. Подробнее см. Рисунок ниже (рисунок взят изfullstackacademy.com):
Рендеринг на стороне сервера: пользователь запрашивает сервер, и HTML-контент создается непосредственно на сервере и возвращается в браузер. Рендеринг на стороне сервера, содержимое страницы генерируется на стороне сервера. Вообще говоря, возможности взаимодействия со страницей при рендеринге на стороне сервера ограничены.Если вы хотите добиться сложного взаимодействия, вам все равно нужно ввести файлы JavaScript, чтобы помочь в реализации. Концепция рендеринга на стороне сервера применима к любому внутреннему языку.
изоморфизм: Концепция изоморфизма существует в новых интерфейсных фреймворках, таких как Vue и React.Изоморфизм на самом деле представляет собой интеграцию рендеринга на стороне клиента и рендеринга на стороне сервера. Мы вместе пишем презентацию и взаимодействие со страницей и позволяем коду выполняться дважды. Выполните один раз на стороне сервера, чтобы добиться рендеринга на стороне сервера, и снова выполните его на стороне клиента, чтобы взять на себя взаимодействие со страницей.Подробный процесс может относиться к следующему рисунку (изображение взято изfullstackacademy.com):
В обычных обстоятельствах, когда мы используем React для написания кода, страницы генерируются клиентской стороной, выполняющей логику JavaScript для динамического подвешивания DOM, что означает, что это обычное одностраничное приложение фактически использует режим рендеринга на стороне клиента. В большинстве случаев рендеринг на стороне клиента может полностью удовлетворить потребности нашего бизнеса, так зачем нам такая изоморфная технология, как SSR?
Ключевые факторы для использования технологии SSR:
-
Время TTFP (время до первой страницы) проекта CSR относительно велико.Ссылаясь на предыдущую легенду, в процессе рендеринга страницы CSR сначала должен быть загружен файл HTML, затем должен быть загружен файл JavaScript, требуемый страницей. , а затем файл JavaScript должен быть обработан для создания страницы. В этом процессе рендеринга участвует как минимум два цикла HTTP-запроса, поэтому он займет определенное время, поэтому при доступе к обычным приложениям React или Vue на низких скоростях сети начальная страница будет иметь белый экран.
-
SEO-возможности CSR-проектов крайне слабы, и в принципе невозможно иметь хороший рейтинг в поисковых системах. Поскольку большинство поисковых систем в настоящее время в основном идентифицируют содержимое HTML, идентификация содержимого файлов JavaScript все еще относительно слаба. Если трафик на проект поступает из поисковых систем, в настоящее время вам крайне неуместно использовать CSR для разработки.
Генерация SSR в основном предназначена для решения двух проблем, упомянутых выше. Используя технологию SSR в React, мы позволяем коду React выполняться один раз на стороне сервера, так что HTML, загруженный пользователем, уже содержит весь контент отображения страницы Таким образом, процесс отображения страницы должен пройти только один HTTP цикла запроса, а время TTFP сокращается более чем в два раза.
В то же время, поскольку все содержимое веб-страницы уже включено в HTML, SEO-эффект веб-страницы также станет очень хорошим. После этого мы позволяем коду React снова выполняться на стороне клиента, добавляем привязки данных и событий к содержимому на HTML-странице, и на странице появляются различные интерактивные возможности React.
Однако реализовать концепцию SSR непросто. Давайте взглянем на архитектурную схему реализации технологии SSR в React:
Использование технологии SSR сделает изначально простой проект React очень сложным, ремонтопригодность проекта снизится, а отслеживание проблем в коде станет затруднительным.
Таким образом, использование SSR для решения проблемы также принесет много побочных эффектов, иногда вред от которых намного больше, чем преимущества, которые дает технология SSR. Исходя из личного опыта, я обычно рекомендую вам не использовать SSR, если только ваш проект не сильно зависит от трафика поисковых систем или не имеет особых требований к времени в верхней части страницы.
Что ж, если вы столкнетесь со сценарием, в котором вы хотите использовать SSR в проекте React, и решите использовать SSR, тогда мы объединим приведенную выше диаграмму архитектуры SSR, чтобы проанализировать трудности технических моментов SSR.
Прежде чем мы начнем, давайте проанализируем взаимосвязь между виртуальным DOM и SSR.
Причина, по которой SSR может быть реализована, в основном из-за существования виртуального DOM.
Как мы сказали выше, в проекте SSR код React будет выполняться один раз на стороне клиента и один раз на стороне сервера. Вы можете подумать, что все в порядке, это весь код JavaScript, который работает как в браузере, так и в Node. Но это не так.Если в вашем React-коде есть код, который напрямую манипулирует DOM, то технологию SSR реализовать невозможно, потому что в среде Node нет понятия DOM, поэтому эти коды находятся в Node. среда сообщит об ошибке.
К счастью, фреймворк React представил концепцию, называемую виртуальным DOM. Виртуальный DOM — это объект JavaScript, отображающий реальный DOM. JavaScript.объект, который делает возможным SSR. На сервере я могу манипулировать объектами JavaScript, и, полагая, что среда — это среда сервера, мы сопоставляем виртуальный DOM со строковым выводом; на стороне клиента я также могу манипулировать объектами JavaScript и полагая, что среда — это среда клиента , я напрямую сопоставляю виртуальный DOM с реальным DOM для завершения монтирования страницы.
Другие фреймворки, такие как Vue, могут реализовать SSR благодаря внедрению той же технологии виртуального DOM, что и в React.
Хорошо, давайте вернемся и посмотрим на блок-схему. Не будем говорить о первых двух шагах. Рендеринг на стороне сервера должен сначала отправить запрос на сервер Node. Основное внимание уделяется шагу 3. Как видите, серверу необходимо определить, какую страницу отображать на основе запрошенного адреса.Этот шаг называется маршрутизацией на стороне сервера.
Мы смотрим на первый шаг 10, когда клиент получает файл JavaScript в соответствии с текущим путем в браузере, а затем определяем компоненты, которые в данный момент хотят показать, еще раз отрисовывая клиент, на этот раз, но также через клиент маршрутизация (начальный конец маршрута).
Итак, далее мы хотим поговорить о разнице между маршрутизацией на стороне сервера и маршрутизацией на стороне клиента.
Различия между рендерингом на стороне клиента и кодом маршрутизации рендеринга на стороне сервера в SSR
Чтобы реализовать архитектуру React SSR, нам нужно, чтобы один и тот же код React выполнялся один раз на стороне клиента и один раз на стороне сервера. Обратите внимание, что один и тот же код React, упомянутый здесь, относится к различным кодам компонентов, которые мы написали, поэтому в изоморфизме может использоваться только код компонента, а такой код, как маршрутизация, не может быть общим, все думают Далее, почему это? На самом деле причина очень проста: на стороне сервера нужно найти компонент маршрутизации через путь запроса, а на стороне клиента нужно найти компонент маршрутизации через URL в браузере, а это два совершенно различные механизмы, поэтому эта часть кода точно не является публичной. Давайте взглянем на код реализации внешней и внутренней маршрутизации в SSR:
Маршрутизация клиента:
const App = () => {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<Route path='/' component={Home}>
</div>
</BrowserRouter>
</Provider>
)
}
ReactDom.render(<App/>, document.querySelector('#root'))
Код маршрутизации на стороне клиента очень прост, и каждый должен с ним ознакомиться, BrowserRouter автоматически отобразит соответствующие компоненты маршрутизации из адреса браузера.
Код маршрутизации на стороне сервера:
const App = () => {
return
<Provider store={store}>
<StaticRouter location={req.path} context={context}>
<div>
<Route path='/' component={Home}>
</div>
</StaticRouter>
</Provider>
}
Return ReactDom.renderToString(<App/>)
Код маршрутизации на стороне сервера относительно сложен, и вам необходимо передать местоположение (текущий путь запроса) компоненту StaticRouter, чтобы StaticRouter мог проанализировать, кто является требуемым в данный момент компонентом на основе пути. (PS: StaticRouter — это компонент маршрутизации, предоставляемый React-Router для рендеринга на стороне сервера.)
Через BrowserRouter мы можем сопоставить компоненты маршрутизации, которые браузер собирается отобразить.Для браузера нам нужно преобразовать компоненты в DOM, поэтому нам нужно использовать метод ReactDom.render для монтирования DOM. StaticRouter может сопоставить компонент, который будет отображаться на стороне сервера.Для серверной стороны нам нужно преобразовать компонент в строку.На данный момент нам нужно только вызвать метод renderToString, предоставленный ReactDom, чтобы получить строку HTML, соответствующую к компоненту приложения.
Для приложения React маршрутизация обычно является точкой входа для выполнения всей программы. В SSR маршрутизация на стороне сервера отличается от маршрутизации на стороне клиента, что означает, что код входа на стороне сервера и код входа на стороне клиента различны.
Мы знаем, что код React можно запустить только после того, как он будет упакован Webpack, то есть код, запускаемый на шагах 3 и 10, на самом деле является кодом, сгенерированным после упаковки исходного кода. Как упоминалось выше, только часть кода при рендеринге на стороне сервера и на стороне клиента одинакова, а остальные отличаются. Следовательно, в зависимости от среды выполнения кода требуется другая упаковка Webpack.
Различия в упаковке между кодом на стороне сервера и кодом на стороне клиента
Просто напишите два файла конфигурации Webpack как DEMO:
Конфигурация клиентского веб-пакета:
{
entry: './src/client/index.js',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'public')
},
module: {
rules: [{
test: /\.js?$/,
loader: 'babel-loader'
},{
test: /\.css?$/,
use: ['style-loader', {
loader: 'css-loader',
options: {modules: true}
}]
},{
test: /\.(png|jpeg|jpg|gif|svg)?$/,
loader: 'url-loader',
options: {
limit: 8000,
publicPath: '/'
}
}]
}
}
Конфигурация веб-пакета на стороне сервера:
{
target: 'node',
entry: './src/server/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build')
},
externals: [nodeExternals()],
module: {
rules: [{
test: /\.js?$/,
loader: 'babel-loader'
},{
test: /\.css?$/,
use: ['isomorphic-style-loader', {
loader: 'css-loader',
options: {modules: true}
}]
},{
test: /\.(png|jpeg|jpg|gif|svg)?$/,
loader: 'url-loader',
options: {
limit: 8000,
outputPath: '../public/',
publicPath: '/'
}
}]
}
};
Как мы уже говорили выше, в SSR код маршрутизации входа для кода рендеринга на стороне сервера и кода на стороне клиента отличается, поэтому в Webpack конфигурация Entry должна отличаться в первую очередь.
Для кода, работающего на стороне сервера, иногда нам нужно ввести некоторые основные модули в Node. Нам нужен Webpack, чтобы идентифицировать похожие основные модули при упаковке. После обнаружения основных модулей нет необходимости объединять код модуля в финальный сгенерированный код., решение этой проблемы очень простое.В конфигурации Webpack на стороне сервера нужно только добавить конфигурацию target:node.
Для кода рендеринга на стороне сервера, если вы загружаете сторонние модули, эти сторонние модули не нужно упаковывать в окончательный исходный код, поскольку эти пакеты были установлены через NPM в среде Node, и вы можете напрямую ссылаться на них. их без дополнительной упаковки в код. Чтобы решить эту проблему, мы можем использовать плагин webpack-node-externals, nodeExternals в коде относится к этому плагину, через этот плагин мы можем решить эту проблему. Что касается проблемы упаковки здесь, в Node, она может показаться немного абстрактной.Студенты, которые не очень хорошо понимают, могут внимательно прочитать соответствующие статьи или документы webpack-node-externals, и вы сможете понять проблемы здесь.
Далее мы продолжаем анализировать, когда в наш код React вводится некоторый код стиля CSS, процесс упаковки на стороне сервера обработает CSS один раз, а клиент обработает его снова. Глядя на конфигурацию, мы видим, что мы использовали isomorphic-style-loader для упаковки на стороне сервера.Когда он обрабатывает CSS, он только генерирует имя класса для соответствующего элемента DOM, а затем возвращает сгенерированный код стиля CSS.
В конфигурации упаковки кода на стороне клиента мы используем css-loader и style-loader.css-loader не только сгенерирует имя класса и проанализированный код CSS в DOM, но и смонтирует код в DOM через style- загрузчик на странице. Однако при этом, поскольку стили на странице фактически добавляются при рендеринге клиента, страница может не иметь стилей в начале.Чтобы решить эту проблему, мы можем получить изоморфность при рендеринге на сервере.Код стиля возвращаемый -style-loader, затем добавляется к отображаемому на стороне сервера HTML в виде строки.
Для добавления таких файлов, как изображения, url-loader также упаковывает код на стороне сервера и код на стороне клиента соответственно. Файлы, созданные в результате упаковки, хранятся в общедоступном каталоге. Таким образом, хотя файлы будут упакованы дважды, более поздние упакованные файлы будут перезаписывать предыдущие файлы, поэтому кажется, что есть только один файл.
Конечно, производительность и элегантность при этом не высоки, просто чтобы дать вам небольшую идею.Если вы хотите оптимизировать, вы можете сделать упаковку изображения только один раз.С помощью некоторых плагинов Webpack, добиться этого несложно, можно даже самому написать загрузчик для решения такой задачи.
Если в вашем приложении React нет асинхронного сбора данных, и вы просто выполняете некоторое статическое отображение контента, после приведенной выше настройки вы обнаружите, что простое приложение SSR можно реализовать быстро. Однако в реальном проекте React у нас должен быть асинхронный сбор данных, и в большинстве случаев мы также используем Redux для управления данными. Но если вы хотите реализовать это в приложениях SSR, это не так просто.
Сбор асинхронных данных в SSR + использование Redux
При рендеринге на стороне клиента использование асинхронных данных в сочетании с Redux следует следующему процессу (соответствует шагу 12 на рисунке):
- Создать магазин
- Отображение компонентов на основе маршрутов
- Действие Dispatch для получения данных
- Обновить данные в магазине
- Средство визуализации компонентов
На стороне сервера, как только содержимое страницы определено, нет возможности отобразить его.Это требует, чтобы данные Магазина были подготовлены при отображении компонента.Поэтому асинхронные данные на стороне сервера объединяются с использованием Redux Процесс выглядит следующим образом (соответствует шагу 4 на рисунке):
- Создать магазин
- Анализировать данные, необходимые в Магазине по маршруту
- Действие Dispatch для получения данных
- Обновите данные в Магазине
- Объединяйте данные и компоненты для создания HTML и возвращайте его одновременно
Ниже мы анализируемРендеринг на стороне сервераЭта часть процесса:
- Создание магазина: в этой части есть подводные камни, и вам следует обратить внимание на то, чтобы их избежать.Как мы все знаем, при рендеринге на стороне клиента всегда есть только один магазин в браузере пользователя, поэтому вы можете написать такой код:
const store = createStore(reducer, defaultState)
export default store;
Однако на стороне сервера есть проблема с этим написанием, потому что Магазин на стороне сервера используется всеми пользователями.Если Магазин построен так, как описано выше, Магазин становится синглтоном, и все пользователи разделяют Магазин, очевидно есть проблема. Таким образом, при рендеринге на стороне сервера создание Store должно выглядеть следующим образом, возвращая функцию, которая повторно выполняется при доступе каждого пользователя, предоставляя каждому пользователю отдельный Store:
const getStore = (req) => {
return createStore(reducer, defaultState);
}
export default getStore;
-
Проанализируйте данные, необходимые в Магазине, в соответствии с маршрутом: для реализации этого шага на стороне сервера мы должны сначала проанализировать все компоненты, которые будут загружены текущим исходящим маршрутом, В это время мы можем использовать некоторые сторонние пакеты , такие как react-router-config , Как использовать этот пакет не будет объясняться слишком много, вы можете проверить документацию, использовать этот пакет, передать путь запроса сервера, это поможет вам проанализировать все компоненты, которые будут отображаться под этот путь.
-
Диспетчерское действие для получения данных. Затем мы добавляем метод для получения данных о каждом компоненте:
Home.loadData = (store) => {
return store.dispatch(getHomeList())
}
Этот метод требует, чтобы вы прошли в хранилище, отображаемое на сервере, и его роль заключается в том, чтобы помочь серверному хранилищу получить данные, требуемые компонентом. Следовательно, на компоненте есть такой метод, и в то же время у нас также есть все компоненты, требуемые текущей маршрутизацией, и, вызывая метод loadData на каждом компоненте по очереди, мы можем получить весь контент данных, требуемый маршрутизация.
- Обновление данных в Магазине: По сути, когда мы выполняем третий шаг, мы уже обновляем данные в Магазине, но нам нужно убедиться, что все данные получены перед генерацией HTML Как с этим быть?
// matchedRoutes 是当前路由对应的所有需要显示的组件集合
matchedRoutes.forEach(item => {
if (item.route.loadData) {
const promise = new Promise((resolve, reject) => {
item.route.loadData(store).then(resolve).catch(resolve);
})
promises.push(promise);
}
})
Promise.all(promises).then(() => {
// 生成 HTML 逻辑
})
Здесь мы используем промисы для решения этой проблемы.Мы создаем очередь промисов и ждем выполнения всех промисов, то есть после выполнения всех store.dispatch, а затем генерируем HTML. Таким образом, мы реализовали процесс SSR в сочетании с Redux.
Выше мы говорили, что при серверной отрисовке данные страницы получаются через функцию loadData. На стороне клиента сбор данных по-прежнему необходимо выполнять, потому что, если эта страница является первой страницей, которую вы посещаете, то контент, который вы видите, отображается сервером, но если вы пройдете маршрут react-router и перейдете на второй одна страница, то эта страница полностью обрабатывается клиентом, поэтому клиент также должен получить данные.
Для получения данных на стороне клиента используем наиболее привычный способ получения данных через componentDidMount. Здесь следует отметить, что componentDidMount будет выполняться только на стороне клиента, и эта функция жизненного цикла не будет выполняться на стороне сервера. Так что нам не нужно беспокоиться о конфликте между componentDidMount и loadData, просто используйте его с уверенностью. Это также причина, по которой сбор данных должен быть помещен в функцию жизненного цикла componentDidMount вместо componentWillMount, что может избежать конфликта между получением данных на стороне сервера и получением данных на стороне клиента.
Узел — это всего лишь средний слой
В предыдущей части мы говорили о проблеме получения данных.В архитектуре SSR Node обычно является просто промежуточным слоем для серверного рендеринга React-кода, а данные, требуемые Node, обычно предоставляет только API-сервер.
Это делается с целью инженерной развязки, а во-вторых, чтобы избежать некоторых проблем с вычислительной производительностью сервера Node.
Пожалуйста, обратите внимание на шаг 4 и шаги 12, 13 на рисунке, мы проанализируем эти шаги далее.
При рендеринге на стороне сервера нет проблем с прямым запросом интерфейса API-сервера для получения данных. Однако на стороне клиента могут быть междоменные проблемы, поэтому в данный момент нам необходимо построить прокси-функцию на стороне сервера.Клиент не запрашивает напрямую API-сервер, а запрашивает Node-сервер, который пересылается прокси и получает данные для сервера API.
Здесь вы можете использовать такие инструменты, как express-http-proxy, чтобы помочь вам быстро создать функцию прокси, но не забудьте разрешить прокси-серверу не только пересылать запросы для вас, но и хранить файлы cookie, чтобы у вас не было разрешений. проблемы.
// Node 代理功能实现代码
app.use('/api', proxy('http://apiServer.com', {
proxyReqPathResolver: function (req) {
return '/ssr' + req.url;
}
}));
Суммировать:
На этом этапе принципы ключевых точек знаний во всей системе процессов SSR связаны последовательно.Если вы уже применяли структуру SSR раньше, я считаю, что расположение этих точек знаний может помочь вам на уровне принципов.
Конечно, я также посчитал, что большая часть студентов, прочитавших эту статью, могут иметь очень ограниченные базовые знания о SSR.После прочтения статьи они могут запутаться.Чтобы помочь этим студентам, я написал очень простой SSR-фреймворк. ., здесь размещается код:
files.Ali CDN.com/TPS-сервис/…
Комбинируя вышеприведенную блок-схему, новички шаг за шагом разбирают логику в блок-схеме.После разбора вернитесь и прочитайте эту статью еще раз, я думаю, все будут просветлены.
Конечно, в процессе собственно реализации архитектуры SSR сложность иногда составляет не идея реализации, а обработка деталей. Например, как установить разные заголовки и описания для разных страниц, чтобы улучшить эффект SEO. В настоящее время мы можем использовать такие инструменты, как реактивный шлем, чтобы помочь нам в достижении наших целей. Этот инструмент оказывает большое влияние на клиентскую и серверную части. -боковой рендеринг.Рекомендуется. Есть также некоторые, такие как дизайн каталога проекта, обработка ситуаций перенаправления 404, 301 и т. д., но эти проблемы нам нужно решать только одну за другой, когда мы сталкиваемся с ними на практике.
Что ж, все, что рассказывается о SSR, здесь, я надеюсь, что эта статья поможет вам более или менее.
Справочная документация
- Официальный сайт вебпака
- What is React Server Side Rendering and should I use it?
- StaticRouter
- The Pain and the Joy of creating isomorphic apps in ReactJS
Статья может быть воспроизведена по желанию, но просьба сохранитьОригинальная ссылка. Добро пожаловать!ES2049 Studio, отправьте свое резюме на caijun.hcj(at)alibaba-inc.com.