Заявление: Эта статья является первой подписанной статьей сообщества Nuggets, и ее перепечатка без разрешения запрещена.
написать впереди
VueRouter, несомненно, постоянно используется каждым разработчиком Vue, но что вы знаете о его исходном коде?
Я полагаю, что когда большинство фронтендов говорят о маршрутизации, они могут сказать, что их ядроhash
а такжеhistory
два режима,hash
режим прослушиванияhashchange
выполнение мероприятия,history
режим прослушиванияpopstate
повторное использование событияpushstate
Измените URL-адрес для достижения, как вы думаете, вы понимаете? Или вы действительно думаете, что знание этого — суть VueRouter? Нет, далеко не так! ! !
На самом деле, мне нравятся большинство людей, не поставили перед умом VuerOter, что это очень простое. Но когда я начал читать источник Vuerooter, это не так просто, как я думал. Источник Vuerooter об общей структуре простой, но детали все еще хотят читать трудные, загадочные различные функции для достижения разделения, а также некоторые детали заставляют меня хотеть молчать, поэтому я прочитал исходную кошку Подумайте о глубине глубины этого способа, не желая направлять GAN две ночи перед большими целями.
В центре внимания этой статьи
Без лишних слов, давайте посмотрим, что вы можете узнать, прочитав эту статью?
Привнес немного здравого смысла в Router и написал упрощенную версию VueRouter (большинство основных функций).В отличие от большинства рукописных статей, код здесь полностью реализован шаг за шагом в соответствии со стандартом исходного кода, включая общую архитектуру. , API и т. д. все одинаковы. Следите за этой статьей еще раз. В дополнение к глубокому пониманию основного исходного кода вы можете легко получить доступ к деталям исходного кода позже. Кажется, что настоящий исходный код можно сказать без преувеличения: вертикальный Наслаждайтесь шелковистая гладкость!
Советы перед чтением
Эта статья основана на последней и наиболее стабильной версии VueRouter V3.5.2, 4.0+ все еще впереди, поэтому она выходит за рамки этой статьи.
Статья с исходным кодом очень скучная и мало кто ее читает, потому что она сложна для понимания и не несет практического удовольствия, поэтому рекомендуется вынуть редактор и следовать за ним руками.
Что касается написанной вручную реализации VueRouter в этой статье, то она в основном включает:
- маршрутизация в режиме хэш/история
- Вложенные маршруты
- компоненты router-view/router-link
- route
- такие методы, как push/replace/go/back
- addRoute/addRoutes/getRouters
- router hook
Нереализованная часть также даст общее введение, и я аннотирую исходный код, который я только что клонировал, и поместил его в каталог проекта рукописного исходного кода (ссылка в конце статьи).Перейдите непосредственно к исходному коду, набор комбинаций ударов, неплохо, давай~
Перед началом можно просто посмотреть три блок-схемы, соответствующие всему Vuerouter, не понять, есть общее впечатление, и в конце статьи будет эта цифра.
Принцип реализации маршрутизации передней части
Внешняя маршрутизация означает, что внешний интерфейс отслеживает изменения URL-адресов, чтобы управлять отображением компонентов на странице, чтобы обеспечить переходы без обновления страницы.Хотя пользователи чувствуют, что они представляют собой группу разных страниц, на самом деле все они находятся на одной странице. Чтобы реализовать интерфейсную маршрутизацию, нам необходимо учитывать два момента:
- URL меняется, но страница не обновляется?
- Отслеживать изменения URL?
Далее давайте посмотрим, как решаются два режима Hash и History.
Простая реализация хеш-маршрутизации
Режим хеширования фактически переключает маршрут, изменяя хеш-значение после # в URL-адресе, потому что изменение хэш-значения в URL-адресе не приведет к обновлению страницы, а затем используйте событие hashchange для отслеживания изменения хеш-значения на управлять отрисовкой компонентов страницы. Небольшой пример:
<!DOCTYPE html>
<html lang="en">
<body>
<a href="#/home">home</a>
<a href="#/about">about</a>
<!-- 渲染路由模块 -->
<div id="view"></div>
</body>
<script>
let view = document.querySelector("#view")
let cb = () => {
let hash = location.hash || "#/home";
}
window.addEventListener("hashchange", cb)
window.addEventListener("load", cb)
</script>
</html>
Как и выше, изменение хеш-значения маршрутизации с помощью двух тегов a эквивалентноrouter-link
компонент, страницаid=view
div мы можем понять это какrouter-view
Component, после загрузки страницы сначала выполните функцию cb для инициализации модулей хеширования и маршрутизации.После нажатия тега a изменение маршрутизации будет отслеживаться с помощью hashchange, чтобы вызвать обновление модуля маршрутизации.
Простая реализация маршрутизации истории
Существует также способ без #, то есть история, которая предоставляет два метода, pushState и replaceState. Используя эти два метода, вы можете изменить путь URL-адреса, не вызывая обновления страницы. В то же время, он также предоставляет событие popstate для мониторинга изменений маршрута, но события popstate не срабатывают при их изменении, как это делает hashchange.
- Изменение URL-адреса при переходе вперед или назад в браузере вызовет событие popstate.
- js может вызвать это событие, вызвав методы back, go, forward и другие методы historyAPI.
Давайте посмотрим, как он реализует мониторинг маршрутизации:
<!DOCTYPE html>
<html lang="en">
<body>
<a href='/home'>home</a>
<a href='/about'>about</a>
<!-- 渲染路由模块 -->
<div id="view"></div>
</body>
<script>
let view = document.querySelector("#view")
// 路由跳转
function push(path = "/home"){
window.history.pushState(null, '', path)
update()
}
// 更新路由模块视图
function update(){
view.innerHTML = location.pathname
}
window.addEventListener('popstate', ()=>{
update()
})
window.addEventListener('load', ()=>{
let links = document.querySelectorAll('a[href]')
links.forEach(el => el.addEventListener('click', (e) => {
// 阻止a标签默认行为
e.preventDefault()
push(el.getAttribute('href'))
}))
push()
})
</script>
</html>
Как и выше, тег arouter-link
компонент, divrouter-view
компоненты.
Поскольку событие popstate может отслеживать браузер только вперед и назад и использовать API истории вперед и назад, в дополнение к операции обновления в мониторинге событий также необходимо вручную обновлять модуль маршрутизации при переходе.
Таким образом можно добиться того же эффекта, что и при использовании хэша.В то же время, поскольку тег a имеет поведение перехода по щелчку по умолчанию, мы предотвращаем это поведение. В то же время мы можем изменить обновление URL-адреса прямо в браузере, но в этом примере это не поддерживается, поскольку для этого требуется взаимодействие с серверной частью.
Вышеупомянутый упрощенный принцип режима хеширования и режима истории Зная эти основы, мы можем начать писать VueRouter
Анализ использования VueRouter
Прежде чем писать VueRouter, нам нужно проанализировать уровень его использования, чтобы увидеть, что у него есть.Давайте сначала рассмотрим его использование:
- Внесите VueRouter в файл конфигурации маршрутизации и используйте его как плагин.
- Настройте объект маршрутизации в файле конфигурации маршрутизации, чтобы создать экземпляр маршрутизации и экспортировать его.
- Смонтируйте экземпляр маршрутизатора, экспортированный файлом конфигурации, в корневой экземпляр Vue.
Все шаги следующие:
// router/index.js
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "Home",
component,
},
{
path: "/about",
name: "About",
component,
}
];
const router = new VueRouter({
mode: "hash",
base: process.env.BASE_URL,
routes,
});
export default router;
В файле проекта main.js:
// main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
new Vue({
router,
render: (h) => h(App),
}).$mount("#app");
Как видите, VueRouter можно создать как класс, а также загрузить как плагин Vue.
Создание экземпляра легко понять, но зачем загружать плагин?
Когда мы используем VueRouter, мы часто используемrouter-link
а такжеrouter-view
Два компонента, мы не нашли куда внедрить эти два компонента, вы когда-нибудь задумывались, почему их можно использовать глобально? На самом деле он регистрируется глобально, когда VueRouter инициализируется как плагин.
Во время использования мы можем использоватьthis.$router
Получите экземпляр маршрутизации, и там будут такие объекты, какpush/go/back
и другие методы, вы также можетеthis.$route
чтобы получить объект маршрута только для чтения, который включает в себя наш текущий маршрут и некоторые параметры и т. д.
Подготовка перед рукописным вводом
Строительство проекта
Создайте проект Vue и используйте терминал, чтобы ввести следующую команду для создания проекта Vue:
vue create hello-vue-router
Обратите внимание, что при сборке выбран VueRouter!
Сборка завершается напрямуюyarn serve
Запустите, как показано ниже, очень знакомый интерфейс:
Тогда мыsrc/
создать новую папкуhello-vue-router/
, код VueRouter, который мы написали сами, находится в этой папке.
Создать новыйindex.js
Задокументируйте, экспортируйте пустой класс Vuerouter:
/*
* @path: src/hello-vue-router/index.js
* @Description: 入口文件 VueRouter类
*/
export default class VueRouter(){
constructor(options){}
}
Затем перейдите к файлу конфигурации маршрутизацииsrc/router/index.js
, замените импортированный VueRouter нашим собственным и измените режим маршрутизации на хэш, потому что сначала мы должны реализовать хэш-режим, как показано ниже:
import Vue from 'vue'
import VueRouter from '@/hello-vue-router/index'
// import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [...]
const router = new VueRouter({
mode: 'hash',
base: process.env.BASE_URL,
routes
})
export default router
Теперь страница становится пустой, а консоль сообщает об ошибке:
Cannot call a class as a function
Ошибка консоли говорит, что класс нельзя вызывать как функцию! ! !
Эй, а где ты сказал, что класс вызывается как функция?
ФактическиVue.use(VueRouter)
Это, говоря об этом, мы должны представить API этого плагина установки Vue.
Анализ исходного кода Vue.use()
Как следует, по сути, этот метод принимает параметр типа function или object. Если аргумент является объектом, он должен иметь метод свойства install. Независимо от того, является ли параметр функцией или объектом, конструктор Vue будет передан в качестве первого параметра при выполнении метода установки или самой функции.
Таким образом, когда мы пишем плагин, если мы пишем функцию или объект с атрибутом функции установки, мы можем получить конструктор Vue, и мы можем использовать его для некоторых вещей.Это очень просто!
Vue.use = function (plugin: Function | Object) {
// installedPlugins为已安装插件列表,若 Vue 构造函数不存在_installedPlugins属性,初始化
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 判断当前插件是否在已安装插件列表,存在直接返回,避免重复安装
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// toArray方法将Use方法的参数转为数组并删除了第一个参数(第一个参数就是我们的插件)
const args = toArray(arguments, 1)
// use是构造函数Vue的静态方法,那这里的this就是构造函数Vue本身
// 把this即构造函数Vue放到参数数组args的第一项
args.unshift(this)
if (typeof plugin.install === 'function') {
// 传入参数存在install属性且为函数
// 将构造函数Vue和剩余参数组成的args数组作为参数传入install方法,将其this指向插件对象并执行install方法
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
// 传入参数是个函数
// 将构造函数Vue和剩余参数组成的args数组作为参数传入插件函数并执行
plugin.apply(null, args)
}
// 像已安装插件列表中push当前插件
installedPlugins.push(plugin)
return this
}
Метод установки предварительной сборки
Теперь давайте начнем писать код! Теперь, когда вы знаете, как Vue загружает плагины, это легко, потому что мы экспортируем класс VueRouter, который также является объектом, поэтому добавьте к нему метод установки.
немного изменитьindex.js
, добавьте статический метод install в класс VueRouter:
/*
* @path: src/hello-vue-router/index.js
* @Description: 入口文件 VueRouter类
*/
import { install } from "./install";
export default class VueRouter(){
constructor(options){}
}
VueRouter.install = install;
затем вsrc/hello-vue-router/
создать каталогinstal.js
, экспортировать метод установки, мы виделиVue.use()
В исходном коде метода должно быть известно, что первым параметром этого метода является конструктор Vue, а именно:
/*
* @path: src/hello-vue-router/install.js
* @Description: 插件安装方法install
*/
export function install(Vue){}
Это также было проанализировано выше: когда плагин установлен, метод установки будет глобально монтировать два компонента в Vue.router-view
а такжеrouter-link
.
Вы знаете, мы делаем только 2 вещи в файле конфигурации маршрутизатора, чтобы инициализировать плагин VueRouter и создать экземпляр VueRouter, затем мы обычно используем его непосредственно в проекте.this.$router & this.$route
Откуда это?
первый$router
является экземпляром объекта VueRouter,$route
текущий объект маршрутизации,$route
На самом деле это$router
Свойство , эти два объекта доступны во всех компонентах Vue.
Некоторые друзья могут еще помнить вступительный файл проектаmain.js
, мы подключаем экспортированный экземпляр маршрутизатора к корневому экземпляру Vue следующим образом:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: function (h) { return h(App) }
}).$mount('#app')
Но проблема возникает снова, мы просто монтируем его в корневой экземпляр, не каждый компонент прикрепляется, и Vue помещает объекты, непосредственно смонтированные в экземпляре Vue, в текущий экземпляр для нас.$options
С точки зрения свойств, в сочетании с тем фактом, что мы подключены только к корневому экземпляру, мы можем получить доступ к объекту экземпляра маршрутизатора, только взявthis.$root.$options.router
чтобы получить это, здесьthis.$root
Полученный корневой экземпляр является корневым экземпляром.
Очевидно, внешнее так не называется.
так,$router & $route
Эти два свойства можно смонтировать только внутри компонента VueRouter, а также их необходимо использовать всеми компонентами во время разработки проекта Vue.
Подробности, как получить его объект-экземпляр в компоненте VueRouter (как получить новый объект VueRouter в этом классе)?
Некоторые друзья могли подумать, что этот экземпляр маршрутизатора смонтирован в корневом экземпляре Vue, да, это маршрутизатор, который передается при создании нового Vue. Вы можете получить это, как вы можете это получить?
Как упоминалось выше, мы можем сначала получить корневой экземпляр Vue, а затем использовать$options.router
Чтобы получить атрибут маршрутизатора, смонтированный на экземпляре, то есть в настоящее время рассматривается вопрос о том, как получить экземпляр компонента Vue в VueRouter (с экземпляром компонента вы можете получить экземпляр корневого компонента для доступа к нему.$options
Атрибуты)
Эй, кажется, я снова подумал об этом. Метод установки VueRouter будет передан в конструктор Vue. Может ли он что-то делать?
Конструктор есть конструктор, это конечно не экземпляр, но конструктор у Vue естьmixin
путь, да混入
маленький подсказки: v УЭ.Суеверие
Подсчитано, что этот метод известен многим, но его необходимо внедрить.
Миксин делится на глобальный миксин и миксин компонента.Мы напрямую используем конструктор Vue.mixin.Это глобальный миксин.Он получает объектный параметр.В этом объектном параметре мы можем написать что угодно в компоненте Vue,а потом пишем это Куча будет смешана (также понимаемая как объединенная) с каждым компонентом Vue.
Например, если написать жизненный цикл и прописать в нем логику, то во всех компонентах Vue смешанная нами логика будет выполняться до начала жизненного цикла. Все еще не знаю? Для другого примера мы написали
methods
, который пишет функцию, то эта функция будет смешана со всеми компонентами Vuemethods
, все компоненты можно вызывать напрямую.
Vue.mixin может напрямую записывать набор компонентов, что очень просто, можно записать глобальный миксин жизненного цикла в компонент.
Тогда снова возникает вопрос, в каком жизненном цикле это записано? На самом деле тоже все просто, достаточно посмотреть, какой жизненный цикл$options
Его можно построить,beforeCreate
этот цикл$options
Он построен, то есть его можно использовать после этого жизненного цикла$options
, нужно ли спрашивать? Конечно, чем раньше, тем лучше, т.beforeCreate
этот жизненный цикл.
Опять же, метод установки может передать параметр конструктору Vue и использовать миксин статического метода конструктора Vue для всех наших компонентов.beforeCreate
Жизненный цикл перемешан с кусочком логики, который на нем смонтирован$router & $route
Атрибуты
Согласно нашей логике выше, мы начнем с полного кода, а затем объясним его шаг за шагом:
/*
* @path: src/hello-vue-router/install.js
* @Description: 插件安装方法install
*/
export let _Vue;
export function install(Vue){
if (install.installed && _Vue === Vue) return;
install.installed = true;
_Vue = Vue;
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
this._routerRoot = this;
this._router = this.$options.router;
this._route = {};
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
},
});
Object.defineProperty(Vue.prototype, "$router", {
get() {
return this._routerRoot._router;
},
});
Object.defineProperty(Vue.prototype, '$route', {
get() {
return this._routerRoot._route;
}
});
Vue.component('RouterView', {});
Vue.component('RouterLink', {});
}
Чтобы объяснить это блоком:
export _Vue;
export function install(Vue){
if (install.installed && _Vue === Vue) return;
install.installed = true;
_Vue = Vue;
}
А? Файл установки не только экспортирует метод установки, но также экспортирует переменную _Vue.Что это такое?
При инициализации плагина выполняется метод install.В этом методе строковый параметр, то есть конструктор Vue, присваивается переменной _Vue и экспортируется.На самом деле у этого _Vue две функции:
Во-первых, чтобы плагин не регистрировался и не устанавливался несколько раз, потому что в методе установки плагина мы добавляем установленный атрибут к этому методу.Когда этот атрибут существует и имеет значение true, а _Vue был назначен конструктору Vue, верните плагин зарегистрирован, перерегистрировать не нужно.
Вторая функция заключается в том, что в конструкторе Vue установлено множество практических API, которые мы можем использовать в классе VueRouter.Конечно, мы также можем использовать его API, представив Vue, но как только пакет будет введен, весь Vue будет упакован. Упаковано, даже если конструктор будет передан в качестве параметра в инсталле, бывает, что когда мы пишем конфигурационный файл роутера, установочный плагин (Vue. Мы присваиваем параметр конструктора переменной и используем его в Класс VueRouter идеален, если не понимаете, то просто посмотрите на картинку⬇️
Далее разберем микс-ин, по сути, грубо говоря, это и есть крепление$router & $route
:
export function install(Vue){
// 全局注册混入,每个 Vue 实例都会被影响
Vue.mixin({
// Vue创建前钩子,此生命周期$options已挂载完成
beforeCreate() {
// 通过判断组件实例this.$options有无router属性来判断是否为根实例
// 只有根实例初始化时我们挂载了VueRouter实例router(main.js中New Vue({router})时)
if (this.$options.router) {
this._routerRoot = this;
// 在 Vue 根实例添加 _router 属性( VueRouter 实例)
this._router = this.$options.router;
this._route = {};
} else {
// 为每个组件实例定义_routerRoot,回溯查找_routerRoot
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
},
});
// 在 Vue 原型上添加 $router 属性( VueRouter )并代理到 this._routerRoot._router
Object.defineProperty(Vue.prototype, "$router", {
get() {
return this._routerRoot._router;
},
});
// 在 Vue 原型上添加 $route 属性( 当前路由对象 )并代理到 this._routerRoot._route
Object.defineProperty(Vue.prototype, '$route', {
get() {
return this._routerRoot._route;
}
});
}
Смотрим что сделано:
Сначала напишите миксин, зарегистрируйте миксин глобально, чтобы он затронул каждый экземпляр Vue. Напишите хук beforeCreate в миксине, потому что этот жизненный циклoptions имеет атрибут маршрутизатора, чтобы определить, является ли он корневым экземпляром. Маршрутизатор экземпляра VueRouter монтируется только при инициализации корневого экземпляра (то есть, когда New Vue({router}) в main.js).
является корневым экземпляром:
Если это корневой экземпляр, добавьте к нему атрибут _router, значением является экземпляр VueRouter, и добавьте атрибут _routerRoot для его монтирования, который является корневым экземпляром.
Как проанализировано выше, здесь также должен быть объект маршрута, поэтому я добавил к нему атрибут _route в конце и установил его на пустой объект на данный момент, и я улучшу его позже.
не корневой экземпляр:
Если это не корневой экземпляр, это экземпляр дочернего компонента. Найдите его родительский экземпляр, чтобы определить, имеет ли его родительский экземпляр атрибут _routerRoot, а если нет, добавьте ссылку на него, чтобы убедиться, что каждый экземпляр компонента может иметь атрибут _routerRoot , то есть пусть каждый экземпляр компонента имеет атрибут _routerRoot.Каждый компонент может ссылаться и обращаться к корневому экземпляру.Обратите внимание, что это не повторяющиеся присваивания, а ссылки между объектами.
Наконец, чтобы сделать каждый компонент доступным$router $ $route
объект, который мы добавили в прототип Vueroute属性并代理到
this._routerRoot._route`, остальное — создать глобальный компонент:
// 全局注册组件router-view
Vue.component('RouterView', {});
// 全局注册组件router-link
Vue.component('RouterLink', {});
Эта часть пока относительно проста: два компонента регистрируются глобально с помощью Vue.component, а объекты конфигурации непосредственно пусты. Ниже приведена простая конфигурация этих двух глобальных компонентов, позволяющая запустить проект, в конце концов, операция по-прежнему сообщает об ошибках.
Предварительная сборка компонентов RouterView и RouterLink
Немного разделившись, мы вsrc/hello-vue-router/
Создайте новый в каталогеcomponents/
папка
существуетcomponents
новая папкаview.js
а такжеlink.js
Два файла, а затем вам нужно сначала изменить метод установки:
/*
* @path: src/hello-vue-router/install.js
* @Description: 插件安装方法install
*/
import View from "./components/view";
import Link from "./components/link";
export function install(Vue){
// 全局注册组件router-view
Vue.component('RouterView', view);
// 全局注册组件router-link
Vue.component('RouterLink', link);
}
Видно, что мы вытащили объекты конфигурации двух компонентов отдельно и записали их в два файла, по сути, каждый файл экспортирует объект конфигурации компонента.
Первый взглядlink.js
, компонент ссылки аналогичен тегу a. На самом деле он по умолчанию отображает тег a. Компонент получает параметр to, который может быть объектом или строкой, которая используется в качестве перехода.
<router-link to="/home">
<router-link :to="{path: '/home'}">
См. реализацию:
/*
* @path: src/hello-vue-router/components/link.js
* @Description: router-link
*/
export default {
name: "RouterLink",
props: {
to: {
type: [String, Object],
require: true
}
},
render(h) {
const href = typeof this.to === 'string' ? this.to : this.to.path
const router = this.$router
let data = {
attrs: {
href: router.mode === "hash" ? "#" + href : href
}
};
return h("a", data, this.$slots.default)
}
}
Во-первых, props получает параметр to, обязательную опцию, которая может быть типа объекта или строки.В функции рендеринга сначала оценивается тип параметра to, и он объединяется в объект.
Затем получите доступ к корневому экземпляру в$router
, это на самом деле прокси, после вывода вы узнаете, что этот прокси проксирует экземпляр VueComponent, и мы добавляем атрибут _routerRoot, указывающий на корневой экземпляр, к каждому экземпляру компонента при установке, здесь мы действительно хотим получить доступ к маршрутизатору объект Есть много видов.
// this._self._routerRoot._router
// this._routerRoot._router
// this.$router
Вы можете использовать любой, но третий тип исходного кода, мы также используем его, он может иметь наименьшее количество символов
Следующим шагом будет возврат VNode.На самом деле параметр h рендера это функция createElement, которая используется для создания VNode.Её параметры описаны на официальном сайте:
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',
// {Object}
// 一个与模板中 attribute 对应的数据对象。可选。
{
// (详情见下一节)
},
// {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
Здесь мы хотим вернуть тег a, поэтому первым параметром является строка a, а вторым параметром является объект данных, соответствующий атрибуту тега.Чтобы вывести ему атрибут href, значением атрибута является параметр to, а режим на что нужно обратить внимание: Проблема в том, что в хеш-режиме перед всеми путями перехода добавляется знак #, поэтому необходимоrouter.mode
Судя по режиму, третий параметр — это дочерний узел, т.е.router-link
Значение, содержащееся в компоненте, можно получить, используя слот по умолчанию.this.$slots.default
Получить слот по умолчанию.
Хорошо, поехалиrouter-link
Компонент почти готов, но еще есть проблемы в режиме истории, о которых мы поговорим позже.
увидеть сноваview.js
, На самом деле нам не нужен компонент RouterView для рендеринга чего-либо.В лучшем случае это заполнитель для замены нашего UI модуля компонента, поэтому одному не нужен жизненный цикл, второму не требуется управление состоянием, а третьему не требуется различные мониторинги, популярные Дело в том, что нет необходимости создавать экземпляр.В качестве трехбесплатного компонента больше всего подходят функциональные компоненты.
/*
* @path: src/hello-vue-router/components/view.js
* @Description: router-view
*/
export default {
name: "RouterView",
functional: true, // 函数式组件
render(h) {
return h('div', 'This is RoutePage')
}
}
Как и выше, прямо установите его в функциональный компонент, а затем функция рендеринга напрямую вернет div с содержимым'This is RoutePage'
(функция h, то есть функция createElement, не имеет второго параметра, который можно опустить), здесь просто предварительная структура, логика будет рассмотрена позже, пусть сначала запустится страница, теперь вы открываете браузер и вы обнаружите, что ошибки нет, навигация также доступна, и вы можете нажать, чтобы переключить маршруты, то есть компонент модуля маршрутизацииrouter-view
всегда только показыватьThis is RoutePage
,следующим образом:
Предварительная конструкция класса Vuerouter
Метод установки можно пока закончить и подумать, что нам нужно сделать в классе VueRouter?
В первую очередь необходимо анализировать параметры при получении параметров.Передается объект, и основными из них являются два атрибута:
- режим маршрутизации
- Маршруты Маршрута маршрутизации Массив конфигурации
На самом деле базовый атрибут тоже важнее, но на это можно сначала не обращать внимания, после того, как логика проработает, будет время его улучшить
Думая о конфигурации режима, нам нужно инициализировать некоторые вещи в соответствующем режиме в соответствии с режимом маршрутизации, переданным режимом, чтобы реализовать мониторинг маршрутизации в этом режиме.
Затем снова подумайте о массиве маршрутов, что нам нужно сделать?
На самом деле самая важная конфигурация в этом массиве это путь маршрутизации и компонент маршрутизации соответствующий пути.Конечно, есть еще некоторые настройки перенаправления, динамической маршрутизации, имени маршрутизации, псевдонима маршрутизации, которые пока не рассматриваются , и будет постепенно улучшаться позже.
Вопрос в том, что нам нужно делать при прослушивании смены маршрута?
Разумеется, получить измененный путь маршрута, найти соответствующую конфигурацию пути в массиве маршрутов, получить его компоненты, а затем отрендерить полученные компоненты в соответствующийrouter-view
входить.
Для конфигурации маршрутов цель очень ясна, потому что это объект массива с древовидной структурой, мы сопоставляем на основе пути, что очень неудобно, поэтому эту конфигурацию необходимо проанализировать заранее как{key : value}
В этой структуре, разумеется, ключ — это наш путь, а значение — элемент конфигурации этого маршрута. После завершения анализа начните вводить код:
/*
* @path: src/hello-vue-router/index.js
* @Description: 入口文件 VueRouter类
*/
import { install } from "./install";
import { createMatcher } from "./create-matcher";
import { HashHistory } from "./history/hash";
import { HTML5History } from "./history/html5";
import { AbstractHistory } from "./history/abstract";
const inBrowser = typeof window !== "undefined";
export default class VueRouter(){
constructor(options) {
// 路由配置
this.options = options;
// 创建路由matcher对象,传入routes路由配置列表及VueRouter实例,主要负责url匹配
this.matcher = createMatcher(options.routes);
let mode = options.mode || "hash";
// 支持所有 JavaScript 运行环境,非浏览器环境强制使用abstract模式,主要用于SSR
if (!inBrowser) {
mode = "abstract";
}
this.mode = mode;
// 根据不同mode,实例化不同history实例
switch (mode) {
case "history":
this.history = new HTML5History(this);
break;
case "hash":
this.history = new HashHistory(this);
break;
case "abstract":
this.history = new AbstractHistory(this);
break;
default:
if (process.env.NODE_ENV !== "production") {
throw new Error(`[vue-router] invalid mode: ${mode}`);
}
}
}
}
VueRouter.install = install;
На самом деле логика в конструкторе класса VueRouter очень проста: он оценивает режим входящего режима, а затем инициализирует разные экземпляры класса.Хотя экземпляры разных классов создаются, методы экземпляров, включая атрибуты, одинаковы.
Полный VueRouter имеет три режима:
- Хэш поддерживается всеми основными браузерами, но URL-адрес имеет знак #, что выглядит некрасиво.
- URL-адрес истории выглядит хорошо, но некоторые старые браузеры его не поддерживают.
- Аннотация поддерживает все среды, в основном используется для SSR Server Side SSR
То, что мы не очень хорошо знаем, может быть абстрактным режимом.На самом деле, этот режим официально определен как режим, который поддерживает любую среду, потому что этот режим предназначен для ручного моделирования среды маршрутизации, и исходный код также имеет ту же логическую схему. суд как выше(inBrowser
), то есть когда в текущей среде нет оконного объекта, то есть небраузерная среда, он напрямую принудительно переходит в этот режим, так что этот режим тоже в основном используется для SSR, и он достаточно прост реализовать его позже, если у вас есть энергия.
Весь конструктор на самом деле не имеет сложной логики. Сначала определите, есть ли в текущей среде объект окна, то есть является ли это средой браузера.Если да, продолжайте идти.Если нет, принудительно сделайте значение режима абстрактным, затем определите значение атрибута режима, и используйте соответствующий класс для инициализации режима маршрутизации, чтобы он соответствовал трем режимам. Например, отсутствие совпадения вызовет ошибку напрямую. Независимо от того, какой режим здесь, мы реализуем некоторые из тех же методов в соответствующем классе и смонтируем инициализированный экземпляр в атрибут истории экземпляра VueRouter.
На самом деле перед проверкой параметра режима также вводится метод createMatcher.Возвращаемое значение этого метода монтируется в атрибут matcher экземпляра VueRouter.Что он делает?
Вы должны были примерно догадаться, и, как я уже сказал выше, это, вероятно, сборка{key : value}
Объект структуры (называемый объектом pathMap) упрощает сопоставление соответствующего модуля маршрутизации по пути пути.
Затем мы шаг за шагом выведем, как инкапсулируется метод createMatcher.
Вывод метода createMatcher
Как вы думаете, метод createMatcher просто создает объект сопоставления pathMap? Нет, в этом случае имя функции должно называться createRouterMap, На самом деле это было действительно это имя в начале, но набор производных обнаружил, что он может не только создавать объект сопоставления pathMap, но иaddRoutes/addRoute/getRoutes
Эти методы также могут быть реализованы здесь.
Что делает конструкция объекта карты pathMap? Соответствие маршрута! Когда вы вводите путь, вы можете получить соответствующую информацию о конфигурации маршрутизации. Объект pathMap эквивалентен распорядителю данных маршрутизации. Все записанные конфигурации маршрутизации находятся здесь. При динамическом добавлении маршрута проанализируйте и добавьте новый объект маршрутизации в объект pathMap Итак, мы объединили все методы сопоставления маршрутов и динамической маршрутизации в функцию createMatcher, назовем ее路由匹配器函数
Что ж, основная функция заключается в создании объекта сопоставления маршрутов, и эта функция возвращает объект, содержащий четыре атрибута метода:
- соответствие маршруту соответствие
- Addroutes динамически добавляет маршруты (параметры должны соответствовать
routes
набор требований к опциям) - addRoute динамически добавлять маршрут (добавлять новое правило маршрутизации)
- getRoutes Получить список всех активных записей маршрута
createRouteMap создает карту маршрута
Прежде всего, мы должны построить объект pathMap, вытащить отдельный файл, чтобы написать этот метод, вsrc/hello-vue-router/
Создайте новый в каталогеcreate-route-map.js
документ:
/*
* @path: src/hello-vue-router/create-route-map.js
* @Description: 生成路由映射
*/
// 生成路由映射
export function createRouteMap(routes){
let routeMap = {}
routes.forEach(route => {
routeMap[route.path] = route
})
return routeMap
}
Как и выше, несколько строк кода генерируют объект карты маршрутизации pathMap. Нет проблем, но мы сопоставляем только один уровень выше, и в конфигурации маршрутизации может быть бесконечное количество уровней подмаршрутов, например следующая конфигурация:
const routes = [
{
path: "/about",
name: "About",
component,
},
{
path: "/parent",
name: "Parent",
component,
children:[
{
path: "child",
name:"Child",
component
}
]
}
];
Какой объект pathMap мы хотим сгенерировать, он выглядит так:
{
"/about": {...},
"/parent": {...},
"/parent/child": {...}
}
Но текущая логика кода генерирует только следующее:
{
"/about": {...},
"/parent": {...}
}
Есть проблема? Есть большая проблема, один слой роутинга ок, а многоуровневый вложенный роутинг прямо gameover. Поэтому для рекурсивной обработки парсинга и изменения кода, это все еще старая процедура, сначала посмотрите на полный код, а затем пошагово разберите его.
export function createRouteMap(routes){
const pathMap = Object.create(null);
// 递归处理路由记录,最终生成路由映射
routes.forEach(route => {
// 生成一个RouteRecord并更新pathMap
addRouteRecord(pathMap, route, null)
})
return pathMap
}
// 添加路由记录
function addRouteRecord(pathMap, route, parent){
const { path, name } = route
// 生成格式化后的path(子路由会拼接上父路由的path)
const normalizedPath = normalizePath(path, parent)
// 生成一条路由记录
const record = {
path: normalizedPath, // 规范化后的路径
regex: "", // 利用path-to-regexp包生成用来匹配path的增强正则对象,用来匹配动态路由 (/a/:b)
components: route.component, // 保存路由组件,省略了命名视图解析
name,
parent, // 父路由记录
redirect: route.redirect, // 重定向的路由配置对象
beforeEnter: route.beforeEnter, // 路由独享的守卫
meta: route.meta || {}, // 元信息
props: route.props == null ? {} : route.props// 动态路由传参
}
// 处理有子路由情况,递归
if (route.children) {
// 遍历生成子路由记录
route.children.forEach(child => {
addRouteRecord(pathMap, child, record)
})
}
// 若pathMap中不存在当前路径,则添加pathList和pathMap
if (!pathMap[record.path]) {
pathMap[record.path] = record
}
}
// 规格化路径
function normalizePath(
path,
parent
) {
// 下标0为 / ,则是最外层path
if (path[0] === '/') return path
// 无父级,则是最外层path
if (!parent) return path
// 清除path中双斜杆中的一个
return `${parent.path}/${path}`.replace(/\/\//g, '/')
}
На самом деле этот кусок кода относительно прост, да еще и закомментирован, всего несколько моментов.
Мы фактически форматируем каждый объект конфигурации маршрутизации в рекурсии и генерируем новый объект записи.Путь объекта фактически является полным путем, то есть, если исходный путь/
В начале это означает, что это маршрут верхнего уровня, а путь — это он сам.Если исходный путь не начинается с/
В начале указав, что это дочерний маршрут, затем нам нужно склеить родительский путь, по этой причине мы написали отдельную функцию normalizePath для генерации полного пути, то есть нормализации пути.
Поскольку родитель передается во время рекурсии, за исключением того, что маршрут верхнего уровня равен нулю, у дочерних маршрутов есть родители, а наша рекурсия дочернего маршрута происходит после создания объекта записи, поэтому каждый входящий родитель форматируется. Для хорошего объекта записи путь родителя также является полным путем, так что независимо от количества дочерних элементов можно указать полный путь.
Затем поговорим об объекте записи, мы также добавили к нему родительский атрибут, указывающий на его родительский объект, чтобы была связь между родителем и дочерним элементом, и некоторые настраиваемые параметры маршрутизации, такие как перенаправление.redirect
, эксклюзивный охранник маршрутаbeforeEnter
, метаинформацияmeta
, название маршрутаname
Они также принимаются и помещаются в объект записи.
говорить в одиночествеregex
атрибут, я думаю, все знают, что VueRouter поддерживает динамическую маршрутизацию, на самом деле он в основном использует трехсторонний пакетpath-to-regexp
Создайте расширенный регулярный объект, используемый для сопоставления пути с соответствующим динамическим маршрутом. После создания регулярного объекта поместите его наregex
В свойствах этот кусок особого значения для нашего почерка не имеет, поэтому я его писать не стал, а оставил пустым.Если вам интересно, можете прямо посмотреть исходники здесь.Главное этоpath-to-regexp
Использование этого пакета не сложно. Вдобавок последнийprops
Атрибуты используются для параметров динамической маршрутизации, так что пока их можно игнорировать.
В конце концов, сгенерированный объект PathMap[{path: record}...]
В этом формате ключ представляет собой форматированный полный путь, а значение — форматированную запись объекта конфигурации маршрутизации.
На этом метод разбора объекта карты маршрута pathMap почти завершен.
createMatcher генерирует сопоставители маршрутов
Далее мыsrc/hello-vue-router/
создать папкуcreate-matcher.js
Файл, согласно нашему анализу выше, примерно структурирован следующим образом:
/*
* @path: src/hello-vue-router/create-route-map.js
* @Description: 路由匹配器Matcher对象生成方法
*/
import { createRouteMap } from "./create-route-map";
export function createMatcher(routes){
// 生成路由映射对象 pathMap
const pathMap = createRouteMap(routes)
// 动态添加路由(添加一条新路由规则)
function addRoute(){ }
// 动态添加路由(参数必须是一个符合 routes 选项要求的数组)
function addRoutes(){ }
// 获取所有活跃的路由记录列表
function getRoutes(){ }
// 路由匹配
function match(){ }
return {
match,
addRoute,
getRoutes,
addRoutes
}
}
Метод создания объекта Matcher для сопоставления маршрутов — createMatcher , нам нужен только один параметр, то есть массив маршрутов, необходимый для создания объекта pathMap карты маршрутов (то есть маршрутов в файле конфигурации маршрутизатора).
На самом деле объект карты маршрута pathMap можно использовать только при сопоставлении маршрутов и динамическом добавлении маршрутов, и эти ситуации включены вcreateMatcher
функция, поэтому вcreateMatcher
Внутри функции напрямую используйте только что написанныйcreateRouteMap
Метод генерирует объект pathMap, который поддерживается внутри при вызове функции, потому чтоcreateMatcher
Несколько методов, возвращаемых функцией, имеют ссылки на объект pathMap, что является типичным сценарием закрытия, поэтому в процессе инициализации всего экземпляра VueRoutercreateMatcher
Функцию нужно вызвать только один раз, и все в порядке,createRouteMap
Этот метод также предлагает способы динамического изменения pathMap .
Основная реализация addRoutes
Первый взглядaddRoutes
Это относительно просто. Определение этого API фактически используется для динамического добавления маршрутов. Простая точка состоит в том, чтобы проанализировать входящий новый объект маршрута и добавить его к старому объекту pathMap. Параметр должен быть массивом, отвечающим требованиям route. , функция позволяет нам добавлять несколько конфигураций маршрутизации в любое время и в любом месте, поскольку параметры представляют собой массивы и имеют тот же формат, что и маршруты, поэтому их можно полностью использовать повторно.createRouteMap
метод.
первыйcreateRouteMap
Метод просто модифицируется, достаточно добавить параметр в ok, с логикой проблем нет.
// 新增 oldPathMap 参数
export function createRouteMap(routes, oldPathMap){
// const pathMap = Object.create(null); old
const pathMap = oldPathMap || Object.create(null); // new
// ...
}
Как и выше, при динамическом добавлении просто передайте старый pathMap. Прежде чем мы напрямую объявили пустой объект pathMap, вы можете судить здесьoldPathMap
Независимо от того, существует ли параметр, его существование назначается PathMap, и нет объекта по умолчанию или пустого объекта. Это упростит настройку, синтаксический анализ и добавление конфигурации, синтаксический анализ и добавление к старому объекту сопоставления, это просто?addRoutes
Способ еще проще:
// 动态添加路由(参数必须是一个符合 routes 选项要求的数组)
function addRoutes(routes){
createRouteMap(routes, pathMap)
}
Основная реализация getRoutes
Что касаетсяgetRoutes
, еще проще, верни напрямуюpathMap
объект
// 获取所有活跃的路由记录列表
function getRoutes(){
return pathMap
}
Основная реализация addRoute
addRoute
Нам нужно уделить немного внимания этому методу, потому что этот метод будет основным для динамического добавления маршрутов в будущей версии 4.0+ и версии 3.0+.addRoute & addRoutes
Оба метода сосуществуют, но хорошо выглядят в 4.0+addRoutes
Метод был удален, давайте сначала посмотрим, как его использовать.
addRoute
Есть два параметра, также 2 использования:
- Добавьте новое правило маршрутизации. Если правило маршрутизации имеет
name
, а файл с таким именем уже существует, он будет перезаписан. - Добавьте новую запись правила маршрутизации в качестве дочернего маршрута существующего маршрута. Если правило маршрутизации имеет
name
, а файл с таким именем уже существует, он будет перезаписан.
Давайте говорить по-народному. Первый — передать объект конфигурации маршрутизации, обратите внимание, что это не предыдущийroutes
Массив представляет собой объект только с одной конфигурацией маршрутизации.Конечно, вы можете написать бесчисленное количество подмаршрутов в рамках этой конфигурации маршрутизации, но при добавлении в этой форме может быть добавлен только один объект маршрутизации, и за один раз добавляется только одна запись , Если текущая маршрутизация существует в конфигурацииname
Та же запись будет перезаписана следующим образом:
this.$router.addRoute({
path: "/parent",
name: "Parent",
component,
children:[
{
path: "child"
// ...
},
// ...
]
})
Второй — два параметра, первый параметр — существующий маршрутname
, второй параметр — это объект конфигурации маршрутизации, который аналогичен объекту конфигурации маршрутизации, используемому в приведенном выше методе, за исключением того, что этот метод будет рассматривать объект конфигурации маршрутизации как первый параметр.name
Добавляется подразрушение соответствующего объекта маршрутизации, и он просто основан на маршруте.name
Непосредственно добавляйте подмаршруты, и в процессе добавления есть повторяющиеся маршрутыname
Также покрыты.
Это выглядит сложно, но на самом деле это очень просто написать.createRouteMap
добавить одинparent
параметры. ИсправлятьcreateRouteMap
функция:
// 新增 parentRoute 参数
export function createRouteMap(routes, oldPathMap, parentRoute){
const pathMap = oldPathMap || Object.create(null);
routes.forEach(route => {
// addRouteRecord(pathMap, route, null) old
addRouteRecord(pathMap, route, parentRoute) // new
})
return pathMap
}
Как показано выше, третий параметр представляет родительский маршрут. Когда вам нужно добавить его к записи, вам нужно только получить родительский маршрут и передать его. Если нет третьего параметра, по умолчанию он равенundefined
Это не повлияет на следующую логику.
напиши дальшеaddRoute
метод:
// 动态添加路由(添加一条新路由规则)
function addRoute(parentOrRoute, route){
const parent = (typeof parentOrRoute !== 'object') ? pathMap[parentOrRoute] : undefined
createRouteMap([route || parentOrRoute], pathMap, parent)
}
Как указано выше,addRoute
Первым параметром метода может быть строка или объект маршрутизации.createRouteMap
Первым параметром метода является массив маршрутизации, поэтому, когда мы его вызываем, массив оборачивается напрямую.По умолчанию используется второй параметр.Если второй параметр не существует, первым параметром является объект маршрутизации, а затем старый Объект pathMap передается, и последний родитель Нам нужно судить в начале функции.
Когда первый параметр не является объектом, то есть на входе есть маршрутname
Строка, давайте немного изменим здесь, используем маршрутизациюpath
Вместо этого (просто поймите смысл) напрямую выньте нормализованный маршрут и назначьте его родителю через ранее разобранный объект pathMap.Если это объект, должен быть только один параметр, а родитель напрямую назначается как undefined, что идеально.
Объясните, почему вы не используете маршрутизацию, как официальную
name
Соответственно, в дополнение к объекту pathMap исходный код также анализирует объект namePath. То, что мы написали, является упрощенной версией. Эти аналогичные вещи включают обработку имен маршрутов, псевдонимов маршрутов, параметров перенаправления и динамических маршрутов. Я опустил это. и сделал Обработку пути маршрутизации может понять каждый.Большая часть другой обработки такая же, и это очень просто.Это не вызывает привыкания и может быть выполнено мной самостоятельно с помощью исходного кода, который я аннотировал.Общая структура то же самое, просто добавьте еще немного кода.
сопоставить маршрутизацию, сопоставить базовую реализацию
Наконец, функция сопоставления маршрутовmatch
Способ тоже очень простой:
// 路由匹配
function match(location){
location = typeof location === 'string' ? { path: location } : location
return pathMap[location.path]
}
match
Мы передаем методу параметр.Этот параметр может быть строкой или объектом, который должен иметь атрибут пути, потому что путь должен использоваться для соответствия настроенным данным модуля маршрутизации.Используйте следующее:
// String | Object
match("/home")
match({path: "/home"})
В начале функции проверяется тип параметра и преобразуется в объект, а затем напрямую возвращается карта путей pathMap, не правда ли просто? Не волнуйтесь, эта часть будет оптимизирована в будущем.
Использование createMatcher и монтирование метода экземпляра
Просмотрите нашиcreateMatcher
То, что делается в методе, по сути, в основном генерирует объект отображения маршрута.pathMap
, который возвращает четыре функции:
- addRoutes
- getRoutes
- addRoute
- match
Для этих методов, на самом деле, последний, который будет установлен на экземпляре VuerOter, при использовании это связано сthis.$router.addRoute()
Таким образом, здесь только основная реализация, и она будет смонтирована на экземпляре позже, среди которыхmatch
Метод будет дополнительно оптимизирован.
Итак, приходите и смотритеcreateMatcher
Использование функции и монтирование этих методов экземпляра снова возвращаются к классу VueRouter:
export default class VueRouter(){
constructor(options) {
this.options = options;
// 创建路由matcher对象,传入routes路由配置列表及VueRouter实例,主要负责url匹配
this.matcher = createMatcher(options.routes);
// ...
}
// 匹配路由
match(location) {
return this.matcher.match(location)
}
// 获取所有活跃的路由记录列表
getRoutes() {
return this.matcher.getRoutes()
}
// 动态添加路由(添加一条新路由规则)
addRoute(parentOrRoute, route) {
this.matcher.addRoute(parentOrRoute, route)
}
// 动态添加路由(参数必须是一个符合 routes 选项要求的数组)
addRoutes(routes) {
this.matcher.addRoutes(routes)
}
}
Как и выше, мы вызвали его прямо в конструкторе класса VueRoutercreateMatcher
функцию и монтирует возвращаемое значение в атрибут matcher экземпляра.На самом деле, этот объект содержит четыре метода, а затем монтирует эти методы в экземпляр, поэтому я не буду вдаваться в подробности.
Эти методы теперь присутствуют в экземпляре VueRouter, иthis.$router
Прокси для экземпляра VueRouter создается при установке, поэтому можно использовать эти методы.
Реализация истории родительского класса режима маршрута
Реализация сопоставителей маршрутов подходит к концу, помните, что еще есть в конструкторе класса VueRouter, кроме сопоставителей маршрутов? Правильно, параметр входящего режима проверяется, и класс создается для трех режимов путем оценки и инстанцирования, а затем монтируется в свойстве истории экземпляра VueRouter.
Затем мы будем реализовывать эти классы один за другим, а именноHTML5History | HashHistory | AbstractHistory
. первый вsrc/hello-vue-router/
новая папкаhistory/
папку, создайте в этой папке три новых файла, соответствующих трем классам построения режима:
- hash.js
- html5.js
- abstract.js
Затем определите родительский класс для трех классов шаблонов маршрутизации.
Мысль: зачем определять родительский класс?
Фактически, в экземпляре инициализацииthis.history
Некоторые методы монтирования являются последовательными.Хотя методы реализации могут быть непоследовательными, они не могут увеличить нагрузку на пользователей, поэтому использование должно быть унифицированным.Чтобы сохранить код и унифицировать, мы можем определить родительский класс и позволить трем Children Все классы наследуются от этого родительского класса.
Итак, во вновь созданном подклассеhistory/
Под папкой создайте новуюbase.js
файл и экспортировать класс History:
/*
* @path: src/hello-vue-router/history/base.js
* @Description: 路由模式父类
*/
export class History {
constructor(router) {
this.router = router;
// 当前路由route对象
this.current = {};
// 路由监听器数组,存放路由监听销毁方法
this.listeners = [];
}
// 启动路由监听
setupListeners() { }
// 路由跳转
transitionTo(location) { }
// 卸载
teardown() {
this.listeners.forEach((cleanupListener) => {
cleanupListener();
});
this.listeners = [];
this.current = "";
}
}
Как и выше, конструктор класса History в основном делает три вещи:
- Сохраните маршрутизатор экземпляра входящего маршрута
- объявляет текущий объект маршрутизации текущим
- Объявлен массив прослушивателей маршрута для хранения метода уничтожения прослушивателя маршрута.
Затем напишите несколько публичных методов:
- Метод setupListeners для запуска прослушивателя маршрута
- Способ перехода по маршруту прыжка
- Режим разгрузки класса прослушивателя при разгрузке и стирании экземпляра Vuerooter
Временно напишите эти три метода, по сути
setupListeners
Здесь только объявлен метод, в подклассе также будет перезаписана основная логика, а дальше толькоteardown
Этот метод удаления идеален.transitionTo
Этот метод перехода маршрутизации и некоторые общедоступные методы, которые необходимо добавить в процессе реализации подклассов, будут постепенно улучшаться в будущем.
Сначала посмотрите на этот метод уничтожения и подумайте, почему его следует уничтожать?
На самом деле, как хэш, так и история этих двух режимов обязательно запишут некоторые слушатели в процессе реализации, и когда экземпляр VueRouter выгрузится, эти мониторы не будут уничтожены, это вызовет утечку памяти, поэтому мы пишем ручную деинсталляцию уничтоженного кода. очень просто
Во-первых, поддерживать общедоступный массив прослушивателей маршрутов.listeners
, в будущем каждый раз, когда в подкласс будет записываться событие слушателя, метод прослушивателя выгрузки будет писаться напрямуюpush
Подойдите к этому массиву, при прослушивании удаления VueRouter вручную вызовите метод удаления, метод заключается в его циклическом вызове.listeners
Таким образом, методы в массиве уничтожают прослушиватель, и вы можете видеть, что последний шаг метода удаленияlisteners
Массив и текущий объект маршрутаcurrent
Все опустело.
Сохраненный объект экземпляра маршрутизатора будет использоваться позже, может быть, что вы не понимаете, должен бытьcurrent
Этот объект, следующий акцент.
Мысли: как нам получить текущий объект маршрутизации?
отвечать:$route
Мысль: где должны храниться объекты маршрутизации? каков эффект?
Первый обзор использования$route
, какими свойствами он обладает?
По сути, сохраняет текущий маршрутpath、hash、meta、query、params
И т. д. здесь фактически хранится все, что связано с текущей маршрутизацией, а официальное определение этого объекта маршрутизации доступно только для чтения.
а такжеcurrent
, является текущим значением, на самом деле это объект маршрутизации, всякий раз, когда мы отслеживаем изменение пути маршрутизации, мы должны синхронно изменять объект маршрутизации, и когда объект маршрутизации изменяется,router-view
Вид, который должен отображать компонент, также необходимо изменить, можно сказать, что этот объект маршрутизации является центром всего VueRouter.
Вы можете спросить, а разве вы только что не сказали, что этот объект доступен только для чтения? Как это еще может измениться? По сути, сам объект маршрутизации заморожен, мы только читаем свойства в объекте, но можем переключить весь объект маршрутизации!
Выше мыcurrent
Начальное значение этого определения объекта маршрутизации — пустой объект. Фактически, поскольку объект маршрутизации является ориентированным на пользователя объектом с фиксированным форматом, для создания этого объекта маршрутизации с фиксированным форматом следует использовать унифицированный метод. Мы вызываем этот метод .createRoute
.
метод createRoute
Или просто вынуть файл для реализации такого метода.
существуетsrc/hello-vue-router/
Создайте новый в каталогеutils/
папку, создайте новую в этой папкеroute.js
файл, внедрить и экспортироватьcreateRoute
метод.
Сначала создайте новый файл, скажемcreateRoute
Перед методом давайте подумаем, когда нам нужно создать этот объект маршрутизации?
Сначала конечно нашcurrent
При инициализации свойства необходимо создать пустой объект маршрута Что еще?
Есть два способа изменить путь: один — напрямую изменить URL-адрес, другой — использоватьpush
метод.
// No.1 oldURL => newURL
let oldURL = "http://localhost:8081/#/about"
let newURL = "http://localhost:8081/#/home?a=1"
// No.2
this.$router.push({
path: "/home",
query: {a: 1}
})
Видно, что при смене маршрута есть много свойств, как и в официальной документации.push
Свойства, поддерживаемые методом, следующие, см. документацию по конкретным функциям:
name
path
hash
query
params
append
replace
Когда путь меняется, нам нужно перейти на новый путь.Новый путь плюс эти атрибуты, которые можно переносить, называются целевыми информационными объектами. Маршрут текущего объекта маршрутизации должен содержать всю информацию о текущем маршруте, объект конфигурации маршрутизации, соответствующий пути + целевой информационный объект = вся информация, а отформатированная информация представляет собой текущий маршрут объекта маршрутизации.
Следовательно, чтобы обновить текущий объект маршрутизации, необходимо сопоставить объект конфигурации маршрутизации по пути, а затем объект конфигурации маршрутизации и целевой информационный объект объединяются и форматируются как маршрут. Где сделать такую операцию обновления?
Оглядываясь назад на то, что мы писали ранееcreateMatcher
функция, которая возвращает метод соответствия следующим образом:
// 路由匹配
function match(location){
location = typeof location === 'string' ? { path: location } : location
return pathMap[location.path]
}
Здесь мы вернули объект конфигурации маршрутизации на тот момент. На самом деле наша конечная цель — сопоставить его с текущим объектом маршрутизации. Мы также проанализировали текущий объект маршрутизации = объект конфигурации маршрутизации + целевой информационный объект, поэтому он является наиболее полным для непосредственно соответствуют объекту маршрутизации data, теперь перепишем этот метод:
/*
* @path: src/hello-vue-router/create-route-map.js
* @Description: 路由匹配器Matcher对象生成方法
*/
import { createRouteMap } from "./create-route-map";
// 导入route对象创建方法
import { createRoute } from "./utils/route"
export function createMatcher(routes){
const pathMap = createRouteMap(routes)
// 路由匹配
function match(location){
location = typeof location === 'string' ? { path: location } : location
return createRoute(pathMap[location.path], location) // 修改
}
// ...
}
как указано выше, вcreateMatcher
возвращается функциейmatch
метод, непосредственно создайте новый объект маршрутизации и верните его. Из этого анализа мы можем определитьcreateRoute
Параметры функции, как указано вышеcreateRoute
В методе есть 2 параметра, первый — это запись объекта сопоставления маршрута, а второй — местоположение целевого информационного объекта (именно поэтому мы называем параметр местоположения метода сопоставления и позволяем ему иметь как объектный, так и строковый форматы. причина).
мы часто используемpush
На самом деле параметром метода является объект местоположения, который может быть как строковым путем, так и объектом.Когда это объект, свойства, которые могут быть переданы, такие же, как и те, которые могут быть настроены методом push над.
Однако в свойствах, написанных вышеappend、replace
Да, две дополнительные функции, требующие дополнительного разбора,push
поддержка метода,router-link
Компоненты также поддерживаются. Их функции см. в следующих документах. Мы временно опускаем синтаксический анализ этих двух параметров, поскольку они не являются основной логикой.
Анализ готов к внедрениюcreateRoute
Метод, старые правила, сначала посмотрите на общий код, и постепенно анализируйте:
/*
* @path: src/hello-vue-router/utils/route.js
* @Description: route对象相关方法
*/
export function createRoute(record, location) {
let route = {
name: location.name || (record && record.name),
meta: (record && record.meta) || {},
path: location.path || "/",
hash: location.hash || "",
query: location.query || {},
params: location.params || {},
fullPath: location.path || "/",
matched: record && formatMatch(record),
};
return Object.freeze(route);
}
// 初始状态的起始路由
export const START = createRoute(null, {
path: '/'
})
// 关联所有路由记录
function formatMatch(record) {
const res = []
while (record) {
// 队列头添加,所以父record永远在前面,当前record永远在最后
// 在router-view组件中获取匹配的route record时会用到
// 精准匹配到路由记录是数组最后一个
res.unshift(record)
record = record.parent
}
return res
}
Как указано выше,createRoute
В методе объект маршрута строится по двум параметрам, принимающим некоторые значения друг от друга. Здесь следует отметить две вещи,fullPath
Параметр на самом деле представляет собой полный путь path+qs+hash, но здесь мы пишем только путь, игнорируя сначала проблему с параметром.
а такжеmatched
Для этого свойства мы напрямую написалиformatMatch
Генерируется функция, и в ней делается только одно: получить все объекты конфигурации маршрутизации, связанные с текущим путем.
параметр функциональной строкиrecord
Это объект конфигурации маршрутизации. При генерации объекта конфигурации маршрутизации мы добавили к нему атрибут parent, указывающий на его родительскую маршрутизацию. Если не помните, просмотрите его.createRouteMap
метод.formatMatch
Функция состоит в том, чтобы рекурсивно найти текущий путь, включая его родительский объект конфигурации маршрутизации, и сформировать массив, которыйmatched
Параметры, например, следующая конфигурация маршрутизации:
let routes = [
{
path: "/parent",
name: "Parent",
component,
children:[
{
path: "child",
name:"Child",
component,
}
]
}
]
Затем эта конфигурация маршрутизации анализируется в pathMap следующим образом:
pathMap = {
"/parent": {path:"/parent", ...},
"/parent/child": {path:"/parent/child", ...},
}
Если новый путь для перехода/parent/child
, при построении маршрута пройтиformatMatch
Метод связывает все свои записи маршрутизации и, наконец, объект маршрутизации.matched
Свойства следующие:
[
{path:"/parent", component, parent ...},
{path:"/parent/child", component, parent ...}
]
Обратите внимание, потому чтоformatMatch
Когда функция рекурсивно находит родителя, мы используемunshift
метод, поэтому последний элемент конечного массива должен быть модулем текущего пути.
На самом деле это делается для подготовки к вложенной маршрутизации, поскольку при наличии вложенной маршрутизации и сопоставлении дочерней записи маршрутизации это фактически означает, что родительская запись маршрутизации также должна быть сопоставлена. Например, чтобы сопоставить /foo/bar, когда сопоставляется сам /foo/bar, его родительский объект маршрутизации /foo также должен совпадать.Окончательный результат сопоставления выглядит следующим образом:
metched = [{path:"/foo", ...},{path:"/foo/bar"}]
// “/foo/bar” 本身匹配模块在数组最后,而第一项是顶级路由匹配项
Таким образом, объект маршрутизацииmatched
Свойство представляет собой массив, а элементы массива — это объекты конфигурации сопоставленной маршрутизации. Порядок элементов массива — от объекта сопоставления маршрутизации верхнего уровня к самому объекту сопоставления текущей подмаршрутизации. На этом этапе выполняется простое создание маршрута. функция в порядке.
Идея переключается обратно на урок истории,current
Мы не присвоили объекту начальное значение маршрутизации, поэтому мы находимся вroute.js
Объект маршрутизации инициализации также записывается в файл, экспортируется и вызываетсяcreateRoute
метод, первый параметр пустой, второй параметр записывает только значение атрибута пути"/"
Объект:
// 初始状态的起始路由
export const START = createRoute(null, {
path: '/'
})
окончательная модификацияbase.js
Файл класса истории, начальное значение объекта маршрутаSTART
импортировать и назначатьcurrent
:
// 导入初始化route对象
import { START } from "../utils/route";
export class History {
constructor(router) {
this.router = router;
// 当前路由route对象
// this.current = {};
// => this.current = START;
this.current = START;
this.listeners = [];
}
// ...
}
Здесь, в родительском классеtransitionTo
То есть метод перехода по маршруту можно продолжать дополнять, при вызове метода перехода по маршруту будет передаваться целевой информационный объект.
-
обновить объект маршрута
current
-
Обновить URL-адрес
-
Обновить вид
// 路由跳转
transitionTo(location, onComplete) {
// 路由匹配,解析location匹配到其路由对应的数据对象
let route = this.router.match(location);
// 更新current
this.current = route;
// 更新URL
this.ensureURL()
// 跳转成功抛出回调
onComplete && onComplete(route)
}
Как и выше, метод перехода маршрутизацииtransitionTo
По сути, входящий — это объект локации,push
Метод также реализуется на основе этого метода.
Когда приходит новый целевой информационный объект, мы должны сначала создать новый объект маршрутизации. История является родительским классом. Позже мы напишем подкласс. Подкласс наследует родительский класс. Когда подкласс инициализирует экземпляр (индекс. Режим файла js часть оценки параметра), фактически переданная в текущем экземпляре VueRouter, поэтому наш родительский класс также может получить его, то есть в конструкторе нашего родительского класса.router
параметр, вешаем его прямо в свойство экземпляра родительского классаrouter
, чтобы мы могли пройтиthis.router
Получите экземпляр Vuerouter.
Помните, мы смонтировали метод match в экземпляре VueRouter? Не забывайте просматривать код.
Мы используемthis.router.match
метод, передайте параметр местоположения, вы можете создать новый объект маршрутизации и, наконец, назначить новый объект маршрутизации дляcurrent
Атрибуты.
OK, согласно нашей логике, изменение маршрута генерирует новый объект маршрута и назначает егоcurrent
Вот и все, осталось обновить URL и обновить представление.
Думаю: зачем обновлять URL?
На самом деле, если вы напрямую изменяете URL-адрес для перехода, вам не нужно обновлять URL-адрес, но если вы используете API для выполнения маршрутных переходов, напримерpush
метод, мы можем управлять объектом маршрута обновления в кодеcurrent
, представление также может быть обновлено, но URL-адрес не изменился, поэтому нам также необходимо обновить URL-адрес.
Итак, вопрос в том, как обновить URL?
Вы можете видеть, что в приведенном выше коде мы вызываемensureURL
способ обновления, иthis
Вызывается, по сути, этот метод не на родительском классе, а на подклассе.
почему быensureURL
метод написан в подклассе?
Поскольку у нас есть 3 режима, и разные режимы заменяют URL-адреса по-разному, лучше всего написать свои собственные методы обновления URL-адресов для каждого подкласса.
Почему здесь можно вызывать методы подкласса?
Поскольку подкласс инициализирует экземпляр, а подкласс наследует родительский класс, можно понять, что методы и свойства родительского класса наследуются подклассом.transitionTo
Метод, конечно же, также наследуется, поэтому при вызове этого метода перехода внутреннийthis
Указатель является подклассом, поэтому метод подкласса может быть вызван напрямую.
Что касается обновления вида, потому что он еще не идеаленrouter-view
Компоненты и подклассы были написаны не очень хорошо, поэтому мы ставим их позже для улучшения.
Наконец, вызывается обратный вызов успешного перехода, и передается текущий параметр объекта маршрута.
Предварительное построение подклассов режима маршрутизации
Давайте сначала построим три подкласса, по сути, создадим разные подклассы в трех файлах, и пусть они все наследуют родительский класс, а мы будем реализовывать их один за другим позже.
hash.js
import { History } from './base'
export class HashHistory extends History {
constructor(router){
super(router);
}
}
html5.js
import { History } from './base'
export class HTML5History extends History {
constructor(router){
super(router);
}
}
abstract.js
import { History } from './base'
export class AbstractHistory extends History {
constructor(router){
super(router);
}
}
Реализация класса HashHistory
приходитьhistory/
в папкеhash.js
файл, мы сначала реализуем класс HashHistory:
/*
* @path: src/hello-vue-router/index.js
* @Description: 路由模式HashHistory子类
*/
import { History } from './base';
export class HashHistory extends History {
constructor(router) {
// 继承父类
super(router);
}
// 启动路由监听
setupListeners() {
// 路由监听回调
const handleRoutingEvent = () => {
let location = getHash();
this.transitionTo(location, () => {
console.log(`Hash路由监听跳转成功!`);
});
};
window.addEventListener("hashchange", handleRoutingEvent);
this.listeners.push(() => {
window.removeEventListener("hashchange", handleRoutingEvent);
});
}
}
// 获取location hash路由
export function getHash() {
let href = window.location.href;
const index = href.indexOf("#");
if (index < 0) return "/";
href = href.slice(index + 1);
return href;
}
Как и выше, мы позволяем классу HashHistory наследовать класс History, а подкласс наследует все от родительского класса. Мы впервые реализовали режим хешированияsetupListeners
метод, то есть запустить метод прослушивания маршрута.
Давайте посмотрим на логику, главное следитьhashchange
События, то есть при изменении хеш-маршрута будет срабатывать его обратный вызов.
Мышление: что нам нужно делать, когда прослушивание пути маршрутизации изменилось?
Если путь меняется, вам необходимо обновить текущий объект маршрутизации, обновить представление и т. д. Мы уже делали этот шаг раньше, да, этоtransitionTo
Это делается в методе перехода, поэтому мы можем вызвать метод перехода маршрута непосредственно при прослушивании изменения маршрута.
Таким образом, обратный вызов сначала передаетgetHash
Инструментальная функция получения текущего значения хэша и возврата пути маршрутизации хэша.Этот метод прост и не будет повторяться. Получив путь, назовите егоtransitionTo
метод.
Кроме того, после запуска слушателя мы отправляемlisteners
Массив (унаследован от родительского класса)push
Метод уничтожения слушателя используется для уничтожения события слушателя при выгрузке, о котором также упоминалось выше.
Далее добавляем метод подкласса:
export class HashHistory extends History {
constructor(router) {
// 继承父类
super(router);
}
// 启动路由监听
setupListeners() { /** ... **/ }
// 更新URL
ensureURL() {
window.location.hash = this.current.fullPath;
}
// 路由跳转方法
push(location, onComplete) {
this.transitionTo(location, onComplete)
}
// 路由前进后退
go(n){
window.history.go(n)
}
// 跳转到指定URL,替换history栈中最后一个记录
replace(location, onComplete) {
this.transitionTo(location, (route) => {
window.location.replace(getUrl(route.fullPath))
onComplete && onComplete(route)
})
}
// 获取当前路由
getCurrentLocation() {
return getHash()
}
}
// 获取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}`
}
// 获取location hash路由
export function getHash() { /** ... **/ }
Мы добавили 5 методов:
-
ensureURL
- Обновите URL-адрес, его реализация на самом деле очень проста, обновите хэш URL-адреса панели навигации, используйте
window.location.hash
С API все в порядке, в методе перехода родительского класса он вызывается после обновления текущего объекта маршрутизации.ensureURL
, а обновленный объект маршрутизации вfullPath
Свойство представляет собой полный хэш-путь, поэтому вы можете просто назначить его напрямую.
- Обновите URL-адрес, его реализация на самом деле очень проста, обновите хэш URL-адреса панели навигации, используйте
-
push
- Метод перехода маршрутизации, этот метод уже реализован в родительском классе, поэтому подключается в
push
вызвать родительский классtransitionTo
Метод прыгает просто отлично, и параметры те же.
- Метод перехода маршрутизации, этот метод уже реализован в родительском классе, поэтому подключается в
-
go
- Прямой и обратный маршрут фактически реализован независимо от того, является ли это переходом в режиме хеширования или в режиме истории, каждый переход изменяет URL-адрес, а запись о переходе сохраняется в браузере.
window.history
стек, а браузер также предоставляетwindow.history.go
Метод используется для прямой и обратной маршрутизации, поэтому его можно вызывать напрямую, а параметры одинаковы.
- Прямой и обратный маршрут фактически реализован независимо от того, является ли это переходом в режиме хеширования или в режиме истории, каждый переход изменяет URL-адрес, а запись о переходе сохраняется в браузере.
-
getCurrentLocation
- Получите текущий адрес маршрутизации URL, так как это хеш-класс, мы реализовали его раньше
getHash
метод для получения маршрута в URL-адресе в хеш-режиме, поэтому просто верните вызывающее значение этого метода.
- Получите текущий адрес маршрутизации URL, так как это хеш-класс, мы реализовали его раньше
-
replace
- Перейти к указанному URL-адресу и заменить последнюю запись в стеке истории
мы фокусируемся наreplace
метод:
Давайте сначала поговорим о роли, на самом деле это прыжок, просто используйтеreplace
Прыжок не будетwindow.history
Стек генерирует запись, то есть когда мы используем со страницыpush
При переходе на страницу b стек[a,b]
, повторное использованиеreplace
При переходе со страницы b на страницу c стек по-прежнему[a, b]
, то в это время мы возвращаемся на предыдущую страницу и переходим прямо со страницы c на страницу a.
На самом деле, мы, вероятно, также знаем, что браузерыwindow.location.replace
Метод может достичь этой функции, но три части обновления (объект маршрутизации, URL, View) необходимо учитывать при прыжках в VuerOter.
Представьте, если бы мыreplace
Новый маршрут, что нам нужно сделать?
Сначала обновите текущий объект маршрутизации, а затем обновите URL-адрес.window.location.replace
Обновления не записываются, и представление окончательно визуализируется.
А? как будто сtransitionTo
почти то же самое, то мы можем изменитьtransitionTo
метод, поместите его на исходный URL-адрес обновленияensureURL
Прыгать в метод за вызовом вызовов успеха, поэтому мы называемtransitionTo
метод, используемый в обратном вызовеwindow.location.replace
Обновление URL делает свое дело.
Вы можете задать вопрос, будет лиensureURL
Метод ставится в конце, в обратном вызовеreplace
Но обратный вызов все равно будет вызываться после выполненияensureURL
метод?
На самом деле обратный вызов используетсяwindow.location.replace
После обновления URL-адреса URL-адрес уже актуален, затем вызовите его снова.ensureURL
Обновите URL-адрес. Поскольку обновляемый URL-адрес совпадает с текущим URL-адресом, страница не будет переходить.
потому чтоensureURL
Метод действительно вызываетwindow.location.hash
, если текущий адрес страницыhttp://localhost:8080/#/about
Мы используем этот API, чтобы изменить его хэш/about
,由于前后 hash 一致,其实等于啥也没做。 . .
Итак, мы модифицируемtransitionTo
просто измените свой обратный вызов успеха и обновите URL-адресensureURL
Порядок вызова методов может быть следующим:
transitionTo(location, onComplete) {
let route = this.router.match(location);
this.current = route;
// 跳转成功抛出回调 放上面
onComplete && onComplete(route)
// 更新URL 放下面
this.ensureURL()
}
Затем реализуйтеreplace
метод:
export class HashHistory extends History {
// 跳转到指定URL,替换history栈中最后一个记录
replace(location, onComplete) {
this.transitionTo(location, (route) => {
window.location.replace(getUrl(route.fullPath))
onComplete && onComplete(route)
})
}
// ...
}
// 获取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}`
}
Как указано выше, звонитеtransitionTo
Метод, в своем обратном вызовеwindow.location.replace
все вместе
Обратите внимание, что здесь мы написали еще один метод инструмента,getUrl
, на самом деле это передача хеш-пути и возврат полного нового пути URL-адреса. Обычная операция повторяться не будет.
Вот, собственно, нашHashHitory
Подклассы почти в порядке.
Дальше идет процесс.
В предыдущей реализации класса VueRouter мы только инициализировали каждый подкласс модуля маршрутизации, но мы еще не включили мониторинг маршрутизации.Обратите внимание, что метод запуска мониторинга в подклассеsetupListeners
, назад сноваsrc/hello-vue-router/index.js
файл, класс VueRouter и добавьте к нему метод инициализации.
Инициализация экземпляра VueRouter
Сборка метода инициализации
Мысль: что должен делать класс VueRouter при инициализации?
Разумеется, для запуска мониторинга класса режима маршрутизации, поскольку мониторинг запущен, его необходимо смонтировать и уничтожить.
Мысли: когда он будет уничтожен?
Когда не нужно следить, когда он уничтожается! ! После удаления корневого экземпляра Vue нет необходимости его отслеживать, поэтому мы можем отслеживать удаление корневого экземпляра Vue.
Вопрос в том, как мы можем внешне контролировать выгрузку экземпляра Vue?
Эх!hook:
Пригодится специальный прослушиватель событий префикса, который официально поддерживается Vue.
Маленькие советы:hook:
специальный прослушиватель событий для префикса
Функция ловушки жизненного цикла в исходном коде передается черезcallHook
функция вызова,callHook
Есть функцияvm._hasHookEvent
суждение, когда оноtrue
на случай, еслиhook:
События со специальными префиксами будут выполняться в соответствующем жизненном цикле.
После того, как событие прослушивателя будет проанализировано в компоненте, оно будет использовано$on
Чтобы зарегистрировать обратные вызовы событий, используйте$on
или$once
При прослушивании событий, если имя события начинается сhook:
в качестве префикса событие будет рассматриваться какhookEvent
, при регистрации обратного вызова события,vm._hasHookEvent
будет установлен наtrue
, когда используешьcallHook
При вызове функции жизненного цикла из-за_hasHookEvent
дляtrue
, будет выполняться напрямую$emit('hook:xxx')
, поэтому зарегистрированная функция жизненного цикла будет выполнена.
- пройти по шаблону
@hook:created
Зарегистрируйтесь в этой форме. - в JS через
vm.$on('hook:created', cb)
илиvm.$once('hook:created', cb)
Зарегистрирован, VM относится к текущему экземпляру компонента.
Классический вопрос интервью,Как прослушать жизненный цикл дочернего компонента в родительском компоненте, ответ заключается в том, чтобы получить экземпляр дочернего компонента (vm) в родительском компоненте, а затем зарегистрироватьhook:
Специальный прослушиватель событий префикс + хук жизненного цикла в порядке.
Здесь мы хотим прослушивать корневой экземпляр, поэтому нам нужно получить объект корневого экземпляра, а затем зарегистрировать прослушиватель, нам не нужно использовать его для прослушивания события уничтожения.$on
,использовать$once
Вы можете, поэтому запускать только один раз, слушатель будет удален после запуска, как показано ниже:
// vm 为根实例对象
vm.$once("hook:destroyed", () => {})
Зная об этих проблемах, продолжайте реализовывать метод init.Поскольку вы хотите получить корневой экземпляр объекта, тоinit
Параметры метода есть.После завершения анализа приступайте к написанию кода!
export default class VueRouter{
init(app) {
// 绑定destroyed hook,避免内存泄露
app.$once('hook:destroyed', () => {
this.app = null
if (!this.app) this.history.teardown()
})
// 存在即不需要重复监听路由
if (this.app) return;
this.app = app;
// 启动监听
this.history.setupListeners();
}
// ...
}
Как и выше, это на самом деле очень просто,init
Метод передает параметр приложения, то есть корневой экземпляр Vue, который оценивается в методе.this.app
Независимо от того, существует он или нет, если есть прямой возврат, это означает, что прослушиватель был зарегистрирован.Если он не существует, экземпляр присваивается свойству app класса VueRouter, и, наконец, вызывается экземпляр VueRouter.history
атрибутsetupListeners
Метод начинает мониторинг.
history
что мыconstructor
Экземпляр класса шаблона маршрутизации, инициализированный вconstructor
Конструктор находится вnew VueRouter
будет выполнено, когда , поэтому мы можем получитьhistory
пример.
И прослушиватель зарегистрированного уничтожения тоже очень прост, то есть использует корневой экземпляр, как упоминалось выше.$once
Зарегистрироватьhook:destroyed
Слушайте, опустойте свойство приложения в обратном вызове и позвонитеhistory
Метод удаления экземпляраteardown
, этот метод реализован в родительском классе режима маршрутизации, если вы его забудете, то сможете оглянуться назад.
ХОРОШО,init
Метод временно закончен, когда мы хотим его вызвать?
вызов метода инициализации
Поскольку у метода init также есть слушатель запуска, его нужно вызывать после того, как все будет инициализировано, и в это время должен быть получен корневой экземпляр Vue.
Оглядываясь назад на все ссылки выше, единственное место, где можно получить корневой экземпляр, — это метод установки плагина.mixin
Время смешаться.
Итак, вsrc/hello-vue-router/install.js
метод установки файлаmixin
Добавьте метод инициализации компонента маршрута выполнения в:
/*
* @path: src/hello-vue-router/install.js
* @Description: 入口文件 VueRouter类
*/
export function install(Vue){
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
this._routerRoot = this;
this._router = this.$options.router;
// 调用VueRouter实例初始化方法
// _router即VueRouter实,此处this即Vue根实例
this._router.init(this) // 添加项
this._route = {};
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
},
});
// ...
}
Тогда вы найдете,mixin
середина_route
Объект или пустой объект, мы достигли цели текущей маршрутизации класса шаблонов маршрутизацииcurrent
атрибут, поэтому здесь ему можно присвоить значение, и код снова модифицируется следующим образом:
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
this._routerRoot = this;
this._router = this.$options.router;
this._router.init(this)
// this._route = {}; old
this._route = this._router.history.current; // new
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
},
});
На этом весь процесс нашего хеш-режима в основном завершен. Вы можете открыть ссылку проекта, чтобы увидеть, нет ли ошибок, и вы можете щелкнуть навигацию, чтобы переключить маршрут. Если есть ошибка, вы, должно быть, написали ее. неправильно, не я. . Хотя об ошибке не сообщается, модуль маршрутизации на странице не отображается, потому чтоrouter-view
Компоненты еще не готовы.
Компонент RouterView идеален
В настоящее время наш компонент RouterView выглядит так:
/*
* @path: src/hello-vue-router/components/view.js
* @Description: router-view
*/
export default {
name: "RouterView",
functional: true,
render(h) {
return h('div', 'This is RoutePage')
}
}
Как и выше, рендеринг компонента всегда представляет собой фиксированный div, и теперь вы можете начать его совершенствовать.
Компонент маршрутизации Динамический рендеринг
Идея очень проста: сначала нужно получить текущий объект маршрутизации, потому что текущий объект маршрутизацииmatched
В массиве хранятся все объекты сопоставления маршрута, связанные с текущим путем.Последний элемент массива — это объект сопоставления маршрута самого текущего пути, поэтому нам нужно только вынуть последний элемент массива, а затем взять его компоненты свойство (то есть модуль маршрутизации, соответствующий текущему пути). ), просто передайте его функции рендеринга напрямую.
Начните модифицировать компонент RouterView:
export default {
name: "RouterView",
functional: true, // 函数式组件
render(h, { parent, data}) {
// parent:对父组件的引用
// data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
// 标识当前渲染组件为router-view
data.routerView = true
let route = parent.$route
let matched;
if(route.matched){
matched = route.matched[route.matched.length - 1]
}
if (!matched) return h();
return h(matched.components, data)
}
}
Если вы не знаете о функциональных компонентах, см. документациюДокументация по функциональным компонентам.
На самом деле код очень простой. Сначала мы идентифицируем компонент RouterView, который в данный момент рендерится. Код добавляет к данным атрибут. В итоге эти данные будут переданы в компонент в качестве второго параметра createElement. Когда мы захотим узнать является ли компонент Если RouterView отображается, об этом можно судить по этому свойству, которое хранится в экземпляре компонента.$vnode
объект данных свойства.
Поскольку мы смонтировали$route
Таким образом, вы можете получить доступ к этому объекту маршрутизации через любой экземпляр, получить объект маршрутизации и использовать любой из них.matched
Последний элемент массива атрибутов, то есть компонент маршрутизации, соответствующий текущему пути.
Наконец, вы можете вернуть компонент непосредственно в функцию h (createElement).
Вроде все ок, откройте страницу проекта и посмотрите.
В дополнение к пустой навигации на странице не сообщается об ошибке.Нажатие на навигацию запускает прослушиватель перехода (консоль имеет вывод), но рендеринг компонента отсутствует, как показано ниже:
что случилось? Пройдите процесс.
Сначала нажмите на навигационный переход, прослушайте изменение хеш-маршрута, перейдитеtransitionTo
Методы, методы делают три вещи:
- Обновить текущий объект маршрута
- Обновить URL-адрес
- Обновить рендеринг компонентов
Эх! Обновите рендеринг компонента, мы, кажется, еще не сделали этот шаг, и мы нашли проблему!
Сначала мы усовершенствовали компонент RouterView, но когда путь маршрутизации обновляется, как мы уведомляем компонент RouterView об обновлении рендеринга? ?
Подумайте об этом, что является ядром Vue? Конечно, он реагирует на данные.Основные данные RouterView$route
, если мы сделаем это адаптивными данными, то при изменении они могут быть автоматически перерисованы напрямую!
Просто сделайте это, как написано ранее$route
, который фактически закреплен в корневом экземпляре Vue_route
Объект, пока_route
Объект можно сделать отзывчивым.Конечно, адаптивный метод по-прежнему опирается на методы, предоставляемые Vue.В противном случае нам слишком трудоемко писать тип, реагирующий на данные.Более того, конструктор Vue сам предоставляет такой API, т.е. ,Vue.util.defineReactive
Функция также очень проста в использовании, измените метод установки:
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
this._routerRoot = this;
this._router = this.$options.router;
this._router.init(this)
// this._route = this._router.history.current; old
Vue.util.defineReactive(this, '_route', this._router.history.current); // new
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
},
});
Как показано выше, мы используемVue.util.defineReactive
API, добавьте реактивное свойство в корневой экземпляр (это)_route
И назначьте его как объект маршрутизации, конструктор Vue можно использовать прямо здесь, потому чтоinstall
Параметры метода включены в Vue.
так что всякий раз_route
При изменении этого объекта компонент RouterView может быть автоматически отрисован.Давайте еще раз посмотрим на страницу и нажмем на навигацию:
блять, все то же самое, почему так? Снова инсульт.
Сначала нажмите на навигационный переход, прослушайте изменение хеш-маршрута, перейдитеtransitionTo
Методы, методы делают три вещи:
- Обновить текущий объект маршрута
- Обновить URL-адрес
- Обновить отрисовку компонента
Вроде нормально, а! Подождите, кажется, проблема снова обнаружена.При обновлении текущего объекта маршрутизации, похоже, только обновлениеcurrent
, так и не обновил_route
,_route
Объектам присваивается значение только один раз при их инициализации. . Измени это! !
первый дляHistory
добавить классlisten
Метод и получить обратный вызов,listen
Внутри функции функция обратного вызова напрямую сохраняется вHistory
Категорияcb
свойства, вtransitionTo
в функцииcurrent
позвоните после обновленияcb
Перезвонил и передал обновление для обновленияroute
объект и_route
Обновление этого шага, размещенное в методе инициализации класса VueRouter, выглядит следующим образом:
// History父类中新增listen方法 保存赋值回调
listen(cb){
this.cb = cb
}
transitionTo(location, onComplete) {
let route = this.router.match(location);
this.current = route;
// 修改
// 调用赋值回调,传出新路由对象,用于更新 _route
this.cb && this.cb(route)
onComplete && onComplete(route)
this.ensureURL()
}
Затем метод INIT класса Vuerouter:
init(app) {
app.$once('hook:destroyed', () => {
this.app = null
if (!this.app) this.history.teardown()
})
if (this.app) return;
this.app = app;
this.history.setupListeners();
// 新增
// 传入赋值回调,为_route赋值,进而触发router-view的重新渲染
// 当前路由对象改变时调用
this.history.listen((route) => {
app._route = route
})
}
Некоторые друзья могут запутаться, но на самом деле он очень прост для понимания, т. е. вызывается в методе inithistory
Экземпляры наследуются от родительского классаlisten
метод, передавая обновление_route
обратный звонок,listen
Функция сохранит этот обратный вызов навсегда, и каждый раз, когда объект маршрутизации обновляется, его можно обновить, передав новый объект маршрутизации и вызвав его один раз._route
.
Теперь откройте страницу и посмотрите на нее еще раз, обновите страницу, рендеринга нет, нажмите на навигацию, и она снова отобразится.
Мысль: почему компонент не отображается при обновлении?
На самом деле это потому, что при изменении пути маршрутизации мы можем его прослушать, а затем проделать все операции, но при инициализации страницы мы не разбираем начальный путь.
Знай проблему и решай ее! На самом деле это тоже просто: получить текущий путь маршрутизации прямо в методе init, а затем вызватьtransitionTo
Метод анализирует путь и отображает его.Снова измените метод init класса VueRouter:
init(app) {
app.$once('hook:destroyed', () => {
this.app = null
if (!this.app) this.history.teardown()
})
if (this.app) return;
this.app = app;
// 新增
// 跳转当前路由path匹配渲染 用于页面初始化
this.history.transitionTo(
// 获取当前页面 path
this.history.getCurrentLocation(),
() => {
// 启动监听放在跳转后回调中即可
this.history.setupListeners();
}
)
this.history.listen((route) => {
app._route = route
})
}
Как и выше, помните, что написано в подклассе режима маршрутизацииgetCurrentLocation
метод? По сути, это получение текущего пути маршрутизации и использованиеhistory
примерtransitionTo
Метод проходит в текущем маршрутном пути, так как это метод init, он эквивалентен выполнению при инициализации страницы, то есть путь текущей страницы будет получен для парсинга и рендеринга один раз при обновлении страницы. Запускаем монитор.setupListeners
Функция помещается в обратный вызов перехода для мониторинга, и это нормально.
Затем снова посмотрите на страницу:
Будь то обновление или прыжок, нет проблем, он может отображаться нормально, приятно!
Отрисовка компонента вложенного маршрута
Давайте снова протестируем вложенные маршруты!
Для подготовки сначала напишите родительскую страницу вsrc/views/
новая папкаParent.vue
файл, написанный в коде:
<template>
<div>
parent page
<router-view></router-view>
</div>
</template>
Затем напишите дочернюю страницу вsrc/views/
новая папкаChild.vue
файл, напишите код:
<template>
<div>
child page
</div>
</template>
Исправлятьsrc/router/index.js
Массив конфигурации маршрутизации файла выглядит следующим образом:
const routes = [
// ...
//新增路由配置
{
path: "/parent",
name: "Parent",
component: ()=>import("./../views/Parent.vue"),
children:[
{
path: "child",
name:"Child",
component:()=>import("./../views/Child.vue")
}
]
}
];
Затем изменитеsrc/App.vue
Навигация по маршруту в файлах, новинкаParent & Child
Две навигации следующие:
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<!-- 新增 -->
<router-link :to="{ path: '/parent' }">Parent</router-link> |
<router-link :to="{ path: '/parent/child' }">Parent Child</router-link>
</div>
<router-view/>
</div>
</template>
Хорошо, это очень простой вложенный маршрут, давайте посмотрим на эффект страницы!
Первые две страницы нормальные,parent
Компонент страницы не отображается, а консоль взрывается напрямую:
child
Страница выглядит следующим образом:
child
Поскольку страница отображает только содержимое дочерней страницы, это вложенный маршрут, и содержимое страницы дочерней страницы записывается на родительской странице.router-view
Отрисовка посередине, поэтому при нажатии на дочернюю страницу обычно должно отображаться содержимое родительской страницы.
На самом деле все проблемы из-за того, что мы не учли ситуацию вложенности, когда писали компонент RouterView.Просмотрите код компонента RouterView:
export default {
name: "RouterView",
functional: true,
render(h, { parent, data}) {
data.routerView = true
let route = parent.$route
let matched;
if(route.matched){
matched = route.matched[route.matched.length - 1]
}
if (!matched) return h();
return h(matched.components, data)
}
}
Проанализируйте с помощью текущего кода компонента RouterView, является ли текущий путь/parent/child
, получить текущий объект маршрутизацииroute
,мы знаемroute.matched
Здесь хранятся все связанные объекты конфигурации маршрутизации после разрешения пути, которые должны выглядеть следующим образом:
[
{path: "/parent", components, ...},
{path: "/parent/child", components, ...}
]
И мы берем последний элемент и берем только модуль подмаршрутизации, поэтому отображается только компонент подмаршрутизации.
Тогда, если текущий путь/parent
, который получается после разбора текущего объекта маршрутизацииroute.matched
Массив выглядит так:
[
{path: "/parent", components, ...}
]
Возьмем последний элемент, рендерится только родительский компонент маршрутизации, т.к.router-view
компонента, продолжайте выполнять логику компонента, а затем визуализируйте родительский компонент. . . Он продолжает зацикливаться, поэтому он взрывает стек. .
Измените компонент RouterView следующим образом: просмотрите полный код, прежде чем объяснять его.
export default {
name: "RouterView",
functional: true, // 函数式组件
render(h, { parent, data}) {
// parent:对父组件的引用
// data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
// 标识当前组件为router-view
data.routerView = true
let depth = 0;
// 逐级向上查找组件,当parent指向Vue根实例结束循环
while(parent && parent._routerRoot !== parent){
const vnodeData = parent.$vnode ? parent.$vnode.data : {};
// routerView属性存在即路由组件深度+1,depth+1
if(vnodeData.routerView){
depth++
}
parent = parent.$parent
}
let route = parent.$route
if (!route.matched) return h();
// route.matched还是当前path全部关联的路由配置数组
// 渲染的哪个组件,走上面逻辑时就会找到depth个RouterView组件
// 由于逐级向上时是从父级组件开始找,所以depth数量并没有包含当前路由组件
// 假如depth=2,则route.matched数组前两项都是父级,第三项则是当前组件,所以depth=索引
let matched = route.matched[depth]
if (!matched) return h();
return h(matched.components, data)
}
}
Этот фрагмент может быть непростым для понимания.
Прежде всего, сделайте логотип для всех компонентов RouterView.
Затем начните сparent
Родительский экземпляр обходит компонент уровень за уровнем и находит верхний корневой экземпляр из текущего родительского экземпляра, то есть когдаparent._routerRoot !== parent
После установки вырваться из петли.
В логике обхода определите экземпляр$vnode
Есть ли какой-либо атрибут данных под атрибутомrouterView
свойства, естьdepth + 1
, последний пустьparent = parent.$parent
,$parent
Вы получаете экземпляр родительского компонента для запуска рекурсии.
Вы должны знать, что независимо от того, что вы делаете, объект маршрута объект маршрута, соответствующий текущему пути, всегда остается неизменным, иroute.matched
представляет собой массив конфигураций маршрутизации, связанных со всеми текущими путями.
Если текущий путь/a/b/c
, трехуровневая вложенная маршрутизация, то ееroute.matched
Должно быть следующим:
[
{path: "/a", ...},
{path: "/a/b", ...},
{path: "/a/b/c", ...},
]
Вложенные три уровня, есть три компонента RouterView,App.vue、a.vue、b.vue
один из каждого, поэтому при рендеринге/a/b/c
, страница должна выглядеть так:
// /a/b/c
a
b
c
когдаApp.vue
Компонент routerView страницы начинает рендеринг, и выполняется поиск логики компонента.depth
уровень,из родительского экземпляраИтерация до корневого экземпляра в поискахrouterView
Атрибут компонентов, есть 0, поэтомуdepth = 0
,route.matched[0]
который/a
компонент маршрутизации.
когдаa.vue
Компонент routerView страницы начинает рендеринг, и выполняется поиск логики компонента.depth
уровень,из родительского экземпляраИтерация до корневого экземпляра в поискахrouterView
Компонент атрибута, есть 1, поэтомуdepth = 1
,route.matched[1]
который/a
компонент маршрутизации.
когдаb.vue
Компонент routerView страницы начинает рендеринг, и выполняется поиск логики компонента.depth
уровень,из родительского экземпляраИтерация до корневого экземпляра в поискахrouterView
Компоненты атрибута, их 2, поэтомуdepth = 2
,route.matched[2]
который/a
компонент маршрутизации.
Взглянув на страницу еще раз, мы обнаружили, что обе страницы вложенного маршрута являются нормальными.
/ родитель:
/родитель/ребенок:
Итак, понял? Я думаю, что это достаточно подробно, и я не знаю, как прочитать его несколько раз, чтобы сопоставить точки останова или распечатать.
Метод монтирования экземпляра VueRouter идеален
В классе режима маршрутизации мы реализовали несколько методов, связанных с скачками маршрутизации, которые еще не были установлены на классе Vuerouter. Давайте монтируем их вместе, а также ранее установленныеaddRoute & addRoutes
Два метода все еще нуждаются в улучшении.
назадsrc/hello-vue-router/index.js
документ:
export default class VueRouter {
// 导航到新url,向 history栈添加一条新访问记录
push(location) {
this.history.push(location)
}
// 在 history 记录中向前或者后退多少步
go(n) {
this.history.go(n);
}
// 导航到新url,替换 history 栈中当前记录
replace(location, onComplete) {
this.history.replace(location, onComplete)
}
// 导航回退一步
back() {
this.history.go(-1)
}
}
Как и выше, добавление нескольких методов, связанных с маршрутными переходами, на самом деле просто вызывает методы для уже реализованного экземпляра истории.
Затем смотрим на ранее смонтированныйaddRoute & addRoutes
два метода.
В настоящее время, когда эти два метода вызываются, они действительно добавляются, что нормально при нормальных обстоятельствах, но есть особый случай, то есть перед инициализацией пути текущей страницы динамически добавляется компонент маршрутизации текущей страницы. , В настоящее время, если мы используем текущий путь к странице. После загрузки API он просто анализирует и добавляет внутреннюю карту пути, но, поскольку текущий объект маршрутизации не обновляется, страница напрямую сообщает об ошибке.
Поэтому необходимо после динамического добавления выполнить операцию обновления маршрута, на самом деле она еще называетсяtransitionTo
Метод может перейти на текущий путь к странице, конечно, это необходимо, чтобы избежать инициализации маршрута, который равен текущему маршрутуSTART
(начальное значение текущего объекта маршрута, написанное ранее).
Итак, измените эти две функции следующим образом:
// 新增START对象导入
import { START } from "./utils/route";
export default class VueRouter {
// 动态添加路由(添加一条新路由规则)
addRoute(parentOrRoute, route) {
this.matcher.addRoute(parentOrRoute, route)
// 新增
if (this.history.current !== START) {
this.history.transitionTo(this.history.getCurrentLocation())
}
}
// 动态添加路由(参数必须是一个符合 routes 选项要求的数组)
addRoutes(routes) {
this.matcher.addRoutes(routes)
// 新增
if (this.history.current !== START) {
this.history.transitionTo(this.history.getCurrentLocation())
}
}
// ...
}
Это относительно просто, поэтому я не буду вдаваться в подробности.
На этом процесс хэширования завершен.
Следующим шагом является пошаговая реализация режима истории, то есть заполнение класса HTML5History.
Реализация класса HTML5History
Хотя детали реализации классов HTML5History и HashHistory немного отличаются, API-интерфейсы, которые мы хотим написать, одинаковы, чтобы они могли полностью соответствовать унифицированным внешним вызовам.
приходитьhistory/
в папкеhtml5.js
файл, с опытом класса HashHistory выше, код будем вставлять прямо сюда, ибо ничего сложного.
/*
* @path: src/hello-vue-router/history/html5.js
* @Description: 路由模式HTML5History子类
*/
import { History } from './base'
export class HTML5History extends History {
constructor(router) {
// 继承父类
super(router);
}
// 启动路由监听
setupListeners() {
// 路由监听回调
const handleRoutingEvent = () => {
this.transitionTo(getLocation(), () => {
console.log(`HTML5路由监听跳转成功!`);
});
};
window.addEventListener("popstate", handleRoutingEvent);
this.listeners.push(() => {
window.removeEventListener("popstate", handleRoutingEvent);
});
}
// 更新URL
ensureURL() {
if (getLocation() !== this.current.fullPath) {
window.history.pushState(
{ key: Date.now().toFixed(3) },
"",
this.current.fullPath
);
}
}
// 路由跳转方法
push(location, onComplete) {
this.transitionTo(location, onComplete)
}
// 路由前进后退
go(n){
window.history.go(n)
}
// 跳转到指定URL,替换history栈中最后一个记录
replace(location, onComplete) {
this.transitionTo(location, (route) => {
window.history.replaceState(window.history.state, '', route.fullPath)
onComplete && onComplete(route)
})
}
// 获取当前路由
getCurrentLocation() {
return getLocation()
}
}
// 获取location HTML5 路由
function getLocation() {
let path = window.location.pathname;
return path;
}
Как и выше, мы можем легко реализовать класс HTML5Histoy, но есть проблема при использованииhistory
, продолжайте нажиматьrouter-link
При создании той же навигации каждый щелчок будет обновлять страницу, о чем мы и говорили ранее:router-link
Окончательный сгенерированный тег - это тег,history
Режим щелчка тега a, переход на страницу будет запущен по умолчанию, поэтому вам необходимо перехватить поведение по умолчанию события щелчка тега a,hash
Не будет, потому что атрибут href, проанализированный в теге a в хеш-режиме,#
начиная с номера.
Где перехватить? конечноrouter-link
компоненты.
Компоненты RouterLink готовы
Это также относительно просто: метка a, возвращаемая компонентом RouterLink, равномерно добавляется для предотвращения перехода по умолчанию, а затем добавляется ручной переход:
export default {
name: "RouterLink",
props: {
to: {
type: [String, Object],
require: true
}
},
render(h) {
const href = typeof this.to === 'string' ? this.to : this.to.path
const router = this.$router
let data = {
attrs: {
href: router.mode === "hash" ? "#" + href : href
},
//新增
on: {
click: e => {
e.preventDefault()
router.push(href)
}
}
};
return h("a", data, this.$slots.default)
}
}
Как и выше, во втором параметре функции createElement(h) мы добавили блокирующее событие перехода по умолчанию к событию клика, без перехода по умолчанию мы сделали ручной переход, то есть прямой вызовrouter
примерpush
способ прыгать.
Реализация класса AbstractHistory
Нет, на самом деле это очень просто реализовать, это использовать массив для имитации исторического стека вызовов, посмотреть исходный код и написать его за несколько минут, это полностью класс, состоящий из массива и различных массивов. операционные API. Я не буду вдаваться в подробности о проблемах с пространством. .
крючок маршрутизатора имплантата
Если вы следите за реализацией, то на этом этапе основное содержание VueRouter почти готово.Далее вы можете широко распространять свои идеи, а затем самостоятельно находить соответствующую реализацию в исходном коде для справки и, наконец, улучшать ее.router hook
, поскольку хук маршрутизации — одна из оставшихся функций, которую трудно реализовать, это очень хорошая возможность потренироваться.
Советы:Существует три типа хуков маршрутизации:
- Хук глобальной маршрутизации
- Хук маршрутизации компонентов
- Эксклюзивная маршрутизация перед входом в охрану
напиши в конце
Если вы все еще не знаете четко процесс после того, как увидели это, посмотрите на эту картинку еще раз, может быть, вы сможете напрямую открыть вторую вену Рена и Ду!
Основная логика всей реализации по-прежнему в порядке, но в деталях по-прежнему много проблем, потому что мы проигнорировали некоторую проверку и реализацию небольших функций, но все же очень полезно понимать исходный код VueRouter. Рекомендуется выполнить его вручную, а после завершения сразу перейти к полному исходному коду VueRouter. Ошибки приветствуются! Оригинал выжигает мозги, писать не просто, если поможет, ставьте лайк! !
Код проекта:hello-vue-router
в корневом каталогеsrc/hello-vue-router
В папке находится полный код написанного от руки VueRouter, который был прокомментирован
в корневом каталогеvue-router-source
Папка представляет собой аннотированный исходный код VueRouter V3.5.2.