предисловие
Прежде всего, приглашаю всех подписаться на меняБлог на гитхабе, это можно расценивать как небольшое поощрение для меня.В конце концов, писательство не может быть реализовано, и это зависит от моего собственного энтузиазма и всеобщего поощрения к настойчивости.
Предыдущий постИзоморфизм Вуэ (1)Мы рассказали, как использовать изоморфизм Vue для рендеринга простого компонента на стороне сервера и активации его на стороне сервера. Соответствующий код был загружен наGithub. В этой статье мы представляем знания, связанные с маршрутизацией в изоморфизме Vue.
маршрутизация
Пишем здесь, давайте сначала обсудим, зачем нам нужна внешняя маршрутизация и зачем нам нужно внедрять Vue-Router в нашу программу? Фактически, самые ранние веб-сайты отображались на сервере, а не на браузерном. Каждый раз, когда вы вводите соответствующий URL-адрес на панели навигации браузера или щелкаете ссылку текущей страницы, браузер получает соответствующий URL-адрес и отображает HTML-страницу. Проблема в том, что каждое действие означает обновление страницы. Все это решает появление асинхронных запросов, мы можем динамически запрашивать данные через XMLHTTPRequest вместо того, чтобы каждый раз обновлять соответствующий интерфейс, реализуя взаимодействие страниц без фонового обновления. Позже появление одностраничного приложения (SPA: Single Page Web Application) продвинуло эту концепцию на шаг вперед: не только взаимодействие со страницей не требует обновления страницы, но даже переход на страницу не требует обновления текущей страницы. Когда переход на страницу не требует обновления текущей страницы, мы должны решить проблему переключения компонентов по разным URL-адресам, что и делает фронтенд-роутинг.
Концепция маршрутизатора на самом деле исходит из фона и отвечает за сопоставление URL-адресов с функциями. Например:
/user -> getAllUsers()
/user/count -> getUserCount()
Каждое из правил сопоставления URL-адреса с функцией, которое мы называемroute
,а такжеrouter
эквивалентно управлениюroute
контейнер. Концепция внешней маршрутизации аналогична, за исключением того, что URL-адрес сопоставляет внешние компоненты. Например:
/user -> User组件
Маршрутизация рендеринга на стороне клиента
Благодаря элегантному дизайну Vue комбинация Vue и Vue Router очень проста: сначала нужно настроить правила маршрутизации и создать экземпляры маршрутизации, затем передать экземпляр маршрутизации корневому элементу Vue, чтобы добавить его, и, наконец, использоватьrouter-view
компонент, чтобы сообщить Vue Router, где выполнять рендеринг.
<div id="app">
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
//引入路由组件
import Home from '../components/Home.vue'
import About from '../components/About.vue'
//路由配置
const routes = [
{ path: '/', component: Home },
{ path: '/home', component: Home },
{ path: '/about', component: About }
]
//创建Vue Router实例
var router = new VueRouter({
routes
})
//引入vue-router
const app = new Vue({
router,
//......
})
серверная маршрутизация
Выше мы представили логику рендеринга Vue Router на стороне клиента. Конечно, это только самая простая логика. Для более продвинутого использования, пожалуйста, обратитесь кОфициальная документация Vue Router, не является предметом этой статьи, поэтому мы не будем повторяться.
Vue Router фактически имеет два режима:hash
узор иhistory
модель.hash
Режим — это режим по умолчанию для Vue Router. Чтобы прояснить эти два режима, мы должны упомянуть различную логику реализации, соответствующую этим двум режимам.
хэш-режим
На самом деле можно подумать, что браузер нельзя обновлять в процессе переключения фронтенд-роутинга, иначе это нарушит правила взаимодействия SPA-роутинга. Прежде всего, мы нацелились на идентификатор фрагмента (якорь) в URL-адресе, как полный URL-адрес, формат следующий
Часть #ch1 - это то, что мы называемидентификатор фрагмента, который обычно можно использовать для обозначения подресурсов в полученном ресурсе. Изменение идентификатора фрагмента не приведет к обновлению браузера, поэтомуhash
Шаблон — это идентификатор фрагмента, используемый в качестве основы для внешней маршрутизации. Во внешней маршрутизации мы называем идентификатор фрагмента хеш-частью, хэш-часть — это только состояние клиента, а хеш-часть не будет получена сервером. мы можем пройтиwindow.onhashchagnge
событие для прослушивания URLhash
Часть изменения, которая также является основой маршрутизации на основе хэшей. Например:
window.addEventListener('hashchange', function(e){
console.log('hashchange', e);
})
Если хеш-часть браузера изменится, функция прослушивателя немедленно вызовет соответствующее событие.
режим истории
HTML5 представляет новые API для изменения URL-адресов без обновления текущей страницы. Существует два соответствующих метода:
pushState(state, title, url)
replaceState(state, title, url)
pushState
Используется для добавления новой записи в историю браузера при изменении адресного содержимого адресной строки.replaceState
затем сpushState
Точно так же текущая запись истории изменяется вместо создания новой. Параметры, соответствующие двум функциям:
- состояние (объект состояния): состояние объекта состояния является объектом JavaScript, и соответствующее состояние события можно прочитать после изменения URL-адреса. Может использоваться для восстановления состояния страницы.
- title (название): этот параметр в настоящее время игнорируется, но может быть использован в будущем. Можно передать пустую строку
- URL: Этот параметр определяет новую историческую запись URL.
а такжеpushState
а такжеreplaceState
Он используется сonpopstate
события, важно отметить, что вызовhistory.pushState()
илиhistory.replaceState()
не сработаетpopstate
мероприятие. Это событие запускается только тогда, когда выполняется действие браузера, например, когда пользователь нажимает кнопку «Назад» в браузере (или вызывает в коде Javascripthistory.back()
)
Например:
window.addEventListener('popstate', function(){
console.log("location: " + document.location + ", state: " +JSON.stringify(event.state));
})
history.pushState({page: 1}, "title 1", "?page=1");
history.back(); //"location: http://example.com/example.html?page=1, state: {"page":1}"
Два режима мы закончили,history
в сравнении сhash
В плане урла должно быть красиво, но нужна поддержка фонового сервера, т.к.history
Я больше всего боюсь, что браузер обновится, например, у нас front-end роутинг начинается с/home
изменить на/about
, это только изменение внешнего URL-адреса, и оно не будет обновлять текущую страницу, а также включение браузера назад и вперед не обновит браузер. Но если после обновления браузер действительно запросит текущий URL-адрес, например/about
. В настоящее время, если браузер не распознает URL-адрес, он может не найти текущую страницу.
простой пример
Сказав так много, какой режим нам нужно использовать для рендеринга сервера? То, что мы используем,history
Режим, это единственный вариант Ответ на самом деле был упомянут выше, потому что хэш-часть является только состоянием клиента и не будет получена сервером. Теперь предположим, что наше текущее приложение имеет два маршрута:
/ -> Home
/about -> About
Сначала мы создаем наш экземпляр маршрутизации. В предыдущей статье мы создавали новый экземпляр компонента для каждого запроса. Цель состоит в том, чтобы перекрестно влиять на разные запросы. То же самое верно и для экземпляров маршрутизации:
// router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '../components/Home.vue'
import About from '../components/About.vue'
Vue.use(Router)
export function createRouter() {
return new Router({
mode: "history",
routes: [{
path: '/', component: Home
}, {
path: "/about", component: About
}]
})
}
createRouter
Каждый раз, когда вызывается функция, создается экземпляр маршрутизации, и конфигурация в экземпляре маршрутизацииhistory
режим и правила маршрутизации настроены.
Далее посмотримHome
Компоненты:
<template>
<div>
<div>当前位置: About</div>
<router-link to="/home">前往Home</router-link>
<button @click="directHome">按钮: 前往Home</button>
</div>
</template>
<script>
export default {
name: "about",
methods: {
directHome: function () {
this.$router.push('/');
}
}
}
</script>
Причина, по которой я использую этот компонентrouter-link
также используется в случаеbutton
, главным образом, чтобы доказать, что клиент был активирован.About
компоненты иHome
Компоненты идентичны, за исключением имен и адресов ссылок, и больше не перечислены.
мы в корневом компонентеApp
Визуализация компонентов сопоставления маршрутов в
//App.Vue
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
Далее нам нужно продолжить трансформациюapp.js
, мы уже представили сервер в предыдущей статьеapp.js
Основная задача открыта за пределами заводской функции, логическая конкретная клиентская и боковая сторона браузера, соответственно, были переданы на клиентский файл ввода и сторона браузераentry-client.js
а такжеentry-server.js
import Vue from 'vue'
import App from './components/App.vue'
import {createRouter} from './router'
export function createApp() {
const router = createRouter()
const app = new Vue({
router,
render: h => h(App)
})
return {
app,
router
}
}
createApp
Отличие от предыдущего заключается в том, что экземпляр Vue внедряется каждый раз при его создании.router
. И вернул созданный экземпляр Vue и экземпляр Vue Router.
Логика рендеринга на стороне сервера сосредоточена вentry-server.js
:
// entry-server.js
import { createApp } from './app'
export default function (context) {
return new Promise((resolve, reject) => {
const {app, router} = createApp()
router.push(context.url)
router.onReady(() => {
// Promise 应该 resolve 应用程序实例,以便它可以渲染
resolve(app)
}, reject)
})
}
entry-server.js
Как запись рендеринга на стороне сервера, она упакована как соответствующийbundle
входящийcreateBundleRenderer
генерироватьrenderer
,передачаrenderer.renderToString
можно пройти вcontext
, который может содержать текущий маршрутurl
. пока мыentry-server.js
в функции, которая принимаетcontext
Затем объект может получить информацию о маршрутизации.
В отличие от статьи выше, мы не возвращали непосредственно экземпляр Vue, а возвращалиPromise
,существуетPromise
Сначала мы звонимcreateApp
Получить экземпляр Vueapp
и экземпляр Vue Routerrouter
, то звонимpush
Функция перемещает текущий маршрут к целевому URL-адресу. Затем мы звоним вrouter.onReady
функция, которая гарантирует, что все ожидающие маршрутаАсинхронная функция ловушкиа такжеАсинхронные компонентыПосле загрузкиresolve
Текущий экземпляр Vue.
а такжеentry-server.js
Аналогично, упакованный входной файл клиентаentry-client.js
Его также необходимо вызвать перед установкой приложения.router.onReady
:
import { createApp } from './app'
const {app, router} = createApp();
router.onReady(() => {
app.$mount('#app')
})
Теперь перейдем к нашемуexpress
Код сервера в основном такой же, как и при последнем рендеринге, но нам нужно датьrenderToString
передатьcontext
объект, который содержит текущийurl
стоимость.
//server.js
//省略......
app.get('*', (req, res) => {
const context = { url: req.url }
renderer.renderToString(context, function (err, html) {
res.end(html)
})
})
//省略......
Теперь мы упаковываем серверную и браузерную частиbundle
и запустите сервер:
Теперь думаем о проблеме, если задать в качестве маршрутизацииrouter
Если защита установлена в , будет ли она выполняться в браузере или на сервере? Для проверки этой задачи даемrouter
Добавить глобальную защитуbeforeEach
а такжеafterEach
:
export function createApp() {
const router = createRouter()
router.beforeEach((to, from, next) => {
console.log("beforeEach---start");
console.log('to: ', to.path, ' from: ', from.path);
console.log("beforeEach---end");
next();
})
router.afterEach((to, from) => {
console.log("afterEach---start");
console.log('to: ', to.path, ' from: ', from.path);
console.log("afterEach---end");
})
// 省略......
}
Мы посещаем напрямую/
Маршрутизация, мы видим, что результаты вывода сервера и клиента выглядят следующим образом:
Это означает, что функция защиты выполняется как на стороне сервера, так и на стороне клиента, а маршруты на обоих концах разрешают ловушки маршрутизации, которые могут существовать в вызывающем компоненте. Вы можете иметь это в виду во время разработки.
разделение кода
Прежде всего, мы можем рассмотреть проблему Основная цель введения изоморфизма Vue состоит в том, чтобы ускорить скорость отображения первого экрана, тогда мы можем рассмотреть, если мы посетим/
При маршрутизации вам нужно только загрузитьHome
Компонент в порядке, его не нужно загружатьAbout
компоненты. Мы можем загрузить его снова, когда нам это нужно.About
компонентов, чтобы мы могли уменьшить размер ресурсов, загружаемых при первоначальном рендеринге, и ускорить интерактивное время. Здесь мы можем рассмотреть возможность разделения кода.
Разделение кода на самом деле является функцией, поддерживаемой Webpack, которая может упаковывать разные коды в разныеbundle
, а затем загружать файл по запросу.
Webpack
Простейшее разбиение кода не более чем ручная работа, можно настроить несколькоentry
Чтобы добиться, но есть много проблем в ручном режиме, таких как несколькоbundle
оба ссылаются на один и тот же модуль, то каждыйbundle
Есть дублирующий код. Эта проблема хорошо решена, мы можем использоватьSplitChunksPlugin
плагин для решения этой проблемы. Но вручную все равно не очень удобно, поэтому Webpack предоставляет более удобныйдинамический импорт.
Функция динамического импорта рекомендуется использовать предложение ECMAScriptimport()
грамматика,import()
Вы можете указать расположение загружаемого модуля, а затем динамически загружать модуль во время выполнения и возвращатьPromise
. Скажем, мы хотим динамически загружать модульlodash
Модули, которые мы можем сначала добавить в файл конфигурации Webpack:
output: {
chunkFilename: '[name].bundle.js',
},
chunkFilename
Именно для конфигурации нужно определить имя незаходного чанка, а затем в коде:
import(/* webpackChunkName: "lodash" */ 'lodash').then(lodash => {
//lodash便可以使用
})
Код упаковки Мы можем обнаружить, что lodash упакован отдельно, потому что в комментарии мы присваиваем значение webpackChunkName для lodash, поэтому он называется lodash.bundle.js. Конечно этоchunkFilename
Это не обязательно, по умолчанию будет названо[id].bundle.js
.
Асинхронные компоненты
Vue предоставляет концепцию асинхронных компонентов, позволяющую нам разбивать код на куски и загружать их по запросу. По сравнению с обычной регистрацией компонентов мы можем определить компоненты в виде фабричной функции, которая получитresolve
Обратный вызов, эта функция обратного вызова будет вызываться, когда вы получите определение компонента с сервера. Или верните прямо в заводскую функциюPromise
. мы знаемimport()
Синтаксис возвращаетPromise
, поэтому мы сопоставляем код до преобразования:
// router.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export function createRouter() {
return new Router({
mode: "history",
routes: [{
path: '/',
component: () => import('../components/Home.vue')
}, {
path: "/about",
component: () => import('../components/About.vue')
}]
})
}
Затем упакуйте пакет клиента:
> vue-ssr-demo@1.0.0 build:client /Users/mr_wang/WebstormProjects/vue-ssr-demo
> cross-env NODE_ENV=production webpack --config build/webpack.client.config.js --progress --hide-modules
Hash: 16fbba9bf008ec7ef466
Version: webpack 3.12.0
Time: 1158ms
Asset Size Chunks Chunk Names
0.8ac6ad83b93d774d3817.js 5.04 kB 0 [emitted]
1.5967060b78729a4577f9.js 5.04 kB 1 [emitted]
app.1c160fc3e08eec3aed0f.js 7.37 kB 2 [emitted] app
vendor.f32c57c9ee5145002da1.js 296 kB 3 [emitted] [big] vendor
manifest.4b057fd51087adaec1f3.js 5.85 kB 4 [emitted] manifest
vue-ssr-client-manifest.json 1.48 kB [emitted]
Мы обнаружили, что в выходном файле больше 0.[хэш].js и 1.[хэш].js, что соответствуетHome
Компоненты иAbout
компоненты. Конечно, если вы считаете, что этот модуль выглядит неясно, вы также можете передать параметр webpackChunkName, как указано выше, чтобы сделать проблемы с пакетами более идентифицируемыми:
component: import(/* webpackChunkName: "home" */'../components/Home.vue')
component: import(/* webpackChunkName: "about" */'../components/About.vue')
На данный момент файлы, упакованные Webpack:
Hash: aaf79995904c4786cadc
Version: webpack 3.12.0
Time: 976ms
Asset Size Chunks Chunk Names
home.bundle.js 5.04 kB 0 [emitted] home
about.bundle.js 5.04 kB 1 [emitted] about
app.f22015420ff0db6ec4b0.js 7.37 kB 2 [emitted] app
vendor.f32c57c9ee5145002da1.js 296 kB 3 [emitted] [big] vendor
manifest.2a21c55e4a3e98ab252c.js 5.83 kB 4 [emitted] manifest
vue-ssr-client-manifest.json 1.44 kB [emitted]
Затем мы запускаем сервер, обращаемся к маршруту '/' и обнаруживаем, что запрос выглядит следующим образом:
Сначала мы смотрим в сеть, мы находим, что,0.[hash].js
сначала просил, потом просил1.[hash].js
, и приоритеты загрузки у них разные,0.[hash].js
приоритет выше, чем1.[hash].js
,Почему это? Давайте посмотрим на соответствующий html.
Мы видим, что0.[hash].js
Это предварительная нагрузка при впрыскивании и1.[hash].js
Когда инжект это prefetch, есть ли разница между preload и prefetch?На самом деле и то, и другое можно написать в одной статье, но здесь мы кратко подытожим.
предварительная выборка — это способ указать браузеру, что нужно извлечь элементможет бытьСледующая страница для доступа к необходимым ресурсам. Это означает, что ресурсы будут выбираться с более низким приоритетом, поэтому предварительная выборка используется для получения ресурсов, которые не используются текущей страницей.
предварительная загрузка — это указание браузеру загружать ресурсы, найденные позже, заранее. Некоторые ресурсы скрыты в CSS и JavaScript, браузер не знает, что странице вот-вот потребуются эти ресурсы, и когда они обнаружены, для загрузки уже слишком поздно, поэтому декларативная ранняя загрузка.
Суммировать
В этой статье в основном рассказывается о том, как использовать роутинг во Vue и как еще больше повысить скорость загрузки первого экрана страницы за счет сегментации кода. специфическийкодВы можете нажать здесь, чтобы просмотреть. Наконец, я надеюсь нажать звезду, чтобы поддержать мой блог.Я очень благодарен.Если есть какие-либо ошибки в выражении, пожалуйста, поправьте меня.