⚛️React — одна из самых популярных UI-библиотек в сообществе разработчиков интерфейсов. Его метод разработки на основе компонентов значительно улучшает процесс разработки интерфейсов. React делает наш код, разбивая большое приложение на мелкие компоненты. Более повторное использование и лучшее качество. ремонтопригодность и т.д. и много других преимуществ...
Версия части IIпортал
С помощью React мы обычно разрабатываем одностраничное приложение (SPA). Одностраничное приложение будет иметь лучший пользовательский интерфейс, чем традиционные веб-страницы на стороне браузера. Браузер обычно получает html с пустым телом, а затем загружает сценарий Указанный js, когда все js загружены, начинают выполнять js и, наконец, отображают его в dom. Во время этого процесса обычный пользователь может только ждать и ничего не может делать. Если пользователь находится в высокоскоростная сеть, высокая конфигурация В устройстве описанный выше процесс загрузки всех js и последующего выполнения может не быть большой проблемой, но во многих случаях скорость нашей сети средняя, а устройство может быть не самым лучшим . В этом случае одностраничное приложение может быть неудобным для пользователя. Пользователь мог покинуть веб-сайт, прежде чем воспользоваться преимуществами SPA на стороне браузера. В этом случае на вашем веб-сайте не будет слишком много просмотров страниц независимо от того, насколько хорошо это сделано.
Но мы не можем вернуться к традиционной разработке одной страницы за одной.Современные библиотеки пользовательского интерфейса предоставляют функцию рендеринга на стороне сервера (SSR), так что разрабатываемые нами SPA-приложения также могут отлично работать на стороне сервера, значительно ускорение Уменьшено время рендеринга первого экрана, чтобы пользователи могли быстрее увидеть содержимое веб-страницы.При этом браузер одновременно загружает необходимые js.После загрузки добавляются все события DOM и различные взаимодействия на страницу. , и, наконец, запустить в виде SPA. Таким образом, мы можем не только улучшить время рендеринга первого экрана, но и получить клиентский пользовательский опыт SPA, что также является необходимой функцией для SEO 😀.
Хорошо, у нас есть общее понимание необходимости SSR. Далее мы можем реализовать функцию рендеринга на стороне сервера в приложении React. Кстати, поскольку мы уже находимся в среде, где асинхронность/ожидание повсюду, здесь мы находимся на на стороне сервера.koa2для реализации нашего рендеринга на стороне сервера.
Инициализировать обычное одностраничное приложение SPA
Прежде всего, давайте проигнорируем рендеринг на стороне сервера.Сначала мы создадим SPA на основе React и React-Router.После того, как мы создали полный SPA, мы добавим функцию SSR, чтобы максимизировать производительность приложения.
Сначала войдите в приложениеApp.js
:
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route } from 'react-router-dom';
const Home = () => <div>Home</div>;
const Hello = () => <div>Hello</div>;
const App = () => {
return (
<Router>
<Route exact path="/" component={Home} />
<Route exact path="/hello" component={Hello} />
</Router>
)
}
ReactDOM.render(<App/>, document.getElementById('app'))
Выше мы маршрутизируем/
а также/hello
Создал 2 компонента, которые просто отображают текст на странице. Но когда наш проект становится все больше и больше и компонентов становится все больше и больше, упакованные нами js могут стать очень большими и даже неуправляемыми, поэтому первый шаг, который нам нужно оптимизировать, это разделение кода (code-splitting), к счастью черезwebpack dynamic importа такжеreact-loadable, мы можем легко сделать это.
Разделение временного кода с помощью React-Loadable
Установите перед использованиемreact-loadable
:
npm install react-loadable
# or
yarn add react-loadable
Затем в вашем javascript:
//...
import Loadable from 'react-loadable';
//...
const AsyncHello = Loadable({
loading: <div>loading...</div>,
//把你的Hello组件写到单独的文件中
//然后使用webpack的 dynamic import
loader: () => import('./Hello'),
})
//然后在你的路由中使用loadable包装过的组件:
<Route exact path="/hello" component={AsyncHello} />
Это очень просто, нам просто нужно импортироватьreact-loadable
, а затем передать некоторые параметры, в том числеloading
Вариант состоит в том, чтобы отображать загрузочный компонент при динамической загрузке js, требуемого компонентом Hello, давая пользователю ощущение загрузки, и опыт будет лучше, чем ничего.
Что ж, теперь, если мы посетим домашнюю страницу, будут загружены только js, от которых зависит компонент Home, а затем щелкните ссылку, чтобы войти на страницу приветствия, компонент загрузки будет отображаться первым, а js, от которого зависит компонент приветствия on будет загружаться асинхронно в то же время. , вместо загрузки для рендеринга компонента приветствия. Благодаря разделению кода на разные блоки кода в зависимости от маршрутов наш SPA был значительно оптимизирован, ура🍻. Что ещеreact-loadable
Также поддерживает SSR, так что вы можете использовать его где угодноreact-loadable
, независимо от того, работает ли он на внешнем интерфейсе или на стороне сервера, пустьreact-loadable
Если сервер работает нормально, нам нужно сделать дополнительную дополнительную конфигурацию, которая будет обсуждаться позже в этой статье, поэтому не волнуйтесь.
Здесь мы создали базовый реагировать SPA, плюс Split Code Split, наше приложение имеет хорошую производительность, но мы также можем оптимизировать производительность приложения, ниже, и мы дополнительно повысим функцию скорости нагрузки SSR, для решения проблемы SEO в спа.
Добавлена функция рендеринга на стороне сервера (SSR).
Во-первых, давайте создадим простейший веб-сервер koa:
npm install koa koa-router
Затем во входном файле koaapp.js
середина:
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
router.get('*', async (ctx) => {
ctx.body = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React SSR</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="/bundle.js"></script>
</body>
</html>
`;
});
app.use(router.routes());
app.listen(3000, '0.0.0.0');
над*
Маршрут представляет любой входящий URL-адрес. Мы отображаем этот HTML-код по умолчанию, включая JS, упакованный в HTML-код. Вы также можете использовать некоторые механизмы шаблонов на стороне сервера (например:nunjucks) для прямого рендеринга HTML-файла, который передается при упаковке веб-пакета.html-webpack-plugin
Чтобы автоматически вставить путь к упакованному ресурсу js/css.
Итак, наш простой коа-сервер готов, теперь мы начинаем писать входной файл React SSR.AppSSR.js
, здесь нам нужно использоватьStaticRouter
на замену предыдущемуBrowserRouter
, так как на стороне сервера роутинг статический, и использовать BrowserRouter не получится, а некоторые настройки будут сделаны позже, чтобы сделатьreact-loadable
запустить на сервере.
Совет: Вы можете поместить весь код на стороне узла, написанный в стиле ES6/JSX, а не часть commonjs, часть JSX, но тогда вам нужно использовать webpack, весь код на стороне сервера скомпилирован в стиле commonjs, чтобы сделать он запускается в среде узла, здесь у нас есть код React SSR для извлечения в одиночку, а затем требуется, чтобы он входил в обычные коды узла. Поскольку в существующем проекте может быть до всего стиля commonjs, предыдущий код узла является единовременной стоимостью, а затем превращается в ES6 немного большим, но последний затем может шаг за шагом мигрировать в прошлое.
Хорошо, теперь в вашемAppSSR.js
середина:
import React from 'react';
//使用静态 static router
import { StaticRouter } from 'react-router-dom';
import ReactDOMServer from 'react-dom/server';
import Loadable from 'react-loadable';
//下面这个是需要让react-loadable在服务端可运行需要的,下面会讲到
import { getBundles } from 'react-loadable/webpack';
import stats from '../build/react-loadable.json';
//这里吧react-router的路由设置抽出去,使得在浏览器跟服务端可以共用
//下面也会讲到...
import AppRoutes from 'src/AppRoutes';
//这里我们创建一个简单的class,暴露一些方法出去,然后在koa路由里去调用来实现服务端渲染
class SSR {
//koa 路由里会调用这个方法
render(url, data) {
let modules = [];
const context = {};
const html = ReactDOMServer.renderToString(
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<StaticRouter location={url} context={context}>
<AppRoutes initialData={data} />
</StaticRouter>
</Loadable.Capture>
);
//获取服务端已经渲染好的组件数组
let bundles = getBundles(stats, modules);
return {
html,
scripts: this.generateBundleScripts(bundles),
};
}
//把SSR过的组件都转成script标签扔到html里
generateBundleScripts(bundles) {
return bundles.filter(bundle => bundle.file.endsWith('.js')).map(bundle => {
return `<script type="text/javascript" src="${bundle.file}"></script>\n`;
});
}
static preloadAll() {
return Loadable.preloadAll();
}
}
export default SSR;
При компиляции этого файла используйте его в конфигурации веб-пакета.target: "node"
а такжеexternals
, а в конфигурации веб-пакета вашего упакованного внешнего приложения вам необходимо добавить подключаемый модуль react-loadable. Упаковка приложения должна быть запущена до пакета ssr, в противном случае информация о компоненте, требуемая react-loadable, будет быть недоступным. Давайте сначала посмотрим на упаковку приложения. :
//webpack.config.dev.js, app bundle
const ReactLoadablePlugin = require('react-loadable/webpack')
.ReactLoadablePlugin;
module.exports = {
//...
plugins: [
//...
new ReactLoadablePlugin({ filename: './build/react-loadable.json', }),
]
}
существует.babelrc
Добавьте загружаемый плагин в:
{
"plugins": [
"syntax-dynamic-import",
"react-loadable/babel",
["import-inspector", {
"serverSideRequirePath": true
}]
]
}
Приведенная выше конфигурация сделаетreact-loadable
Узнайте, какие компоненты окончательно отображаются на сервере, а затем вставьте их непосредственно в тег html-скрипта и примите во внимание компоненты SSR во время инициализации интерфейса, чтобы избежать повторной загрузки.
//webpack.ssr.js
const nodeExternals = require('webpack-node-externals');
module.exports = {
//...
target: 'node',
output: {
path: 'build/node',
filename: 'ssr.js',
libraryExport: 'default',
libraryTarget: 'commonjs2',
},
//避免把node_modules里的库都打包进去,此ssr js会直接运行在node端,
//所以不需要打包进最终的文件中,运行时会自动从node_modules里加载
externals: [nodeExternals()],
//...
}
потом в коаapp.js
, потребовать его и вызвать метод SSR:
//...koa app.js
//build出来的ssr.js
const SSR = require('./build/node/ssr');
//preload all components on server side, 服务端没有动态加载各个组件,提前先加载好
SSR.preloadAll();
//实例化一个SSR对象
const s = new SSR();
router.get('*', async (ctx) => {
//根据路由,渲染不同的页面组件
const rendered = s.render(ctx.url);
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app">${rendered.html}</div>
<script type="text/javascript" src="/runtime.js"></script>
${rendered.scripts.join()}
<script type="text/javascript" src="/app.js"></script>
</body>
</html>
`;
ctx.body = html;
});
//...
Выше приведена простая реализация React SSR для веб-сервера koa, чтобы сделатьreact-loadable
Знать, какие компоненты рендерятся на стороне сервера,rendered
внутриscripts
Массив содержит различные теги сценария, состоящие из компонентов SSR, которые вызываютSSR#generateBundleScripts()
метод, вам необходимо убедиться, что эти теги скрипта находятся вruntime.js
после ((черезCommonsChunkPlugin
для извлечения )) и перед пакетами приложений (то есть он уже должен знать, какие компоненты были обработаны ранее, когда он инициализируется). Болееreact-loadable
Поддержка сервера, см.здесь.
Выше мы также извлекли маршруты реакции-маршрутизатора отдельно, чтобы он мог работать в браузере и на сервере, следующее:AppRoutes
Компоненты:
//AppRoutes.js
import Loadable from 'react-loadable';
//...
const AsyncHello = Loadable({
loading: <div>loading...</div>,
loader: () => import('./Hello'),
})
function AppRoutes(props) {
<Switch>
<Route exact path="/hello" component={AsyncHello} />
<Route path="/" component={Home} />
</Switch>
}
export default AppRoutes
//然后在 App.js 入口中
import AppRoutes from './AppRoutes';
// ...
export default () => {
return (
<Router>
<AppRoutes/>
</Router>
)
}
Исходное состояние рендеринга на стороне сервера
На данный момент мы создали React SPA, и он может работать как на стороне браузера, так и на стороне сервера 🍺, как это называется в сообществе.universal app
илиisomophic app
. Но наше текущее приложение все еще имеет устаревшую проблему.Вообще говоря, данные или статус нашего приложения должны быть получены асинхронно через удаленный API.После получения данных мы можем начать рендеринг компонента.То же самое верно и для сервера -сторона SSR Нам нужно динамически получить исходные данные, а затем их можно перекинуть в React для SSR, а затем на стороне браузера нам нужно инициализировать, чтобы получить данные инициализации этих SSR синхронно, чтобы избежать повторного получения его при инициализации браузера.
Далее мы просто получаем некоторую информацию о проекте с github в качестве данных для инициализации страницы, в koa'sapp.js
середина:
//...
const fetch = require('isomorphic-fetch');
router.get('*', async (ctx) => {
//fetch branch info from github
const api = 'https://api.github.com/repos/jasonboy/wechat-jssdk/branches';
const data = await fetch(api).then(res => res.json());
//传入初始化数据
const rendered = s.render(ctx.url, data);
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app">${rendered.html}</div>
<script type="text/javascript">window.__INITIAL_DATA__ = ${JSON.stringify(data)}</script>
<script type="text/javascript" src="/runtime.js"></script>
${rendered.scripts.join()}
<script type="text/javascript" src="/app.js"></script>
</body>
</html>
`;
ctx.body = html;
});
тогда в вашемHello
компонент, вам нужно проверитьwindow
Существует ли он в нем (или единообразно оценивается в записи приложения, а затем передается дочернему компоненту через реквизиты)window.__INITIAL_DATA__
, если есть, то будем использовать непосредственно как исходные данные, если нет, то будемcomponentDidMount
Данные приходят и уходят в функции жизненного цикла:
export default class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {
//这里直接判断window,如果是父组件传入的话,通过props判断
github: window.__INITIAL_DATA__ || [],
};
}
componentDidMount() {
//判断没有数据的话,再去请求数据
//请求数据的方法也可以抽出去,以让浏览器及服务端能统一调用,避免重复写
if (this.state.github.length <= 0) {
fetch('https://api.github.com/repos/jasonboy/wechat-jssdk/branches')
.then(res => res.json())
.then(data => {
this.setState({ github: data });
});
}
}
render() {
return (
<div>
<ul>
{this.state.github.map(b => {
return <li key={b.name}>{b.name}</li>;
})}
</ul>
</div>
);
}
}
Что ж, теперь, если страница рендерится сервером, браузер получит весь отрендеренный html, включая данные инициализации, а затем объединит содержимое этих SSR с загруженными js, чтобы сформировать полноценный SPA, точно так же, как обычный SPA — это то же самое, но мы получаем лучшую производительность, лучшее SEO 😎.
🎉Обновление React-v16
В последней версии React V16 API-интерфейс SSR был значительно оптимизирован и предоставляет новый API-интерфейс на основе потоков для повышения производительности.Через API-интерфейс потоковой передачи сервер может отображаться во время рендеринга HTML-кода переднего рендеринга Перейти в браузер , браузер также может начать рендеринг страницы заранее, а не все компоненты сервера, чтобы начать инициализацию в конце браузера, повысить производительность, а также снизить потребление ресурсов сервера. Также необходимо обратить внимание на сторону браузера.ReactDOM.hydrate()
на замену предыдущемуReactDOM.render()
, больше обновлений смотрите в средней статьеwhats-new-with-server-side-rendering-in-react-16.
💖Чтобы увидеть полную демонстрацию, см.koa-web-kit, koa-web-kit
Это современная полнофункциональная среда разработки на основе React/Koa, включая поддержку React SSR, которую можно напрямую использовать для тестирования функций рендеринга на стороне сервера.😀
В заключение
Ну, вышесказанное — это простая практика React-SSR + Koa.Благодаря SSR мы не только улучшаем производительность, но и очень хорошо отвечаем требованиям SEO, Лучшее из обоих миров🍺.