❝В прошлый раз я говорил с вами о пользовательской маршрутизации небольших программ.
❞routes
, начал путь маршрутизации; сегодня я применю маршрутизацию на одной странице и буду говорить со всеми о 50 центах.
Возможности одностраничного приложения
"Предполагать:"На веб-странице есть кнопка, нажмите, чтобы перейти на другие страницы сайта.
"Многостраничное приложение:"Нажатие на кнопку перезагрузит html-ресурс и обновит всю страницу;
"Одностраничное приложение:"Нажмите кнопку, никакого нового html-запроса, только частичное обновление, которое может создать почти родной опыт, гладкий, как шелк.
Почему одностраничные приложения SPA могут практически не обновляться? Из-за своего SP—single-page
. При первом входе в приложение уникальныйhtml
Страница и ее общедоступные статические ресурсы, последующий так называемый «скачок», больше не берутся с сервераhtml
файл, простоDOM
Операция замены является модульной (цзя) (чжуан).
Так как же js фиксирует время переключения компонентов и меняет URL-адрес браузера без обновления? Зависит отhash
а такжеHTML5History
.
хэш-маршрутизация
характерная черта
- аналогичный
www.xiaoming.html#bar
хэш-маршрутизация, когда#
При изменении последующего хеш-значения данные не будут запрашиваться с сервера.hashchange
события для отслеживания изменений URL,DOM
Действия для имитации перехода по страницам - Не требуется взаимодействие с сервером
- Не подходит для SEO
принцип
Маршрутизация истории HTML5
характерная черта
-
History
Режим — это новая функция HTML5, которая более интуитивно понятна, чем хеш-маршрутизация, и выглядит следующим образом.www.xiaoming.html/bar
, симуляция перехода на страницу выполненаhistory.pushState(state, title, url)
Чтобы обновить маршрут браузера, прослушивайте изменения маршрута.popstate
событие для работыDOM
- Требуется внутреннее сотрудничество для перенаправления
- Относительно SEO дружественный
принцип
Интерпретация исходного кода Vue-router
кVue
маршрутизацияvue-router
Например, давайте вместе посмотрим на его исходный код.
Советы: Поскольку основное внимание в этой статье уделяется объяснению двух режимов одностраничной маршрутизации, ниже перечислены только некоторые ключевые коды, в основном для объяснения:
- Зарегистрировать плагин
- Конструктор VueRouter, отличающийся режимом маршрутизации
- Глобальная регистрация компонентов
- методы push и listen для режима hash/HTML5History
- метод перехода
Зарегистрировать плагин
Прежде всего, в качестве плагина необходимо выставитьinstall
Сознание метода, дающегоVue
папа идиuse
.
исходный кодinstall.js
В файле определяется способ регистрации и установки плагиновinstall
, смешанные методы для функции ловушки каждого компонента иbeforeCreate
Инициализировать маршрут при выполнении хука:
Vue.mixin({
beforeCreate () {
if (isDef(this.$options.router)) {
this._routerRoot = this
this._router = this.$options.router
this._router.init(this)
Vue.util.defineReactive(this, '_route', this._router.history.current)
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
registerInstance(this, this)
},
// 全文中以...来表示省略的方法
...
});
различать режим
Затем мы начинаем сindex.js
Найдите базовый класс для всего плагинаVueRouter
, нетрудно заметить, что он находится вconstructor
, в зависимости отmode
Использование разных экземпляров маршрутизации.
...
import {install} from './install';
import {HashHistory} from './history/hash';
import {HTML5History} from './history/html5';
...
export default class VueRouter {
static install: () => void;
constructor (options: RouterOptions = {}) {
if (this.fallback) {
mode = 'hash'
}
if (!inBrowser) {
mode = 'abstract'
}
this.mode = mode
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
}
}
Зарегистрируйте компонент router-link глобально
В настоящее время мы можем спросить: при использовании vue-router общий<router-link/>
,<router-view/>
Где он был введен?
назадinstall.js
файл, который вводит и глобально регистрирует компоненты router-view и router-link:
import View from './components/view';
import Link from './components/link';
...
Vue.component('RouterView', View);
Vue.component('RouterLink', Link);
существует./components/link.js
середина,<router-link/>
Компонент привязан по умолчаниюclick
событие, нажмите триггерhandler
метод для выполнения соответствующей операции маршрутизации.
const handler = e => {
if (guardEvent(e)) {
if (this.replace) {
router.replace(location, noop)
} else {
router.push(location, noop)
}
}
};
Так же, как начало упомянуто,VueRouter
отличается в конструктореmode
Инициализированные экземпляры истории для разных режимов, таким образомrouter.replace、router.push
методы не одинаковы. Далее мы вытащим исходный код этих двух режимов соответственно.
хэш-режим
В файле history/hash.js определяетсяHashHistory
класс, наследуемый от history/base.jsHistory
базовый класс.
этоprototype
определено вышеpush
Метод: в среде браузера, поддерживающей режим HTML5History (supportsPushState
правда) звонитеhistory.pushState
для изменения адреса браузера; в других средах браузера он будет использоваться напрямуюlocation.hash = path
чтобы заменить его новым хеш-адресом.
На самом деле, когда я впервые прочитал это, я немного сомневался: поскольку это уже режим хеширования, почему я должен его осуждать?supportsPushState
? Это было для поддержкиscrollBehavior
,history.pushState
Вы можете передать ключ параметра в прошлом, чтобы у каждой истории URL был ключ, и ключ использовался для сохранения информации о местоположении каждого маршрута.
В то же время прототип связанsetupListeners
метод, отвечающий за отслеживание времени изменения хэша: в среде браузера, поддерживающей режим HTML5History, отслеживатьpopstate
события; в других браузерах слушатьhashchange
. После прослушивания изменения запуститеhandleRoutingEvent
метод, который вызывает родительский классtransitionTo
Логика перехода для выполнения операции замены DOM.
import { pushState, replaceState, supportsPushState } from '../util/push-state'
...
export class HashHistory extends History {
setupListeners () {
...
const handleRoutingEvent = () => {
const current = this.current
if (!ensureSlash()) {
return
}
// transitionTo调用的父类History下的跳转方法,跳转后路径会进行hash化
this.transitionTo(getHash(), route => {
if (supportsScroll) {
handleScroll(this.router, route, current, true)
}
if (!supportsPushState) {
replaceHash(route.fullPath)
}
})
}
const eventType = supportsPushState ? 'popstate' : 'hashchange'
window.addEventListener(
eventType,
handleRoutingEvent
)
this.listeners.push(() => {
window.removeEventListener(eventType, handleRoutingEvent)
})
}
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(
location,
route => {
pushHash(route.fullPath)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
},
onAbort
)
}
}
...
// 处理传入path成hash形式的URL
function getUrl (path) {
const href = window.location.href
const i = href.indexOf('#')
const base = i >= 0 ? href.slice(0, i) : href
return `${base}#${path}`
}
...
// 替换hash
function pushHash (path) {
if (supportsPushState) {
pushState(getUrl(path))
} else {
window.location.hash = path
}
}
// util/push-state.js文件中的方法
export const supportsPushState =
inBrowser &&
(function () {
const ua = window.navigator.userAgent
if (
(ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
ua.indexOf('Mobile Safari') !== -1 &&
ua.indexOf('Chrome') === -1 &&
ua.indexOf('Windows Phone') === -1
) {
return false
}
return window.history && typeof window.history.pushState === 'function'
})()
Режим истории HTML5
похожий,HTML5History
Класс определен в history/html5.js.
определениеpush
Метод прототипа, вызовhistory.pusheState
Измените путь браузера.
В то же время прототипsetupListeners
пара методовpopstate
Осуществляется мониторинг событий, своевременная замена DOM.
import {pushState, replaceState, supportsPushState} from '../util/push-state';
...
export class HTML5History extends History {
setupListeners () {
const handleRoutingEvent = () => {
const current = this.current;
const location = getLocation(this.base);
if (this.current === START && location === this._startLocation) {
return
}
this.transitionTo(location, route => {
if (supportsScroll) {
handleScroll(router, route, current, true)
}
})
}
window.addEventListener('popstate', handleRoutingEvent)
this.listeners.push(() => {
window.removeEventListener('popstate', handleRoutingEvent)
})
}
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
pushState(cleanPath(this.base + route.fullPath))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}
}
...
// util/push-state.js文件中的方法
export function pushState (url?: string, replace?: boolean) {
saveScrollPosition()
const history = window.history
try {
if (replace) {
const stateCopy = extend({}, history.state)
stateCopy.key = getStateKey()
history.replaceState(stateCopy, '', url)
} else {
history.pushState({ key: setStateKey(genStateKey()) }, '', url)
}
} catch (e) {
window.location[replace ? 'replace' : 'assign'](url)
}
}
transitionTo обрабатывает логику смены маршрута
Два упомянутых выше режима маршрутизации запускаются при прослушиванииthis.transitionTo
, что это? На самом деле это метод-прототип, определенный в базовом классе history/base.js для обработки логики изменения маршрутизации.
пройти первымconst route = this.router.match(location, this.current)
Сравните входящее значение с текущим значением и верните соответствующий объект маршрутизации, затем оцените, совпадает ли новый маршрут с текущим маршрутом, и если он такой же, верните его напрямую;this.confirmTransition
Выполните обратный вызов, чтобы обновить объект маршрутизации и заменить DOM, связанный с представлением.
export class History {
...
transitionTo (
location: RawLocation,
onComplete?: Function,
onAbort?: Function
) {
const route = this.router.match(location, this.current)
this.confirmTransition(
route,
() => {
const prev = this.current
this.updateRoute(route)
onComplete && onComplete(route)
this.ensureURL()
this.router.afterHooks.forEach(hook => {
hook && hook(route, prev)
})
if (!this.ready) {
this.ready = true
this.readyCbs.forEach(cb => {
cb(route)
})
}
},
err => {
if (onAbort) {
onAbort(err)
}
if (err && !this.ready) {
this.ready = true
// https://github.com/vuejs/vue-router/issues/3225
if (!isRouterError(err, NavigationFailureType.redirected)) {
this.readyErrorCbs.forEach(cb => {
cb(err)
})
} else {
this.readyCbs.forEach(cb => {
cb(route)
})
}
}
}
)
}
...
}
наконец
Что ж, вышеизложенное — это небольшое знание одностраничной маршрутизации, я надеюсь, что мы сможем работать вместе с самого начала и никогда не сдаваться~~