Недавно был задан вопрос: вы используете в своем проекте SPA Vue (одностраничный) или многостраничный дизайн Vue?
Эта статья посвящена одностраничному дизайну Vue SPA. О том, как расширить многостраничный дизайн VueПожалуйста, нажмите, чтобы просмотреть.
Что такое vue-маршрутизатор?
Прежде всего, нам нужно знать, что такое vue-router и что он делает?
Маршрутизация здесь не относится к аппаратным маршрутизаторам, на которые мы обычно ссылаемся.Маршрутизация здесь — это диспетчер путей SPA (одностраничное приложение). Другими словами, vue-router — это система управления путями ссылок WebApp.
vue-router — официальный плагин маршрутизации для Vue.js, глубоко интегрированный с vue.js и подходящий для создания одностраничных приложений.
В чем разница между этим и традиционным переходом на страницу?
1. Одностраничное приложение Vue основано на маршрутизации и компонентах.Маршрутизация используется для установки путей доступа и сопоставления путей и компонентов.
2. Традиционные страничные приложения используют некоторые гиперссылки для переключения страниц и переходов между ними.
В одностраничном приложении vue-router это переключение между путями, то есть переключение компонентов. Суть модуля маршрутизации заключается в установлении отношения отображения между URL-адресом и страницей.
Что касается того, почему вы не можете использовать тег a, это потому, что все одностраничные приложения сделаны с помощью Vue, что эквивалентно только одной основной странице index.html, поэтому вы пишетеМетки не работают и должны управляться с помощью vue-router.
Принцип реализации Vue-маршрутизатора
SPA (одностраничное приложение): одностраничное приложение только с одной полной страницей; когда оно загружает страницу, оно не загружает содержимое всей страницы, а только обновляет содержимое в указанном контейнере.
Одним из основных элементов одностраничного приложения (SPA) является:
1. Обновить представление без повторного запроса страницы;
2. Когда vue-router реализует одностраничную интерфейсную маршрутизацию, он предоставляет три режима: режим хеширования, режим истории и абстрактный режим. Какой режим использовать, определяется в соответствии с параметром режима.
режим маршрутизации
vue-router предоставляет три режима работы:
● хэш: используйте хэш-значение URL для маршрутизации. Режим по умолчанию.
● история: зависит от HTML5 History API и конфигурации сервера. Проверьте режим истории HTML5.
● abstract: поддерживает все среды выполнения JavaScript, например серверную часть Node.js.
Хэш-режим
Режим vue-router по умолчанию — хеш-режим — используйте хэш URL-адреса для имитации полного URL-адреса, когда URL-адрес изменяется, страница не будет перезагружена..
Хэш (#) — это точка привязки URL-адреса, представляющая позицию на веб-странице. Если вы просто измените часть после # (/#/..), браузер загрузит только содержимое соответствующей позиции и не будет перезагружать веб-страницу. То есть, # используется для управления действиями браузера, и это совершенно бесполезно для серверной стороны. HTTP-запрос не включает #; в то же время, каждый раз, когда часть после изменения # запись будет добавлена в историю доступа браузера, с помощью кнопки "назад" вернуться на предыдущую позицию;Поэтому режим Hash отображает разные данные в указанном месте DOM в соответствии с разными значениями, изменяя значение привязки..
Режим истории
API истории HTML5 предоставляет функцию, которая позволяет разработчикам изменять URL-адрес сайта без обновления всей страницы, то есть использовать API history.pushState для завершения перехода по URL-адресу без перезагрузки страницы;
Поскольку режим хеширования будет иметь свой собственный # в URL-адресе, если мы не хотим уродливого хэша, мы можем использовать режим маршрутизации истории, просто добавьте «режим:« история »» при настройке правил маршрутизации, этот режим полностью использует истории .pushState API для выполнения перехода по URL-адресу без перезагрузки страницы.
//main.js文件中
const router = new VueRouter({
mode: 'history',
routes: [...]
})
При использовании режима истории URL-адрес похож на обычный URL-адрес, например.yoursite.com/user/id, лучше…Однако, чтобы хорошо играть в этом режиме, ему также нужна поддержка фоновой конфигурации. Поскольку наше приложение является одностраничным клиентским приложением, если фон не настроен должным образом, когда пользователь напрямую обращается к браузеру
Итак, вам нужно добавить ресурс-кандидат, который охватывает все ситуации на стороне сервера: если URL-адрес не соответствует ни одному из статических ресурсов, он должен возвращать ту же страницу index.html, от которой зависит ваше приложение.
export const routes = [
{path: "/", name: "homeLink", component:Home}
{path: "/register", name: "registerLink", component: Register},
{path: "/login", name: "loginLink", component: Login},
{path: "*", redirect: "/"}]
Здесь установлено, что если URL-адрес введен неправильно или URL-адрес не соответствует каким-либо статическим ресурсам, он автоматически перейдет на Домашнюю страницу.
абстрактный режим
Абстрактный режим заключается в использовании независимого от браузера виртуального управления историей просмотров.
В соответствии с различиями платформ в среде Weex поддерживается только абстрактный режим. Однако vue-router сам проверит среду.Если он обнаружит, что API браузера отсутствует, vue-router автоматически заставит его войти в абстрактный режим, поэтому при использовании vue-router, пока вы не напишете режим, он будет просматривать по умолчанию.Хеш-режим используется в серверной среде, а абстрактный режим используется в мобильной среде. (Конечно, вы также можете явно указать использование абстрактного режима во всех случаях)
Как использовать vue-маршрутизатор
1: Скачать npm i vue-router -S
**2: Ввести в main.js ** импортировать VueRouter из 'vue-router';
3: Установите плагин Vue.use(VueRouter);
4: Создайте объект маршрутизации и настройте правила маршрутизации
let router = new VueRouter({routes:[{path:'/home',component:Home}]});
5: передать свой объект маршрута экземпляру Vue, добавьте маршрутизатор: маршрутизатор в параметры
6: Оставьте яму в app.vue
<router-view></router-view>
Для конкретной реализации см. следующий код:
//main.js文件中引入
import Vue from 'vue';
import VueRouter from 'vue-router';
//主体
import App from './components/app.vue';
import index from './components/index.vue'
//安装插件
Vue.use(VueRouter); //挂载属性
//创建路由对象并配置路由规则
let router = new VueRouter({
routes: [
//一个个对象
{ path: '/index', component: index }
]
});
//new Vue 启动
new Vue({
el: '#app',
//让vue知道我们的路由规则
router: router, //可以简写router
render: c => c(App),
})
Наконец, не забудьте «оставить яму» в app.vue.
//app.vue中
<template>
<div>
<!-- 留坑,非常重要 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
data(){
return {}
}
}
</script>
анализ исходного кода vue-router
Давайте сначала посмотрим на путь реализации Vue.
В файле ввода вам нужно создать экземпляр объекта экземпляра VueRouter, а затем передать его в параметры экземпляра Vue.
export default class VueRouter {
static install: () => void;
static version: string;
app: any;
apps: Array<any>;
ready: boolean;
readyCbs: Array<Function>;
options: RouterOptions;
mode: string;
history: HashHistory | HTML5History | AbstractHistory;
matcher: Matcher;
fallback: boolean;
beforeHooks: Array<?NavigationGuard>;
resolveHooks: Array<?NavigationGuard>;
afterHooks: Array<?AfterNavigationHook>;
constructor (options: RouterOptions = {}) {
this.app = null
this.apps = []
this.options = options
this.beforeHooks = []
this.resolveHooks = []
this.afterHooks = []
// 创建 matcher 匹配函数
this.matcher = createMatcher(options.routes || [], this)
// 根据 mode 实例化具体的 History,默认为'hash'模式
let mode = options.mode || 'hash'
// 通过 supportsPushState 判断浏览器是否支持'history'模式
// 如果设置的是'history'但是如果浏览器不支持的话,'history'模式会退回到'hash'模式
// fallback 是当浏览器不支持 history.pushState 控制路由是否应该回退到 hash 模式。默认值为 true。
this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
if (this.fallback) {
mode = 'hash'
}
// 不在浏览器内部的话,就会变成'abstract'模式
if (!inBrowser) {
mode = 'abstract'
}
this.mode = mode
// 根据不同模式选择实例化对应的 History 类
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}`)
}
}
}
match (
raw: RawLocation,
current?: Route,
redirectedFrom?: Location
): Route {
return this.matcher.match(raw, current, redirectedFrom)
}
get currentRoute (): ?Route {
return this.history && this.history.current
}
init (app: any /* Vue component instance */) {
process.env.NODE_ENV !== 'production' && assert(
install.installed,
`not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
`before creating root instance.`
)
this.apps.push(app)
// main app already initialized.
if (this.app) {
return
}
this.app = app
const history = this.history
// 根据history的类别执行相应的初始化操作和监听
if (history instanceof HTML5History) {
history.transitionTo(history.getCurrentLocation())
} else if (history instanceof HashHistory) {
const setupHashListener = () => {
history.setupListeners()
}
history.transitionTo(
history.getCurrentLocation(),
setupHashListener,
setupHashListener
)
}
history.listen(route => {
this.apps.forEach((app) => {
app._route = route
})
})
}
// 路由跳转之前
beforeEach (fn: Function): Function {
return registerHook(this.beforeHooks, fn)
}
// 路由导航被确认之间前
beforeResolve (fn: Function): Function {
return registerHook(this.resolveHooks, fn)
}
// 路由跳转之后
afterEach (fn: Function): Function {
return registerHook(this.afterHooks, fn)
}
// 第一次路由跳转完成时被调用的回调函数
onReady (cb: Function, errorCb?: Function) {
this.history.onReady(cb, errorCb)
}
// 路由报错
onError (errorCb: Function) {
this.history.onError(errorCb)
}
// 路由添加,这个方法会向history栈添加一个记录,点击后退会返回到上一个页面。
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.history.push(location, onComplete, onAbort)
}
// 这个方法不会向history里面添加新的记录,点击返回,会跳转到上上一个页面。上一个记录是不存在的。
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.history.replace(location, onComplete, onAbort)
}
// 相对于当前页面向前或向后跳转多少个页面,类似 window.history.go(n)。n可为正数可为负数。正数返回上一个页面
go (n: number) {
this.history.go(n)
}
// 后退到上一个页面
back () {
this.go(-1)
}
// 前进到下一个页面
forward () {
this.go(1)
}
getMatchedComponents (to?: RawLocation | Route): Array<any> {
const route: any = to
? to.matched
? to
: this.resolve(to).route
: this.currentRoute
if (!route) {
return []
}
return [].concat.apply([], route.matched.map(m => {
return Object.keys(m.components).map(key => {
return m.components[key]
})
}))
}
resolve (
to: RawLocation,
current?: Route,
append?: boolean
): {
location: Location,
route: Route,
href: string,
// for backwards compat
normalizedTo: Location,
resolved: Route
} {
const location = normalizeLocation(
to,
current || this.history.current,
append,
this
)
const route = this.match(location, current)
const fullPath = route.redirectedFrom || route.fullPath
const base = this.history.base
const href = createHref(base, fullPath, this.mode)
return {
location,
route,
href,
// for backwards compat
normalizedTo: location,
resolved: route
}
}
addRoutes (routes: Array<RouteConfig>) {
this.matcher.addRoutes(routes)
if (this.history.current !== START) {
this.history.transitionTo(this.history.getCurrentLocation())
}
}
}
HashHistory
• Хотя хеш появляется в URL-адресе, он не будет включен в HTTP-запрос. Он используется для управления действиями браузера и не влияет на сервер. Поэтому изменение хеша не приведет к перезагрузке страницы.
• Вы можете добавить события слушателя для изменений хэша:
window.addEventListener("hashchange",funcRef,false)
• При каждом изменении хэша (window.location.hash) запись будет добавляться в историю доступа браузера.
export class HashHistory extends History {
constructor (router: Router, base: ?string, fallback: boolean) {
super(router, base)
// check history fallback deeplinking
// 如果是从history模式降级来的,需要做降级检查
if (fallback && checkFallback(this.base)) {
// 如果降级且做了降级处理,则返回
return
}
ensureSlash()
}
.......
function checkFallback (base) {
const location = getLocation(base)
// 得到除去base的真正的 location 值
if (!/^\/#/.test(location)) {
// 如果此时地址不是以 /# 开头的
// 需要做一次降级处理,降为 hash 模式下应有的 /# 开头
window.location.replace(
cleanPath(base + '/#' + location)
)
return true
}
}
function ensureSlash (): boolean {
// 得到 hash 值
const path = getHash()
if (path.charAt(0) === '/') {
// 如果是以 / 开头的,直接返回即可
return true
}
// 不是的话,需要手动保证一次 替换 hash 值
replaceHash('/' + path)
return false
}
export function getHash (): string {
// We can't use window.location.hash here because it's not
// consistent across browsers - Firefox will pre-decode it!
// 因为兼容性的问题,这里没有直接使用 window.location.hash
// 因为 Firefox decode hash 值
const href = window.location.href
const index = href.indexOf('#')
return index === -1 ? '' : decodeURI(href.slice(index + 1))
}
// 得到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
}
}
// 替代hash
function replaceHash (path) {
if (supportsPushState) {
replaceState(getUrl(path))
} else {
window.location.replace(getUrl(path))
}
}
Изменения хэша автоматически добавляются в историю доступа браузера. Итак, как реализовано обновление представления, посмотрите на метод transitionTo():
transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const route = this.router.match(location, this.current) //找到匹配路由
this.confirmTransition(route, () => { //确认是否转化
this.updateRoute(route) //更新route
onComplete && onComplete(route)
this.ensureURL()
// fire ready cbs once
if (!this.ready) {
this.ready = true
this.readyCbs.forEach(cb => { cb(route) })
}
}, err => {
if (onAbort) {
onAbort(err)
}
if (err && !this.ready) {
this.ready = true
this.readyErrorCbs.forEach(cb => { cb(err) })
}
})
}
//更新路由
updateRoute (route: Route) {
const prev = this.current // 跳转前路由
this.current = route // 装备跳转路由
this.cb && this.cb(route) // 回调函数,这一步很重要,这个回调函数在index文件中注册,会更新被劫持的数据 _router
this.router.afterHooks.forEach(hook => {
hook && hook(route, prev)
})
}
}
pushState
export function pushState (url?: string, replace?: boolean) {
saveScrollPosition()
// try...catch the pushState call to get around Safari
// DOM Exception 18 where it limits to 100 pushState calls
// 加了 try...catch 是因为 Safari 有调用 pushState 100 次限制
// 一旦达到就会抛出 DOM Exception 18 错误
const history = window.history
try {
if (replace) {
// replace 的话 key 还是当前的 key 没必要生成新的
history.replaceState({ key: _key }, '', url)
} else {
// 重新生成 key
_key = genKey()
// 带入新的 key 值
history.pushState({ key: _key }, '', url)
}
} catch (e) {
// 达到限制了 则重新指定新的地址
window.location[replace ? 'replace' : 'assign'](url)
}
}
replaceState
// 直接调用 pushState 传入 replace 为 true
export function replaceState (url?: string) {
pushState(url, true)
}
Общая черта двух методов pushState и replaceState: когда они вызываются для изменения стека истории браузера, несмотря на то, что текущий URL-адрес изменился, браузер не будет сразу отправлять запрос на URL-адрес, что является внешней маршрутизацией для однократного использования. страница приложения, обновление представления, но не Страница повторного запроса обеспечивает основу.
supportsPushState
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 && 'pushState' in window.history
})()
На самом деле, так называемый адаптивный атрибут, то есть при изменении значения _route будет автоматически вызываться метод render() экземпляра Vue для обновления представления. $router.push()-->HashHistory.push()-->History.transitionTo()-->History.updateRoute()-->{app._route=route}-->vm.render()
Слушайте адресную строку
В браузере пользователь может напрямую ввести маршрут изменения в адресной строке браузера, поэтому также необходимо отслеживать изменение маршрута в адресной строке браузера и иметь такое же поведение ответа, как вызов через код. эта функция передается через setupListeners Listen для реализации hashchange:
setupListeners () {
window.addEventListener('hashchange', () => {
if (!ensureSlash()) {
return
}
this.transitionTo(getHash(), route => {
replaceHash(route.fullPath)
})
})
}
HTML5History
Интерфейс истории — это интерфейс, предоставляемый стеком истории браузера.С помощью методов back(), forward(), go() и других методов мы можем читать информацию из стека истории браузера и выполнять различные операции перехода.
export class HTML5History extends History {
constructor (router: Router, base: ?string) {
super(router, base)
const expectScroll = router.options.scrollBehavior //指回滚方式
const supportsScroll = supportsPushState && expectScroll
if (supportsScroll) {
setupScroll()
}
const initLocation = getLocation(this.base)
//监控popstate事件
window.addEventListener('popstate', e => {
const current = this.current
// Avoiding first `popstate` event dispatched in some browsers but first
// history route not updated since async guard at the same time.
// 避免在某些浏览器中首次发出“popstate”事件
// 由于同一时间异步监听,history路由没有同时更新。
const location = getLocation(this.base)
if (this.current === START && location === initLocation) {
return
}
this.transitionTo(location, route => {
if (supportsScroll) {
handleScroll(router, route, current, true)
}
})
})
}
Режим хеширования изменяет только содержимое хэш-части, а хэш-часть не будет включена в http-запрос (хэш со знаком #):
oursite.com/#/user/id//По запросу отправьте только http://oursite.com/
Поэтому в хеш-режиме не будет проблем запросить страницу по url
В режиме истории URL-адрес изменяется так же, как и URL-адрес обычного бэкэнда запроса (история без #)
Если такой запрос отправляется на серверную часть, серверная часть не настраивает обработку маршрута получения, соответствующую /user/id, и будет возвращена ошибка 404.
Официально рекомендуемое решение — добавить ресурс-кандидат, который охватывает все ситуации на стороне сервера: если URL-адрес не соответствует ни одному из статических ресурсов, он должен возвращать ту же страницу index.html, от которой зависит ваше приложение. Кроме того, при этом сервер больше не возвращает страницу ошибки 404, поскольку файл index.html возвращается для всех путей. Чтобы этого избежать, переопределите все случаи маршрутизации в приложении Vue, а затем дайте страницу 404. Или, если вы используете Node.js в качестве фона, вы можете использовать маршрут на стороне сервера для сопоставления URL-адреса и возвращать 404, если маршрут не соответствует, тем самым реализуя резервный вариант.
Сравнение двух режимов
В общих сценариях спроса режим хеширования аналогичен режиму истории.Согласно введению MDN, вызов history.pushState() имеет следующие преимущества по сравнению с прямым изменением хэша:
• Новый URL-адрес, установленный pushState, может быть любым URL-адресом того же происхождения, что и текущий URL-адрес, а хэш может изменять только часть после #, поэтому можно установить только URL-адрес того же документа, что и текущий.
• Новый URL-адрес, установленный pushState, может точно совпадать с текущим URL-адресом, который также добавит запись в стек, а новое значение, установленное хэшем, должно отличаться от исходного, чтобы инициировать добавление записи в стек.
• pushState может добавлять записи данных любого типа через stateObject, тогда как hash может добавлять только короткие строки pushState может дополнительно установить атрибут title для последующего использования
AbstractHistory
"Абстрактный" режим не включает записи, связанные с адресами браузера. Процесс такой же, как и в "HashHistory". Принцип заключается в моделировании функции стека истории браузера через массив.
//abstract.js实现,这里通过栈的数据结构来模拟路由路径
export class AbstractHistory extends History {
index: number;
stack: Array<Route>;
constructor (router: Router, base: ?string) {
super(router, base)
this.stack = []
this.index = -1
}
// 对于 go 的模拟
go (n: number) {
// 新的历史记录位置
const targetIndex = this.index + n
// 小于或大于超出则返回
if (targetIndex < 0 || targetIndex >= this.stack.length) {
return
}
// 取得新的 route 对象
// 因为是和浏览器无关的 这里得到的一定是已经访问过的
const route = this.stack[targetIndex]
// 所以这里直接调用 confirmTransition 了
// 而不是调用 transitionTo 还要走一遍 match 逻辑
this.confirmTransition(route, () => {
this.index = targetIndex
this.updateRoute(route)
})
}
//确认是否转化路由
confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {
const current = this.current
const abort = err => {
if (isError(err)) {
if (this.errorCbs.length) {
this.errorCbs.forEach(cb => { cb(err) })
} else {
warn(false, 'uncaught error during route navigation:')
console.error(err)
}
}
onAbort && onAbort(err)
}
//判断如果前后是同一个路由,不进行操作
if (
isSameRoute(route, current) &&
route.matched.length === current.matched.length
) {
this.ensureURL()
return abort()
}
//下面是各类钩子函数的处理
//*********************
})
}
Увидев здесь, вы в основном освоили маршрутизацию vue-router.Если вы хотите увидеть исходный код, вы можетеНажмите, чтобы проверитьСмотреть
Если вам это нравится, вы можете дать мне звезду,github
благодарныйАйне_ДжииCaiBoBoИдеи предоставлены двумя учителями.