Введение в внутреннюю маршрутизацию
Концепция маршрутизации впервые появилась в бэкенде. Это часто наблюдалось при разработке страниц с помощью шаблонизаторов в прошлом.
http://www.xxx.com/login
Общий процесс можно представить так:
-
Браузер делает запрос
-
Сервер прослушивает запрос на порту 80 (или 443) и анализирует URL-адрес.
-
В соответствии с конфигурацией маршрутизации сервера вернуть соответствующую информацию (это может быть строка html или данные json, изображения и т. д.)
-
Браузер решает, как анализировать данные на основе Content-Type пакета.
Проще говоря, маршрутизация — это способ взаимодействия с бэкэнд-сервером, запрос разных ресурсов по разным путям, а запрос разных страниц — одна из функций маршрутизации.
Внешняя маршрутизация
1. хеш-режим
Благодаря популярности ajax взаимодействие с асинхронными запросами данных выполняется без обновления браузера. Более продвинутая версия асинхронного интерактивного взаимодействия — это SPA — одностраничное приложение. Одностраничные приложения не только не обновляются при взаимодействии со страницей, но и перескакивают страницы без обновления.Для реализации одностраничных приложений существует интерфейсная маршрутизация. Подобно маршрутизации на стороне сервера, интерфейсную маршрутизацию на самом деле очень просто реализовать: она сопоставляет разные пути URL, анализирует их и динамически отображает региональный HTML-контент. Но в этом есть проблема, то есть каждый раз при изменении url страница будет обновляться. Идея решения проблемы состоит в том, чтобы гарантировать, что страница не обновляется при изменении URL-адреса. До 2014 года все реализовывали маршрутизацию через хеш, а хэш URL был похож на:
http://www.xxx.com/#/login
Этот #. Последующее изменение значения хеша не приведет к тому, что браузер отправит запрос на сервер, если браузер не отправит запрос, страница не будет обновлена. Кроме того, каждый раз, когда значение хеш-функции изменяется, также срабатываетhashchange
Это событие, благодаря которому мы можем узнать, какие изменения произошли с хеш-значением. Тогда мы можем отслеживатьhashchange
Для реализации операции обновления части страницы:
function matchAndUpdate () {
// todo 匹配 hash 做 dom 更新操作
}
window.addEventListener('hashchange', matchAndUpdate)
2. режим истории
14 лет спустя с момента выпуска стандарта HTML5. Есть еще два API,pushState
иreplaceState
, через эти два API URL-адрес можно изменить без отправки запроса. Такжеpopstate
событие. С их помощью интерфейсную маршрутизацию можно реализовать по-другому, но принцип тот же, что и при реализации хеширования. С внедрением HTML5 в адресе одностраничного маршрута не будет лишнего #, и он станет красивее. Но из-за отсутствия знака #, когда пользователь обновляет страницу и выполняет другие операции, браузер все равно будет отправлять запрос на сервер. Чтобы избежать такой ситуации, для данной реализации требуется поддержка сервера, а все маршруты нужно перенаправлять на корневую страницу.
function matchAndUpdate () {
// todo 匹配路径 做 dom 更新操作
}
window.addEventListener('popstate', matchAndUpdate)
Реализация маршрутизатора Vue
Давайте взглянемvue-router
Как это определяется:
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
routes: [...]
})
new Vue({
router
...
})
можно увидетьvue-router
черезVue.use
Метод внедряется в экземпляр Vue, и нам нужно использовать его глобально при его использовании.vue-router
изrouter-view
иrouter-link
компоненты иthis.$router/$route
такой экземпляр объекта. Так как же реализуются эти операции? Далее я познакомлю вас с деталями в нескольких главах.vue-router
Мир.
реализация vue-router -- установить
реализация vue-router -- новый VueRouter (опции)
Реализация vue-router -- HashHistory
реализация vue-router — история HTML5
Реализация vue-router — мониторинг изменения маршрута
Создание колес — практическая реализация маршрутизатора, управляемого данными
После вышеизложенного я считаю, что у вас естьvue-router
Получил некоторое общее представление. Итак, чтобы реализовать обезжиренное решение, давайте возьмем за руку решение, основанное на данных, подобное следующему.router
:
new Router({
id: 'router-view', // 容器视图
mode: 'hash', // 模式
routes: [
{
path: '/',
name: 'home',
component: '<div>Home</div>',
beforeEnter: (next) => {
console.log('before enter home')
next()
},
afterEnter: (next) => {
console.log('enter home')
next()
},
beforeLeave: (next) => {
console.log('start leave home')
next()
}
},
{
path: '/bar',
name: 'bar',
component: '<div>Bar</div>',
beforeEnter: (next) => {
console.log('before enter bar')
next()
},
afterEnter: (next) => {
console.log('enter bar')
next()
},
beforeLeave: (next) => {
console.log('start leave bar')
next()
}
},
{
path: '/foo',
name: 'foo',
component: '<div>Foo</div>'
}
]
})
расположение идей
Первый управляется данными, поэтому мы можем передатьroute
объект для представления текущего состояния маршрутизации, например:
current = {
path: '/', // 路径
query: {}, // query
params: {}, // params
name: '', // 路由名
fullPath: '/', // 完整路径
route: {} // 记录当前路由属性
}
current.route
В нем хранится информация о конфигурации текущего маршрута, поэтому нам нужно только слушатьcurrent.route
меняется на динамическийrender
страница.
Затем нам нужно отслеживать различные изменения маршрутизации и реагировать на них соответствующим образом. и реализоватьhash
иhistory
модель.
управляемый данными
Здесь мы расширяемvue
Модель, управляемая данными, реализация простого захвата данных и обновление представления. Сначала определите нашуobserver
class Observer {
constructor (value) {
this.walk(value)
}
walk (obj) {
Object.keys(obj).forEach((key) => {
// 如果是对象,则递归调用walk,保证每个属性都可以被defineReactive
if (typeof obj[key] === 'object') {
this.walk(obj[key])
}
defineReactive(obj, key, obj[key])
})
}
}
function defineReactive(obj, key, value) {
let dep = new Dep()
Object.defineProperty(obj, key, {
get: () => {
if (Dep.target) {
// 依赖收集
dep.add()
}
return value
},
set: (newValue) => {
value = newValue
// 通知更新,对应的更新视图
dep.notify()
}
})
}
export function observer(value) {
return new Observer(value)
}
Далее нам нужно определитьDep
иWatcher
:
export class Dep {
constructor () {
this.deppend = []
}
add () {
// 收集watcher
this.deppend.push(Dep.target)
}
notify () {
this.deppend.forEach((target) => {
// 调用watcher的更新函数
target.update()
})
}
}
Dep.target = null
export function setTarget (target) {
Dep.target = target
}
export function cleanTarget() {
Dep.target = null
}
// Watcher
export class Watcher {
constructor (vm, expression, callback) {
this.vm = vm
this.callbacks = []
this.expression = expression
this.callbacks.push(callback)
this.value = this.getVal()
}
getVal () {
setTarget(this)
// 触发 get 方法,完成对 watcher 的收集
let val = this.vm
this.expression.split('.').forEach((key) => {
val = val[key]
})
cleanTarget()
return val
}
// 更新动作
update () {
this.callbacks.forEach((cb) => {
cb()
})
}
}
Здесь мы реализовали простой подписчик-публикатор, поэтому нам нужноcurrent.route
Совершайте захват данных. однаждыcurrent.route
Обновить, мы можем вовремя обновить текущую страницу:
// 响应式数据劫持
observer(this.current)
// 对 current.route 对象进行依赖收集,变化时通过 render 来更新
new Watcher(this.current, 'route', this.render.bind(this))
Что ж... на данный момент мы, похоже, завершили простое обновление данных. фактическиrender
То есть динамически отображать соответствующий контент для указанной области страницы, здесь только упрощенный вариантrender
:
render() {
let i
if ((i = this.history.current) && (i = i.route) && (i = i.component)) {
document.getElementById(this.container).innerHTML = i
}
}
хеш и история
Далееhash
иhistory
Реализация шаблона, здесь мы можем продолжать использоватьvue-router
Идея состоит в том, чтобы установить различные модели обработки. Давайте посмотрим на основной код, который я реализовал:
this.history = this.mode === 'history' ? new HTML5History(this) : new HashHistory(this)
Когда страница меняется, нам просто нужно слушатьhashchange
иpopstate
События, преобразование маршрутизацииtransitionTo
:
/**
* 路由转换
* @param target 目标路径
* @param cb 成功后的回调
*/
transitionTo(target, cb) {
// 通过对比传入的 routes 获取匹配到的 targetRoute 对象
const targetRoute = match(target, this.router.routes)
this.confirmTransition(targetRoute, () => {
// 这里会触发视图更新
this.current.route = targetRoute
this.current.name = targetRoute.name
this.current.path = targetRoute.path
this.current.query = targetRoute.query || getQuery()
this.current.fullPath = getFullPath(this.current)
cb && cb()
})
}
/**
* 确认跳转
* @param route
* @param cb
*/
confirmTransition (route, cb) {
// 钩子函数执行队列
let queue = [].concat(
this.router.beforeEach,
this.current.route.beforeLeave,
route.beforeEnter,
route.afterEnter
)
// 通过 step 调度执行
let i = -1
const step = () => {
i ++
if (i > queue.length) {
cb()
} else if (queue[i]) {
queue[i](step)
} else {
step()
}
}
step(i)
}
}
Таким образом, с одной стороны, мы проходимthis.current.route = targetRoute
Обновление ранее захваченных данных достигается для достижения обновления представления. С другой стороны, мы реализуем базовую функцию ловушки через планирование очереди задач.beforeEach
,beforeLeave
,beforeEnter
,afterEnter
.
Здесь почти то же самое, попутно реализуем несколько API:
/**
* 跳转,添加历史记录
* @param location
* @example this.push({name: 'home'})
* @example this.push('/')
*/
push (location) {
const targetRoute = match(location, this.router.routes)
this.transitionTo(targetRoute, () => {
changeUrl(this.router.base, this.current.fullPath)
})
}
/**
* 跳转,添加历史记录
* @param location
* @example this.replaceState({name: 'home'})
* @example this.replaceState('/')
*/
replaceState(location) {
const targetRoute = match(location, this.router.routes)
this.transitionTo(targetRoute, () => {
changeUrl(this.router.base, this.current.fullPath, true)
})
}
go (n) {
window.history.go(n)
}
function changeUrl(path, replace) {
const href = window.location.href
const i = href.indexOf('#')
const base = i >= 0 ? href.slice(0, i) : href
if (replace) {
window.history.replaceState({}, '', `${base}#/${path}`)
} else {
window.history.pushState({}, '', `${base}#/${path}`)
}
}
Это в основном здесь. Адрес источника:
Если вам интересно, вы можете играть сами. Реализация относительно сырая, если у вас есть какие-либо вопросы, пожалуйста, дайте указатели.