предисловие
В этой статье будет рассказано, как реализовать элемент управления навигатором, который отображает соответствующий компонент маршрута без использования представления маршрутизатора, предоставляемого vue-router, и постепенно увеличивать различие между основным и второстепенным этапами, кэшированием страниц, анимацией переключения страниц, возвратом влево. поддержка и другие функции.
Исходный код этого компонента находится у меня на гитхабе:GitHub.com/coolingtower0223/thatv…
Демонстрация этого компонента:navigator-demo.herokuapp.com/#/view1(Рекомендуется открывать его на мобильном устройстве (вы можете столкнуться с проблемой возврата левого пролистывания при использовании браузера, такого как Safari, на устройстве iOS) или использовать инструмент chrome dev, чтобы открыть его в мобильном режиме для поддержки сенсорного ввода. Мероприятия)
нужно
Компания автора разработала фреймворк одностраничного веб-приложения, изначально использовавшийся для Backbone.
В этом приложении используется популярный верхний заголовок, средний контент и нижняя панель вкладок.
Этот макет обычно требует, чтобы заголовок, контент и панель вкладок имели следующую логику рендеринга.
- панель вкладок создается только один раз во время запуска приложения
- Существует однозначное соответствие между заголовком и содержимым, и разные представления соответствуют разным заголовкам.
- После нажатия кнопки на панели вкладок представлен вид в приложении.основной вид
- Например, на рисунке ниже на панели вкладок пять кнопок, тогда в приложении 5 основных видов.
- Основной вид обычно предназначен для представления наиболее важных функций в приложении, которые первыми отображаются для пользователей, таких как «Моя информация», «Список продуктов», «Рекомендации на главной странице» и т. д.
- Основное представление должно создаваться только один раз во время выполнения приложения. Например, когда пользователь открывает домашнюю страницу в первый раз, динамическое содержимое на домашней странице может отображаться с помощью вызовов API; когда пользователь открывает домашнюю страницу во второй раз, отображаются только страницы, кэшированные между ними. Созданные, смонтированные и другие функции жизненного цикла этой страницы не должны использоваться.
- Анимация не требуется при переключении между основными представлениями приложения.
- После нажатия кнопки в контент или заголовок, представленный представление является одним в приложениивид сбоку
- Подпредставление — это представление, используемое другими функциональными страницами, кроме основного представления.
- Вторичное представление обычно назначается для представления второстепенных функций в приложении, предназначенных для отображения определенных данных, или представления функций с длительным процессом, таким как «подробное представление продукта», «шаг в регистрационной форме». , так далее.
- Переключение страниц с участием подпредставления требует анимационных эффектов
- Каждый раз, когда вы переходите к подпредставлению, в зависимости от ситуации, подпредставление должно быть новым экземпляром.
- В дополнительном представлении вы можете провести пальцем влево, чтобы вернуться к предыдущему представлению.
- (Пожалуйста, обратитесь к следующим подразделам для конкретных правил перехода)
В эпоху Backbone правило маршрутизации состояло только из совпадающего шаблона пути и соответствующей функции обработки.Когда хэш-часть URL-адреса изменяется и измененное значение соответствует правилу маршрутизации, вызывается функция обработки, указанная в правиле маршрутизации.. В функции обработки нам нужно реализовать всю логику обновления и рендеринга содержимого страницы.
В эпоху Vue от каждого небольшого компонента страницы до самой страницы — это компонент Vue. Правило маршрутизации в Vue состоит из совпадающего шаблона пути и соответствующего компонента.Когда хэш-часть URL-адреса изменяется и измененное значение соответствует определенному правилу маршрутизации, Vue создаст экземпляр компонента, соответствующего этому правилу, и отобразит его в компоненте просмотра маршрутизатора в приложении.. Нам не нужно самим реализовывать логику обновления и отрисовки содержимого страницы.
После приведенного выше сравнения мы можем обнаружить, что Backbone необходимо реализовать логику рендеринга соответствующей маршрутизации самостоятельно, поэтому мы можем реализовать вышеуказанные функции, такие как кэширование страниц и переход анимации, самостоятельно. Однако, основываясь на представлении vue-router о маршрутизаторе, нельзя предотвратить поведение некоторых фреймворков по умолчанию (например, каждый раз при переключении маршрута соответствующий компонент является новым экземпляром).
Хотя, определяя правило маршрутизации с пустым атрибутом компонента и используя функцию ловушки beforeEach vue-router, можно также достичь определенной цели взлома. Но когда я начал реализовывать это требование, мои коллеги уже написали все правила маршрутизации с атрибутами компонентов. Чтобы уменьшить количество модификаций кода и достичь определенной степени повторного использования за счет реализации пользовательских элементов управления, я, наконец, решил написать свой собственный компонент представления маршрутизации без представления маршрутизатора, предоставляемого vue-router.
Простейший роутер-просмотр
Как упоминалось в предыдущем разделе, нам нужно реализовать собственный навигатор, не полагаясь на компонент router-view, официально предоставляемый vue-router. Анализируя функции и особенности router-view, мы можем сделать:
- Как компонент, router-view не имеет собственного фиксированного шаблона. Это означает, что мы можем использовать только функцию рендеринга для реализации этого компонента.
- В методе рендеринга этого компонентаНеобходимо вернуть vnode компонента, соответствующего текущему маршруту
- Метод рендеринга этого компонента будет вызываться, когда свойство данных компонента или состояние объекта, внедренного в компонент, изменяется, и значение состояния было обновлено при его вызове.
После периода исследования мы знаем, что при вызове функции рендеринга доступ к компоненту, соответствующему текущему маршруту, можно получить через следующие свойства в области действия функции рендеринга:this.$route.matched[0].components.default
Семантика приведенного выше кода такова: компонент по умолчанию в компоненте, указанном первым правилом маршрутизации, которому соответствует текущий маршрут.
Также известно: первый параметр функции рендеринга (обычно с именем h) — это функция внутри Vue для создания vnode. он может либо использоватьh(tag, attributes, children)
, который возвращает vnode произвольных свойств и структур, или вы можете использоватьh(Component)
, возвращает vnode указанного компонента.
Таким образом, нам нужно только реализовать такой метод рендеринга, чтобы реализовать базовое представление маршрутизатора:
render(h) {
return h(this.$route.matched[0].components.default)
}
В рамках компонентов, отличных от функции рендеринга, если функция h недоступна, вы можете использовать вместо нее this.$createElement
учиться по аналогии
Простейший пример router-view выше иллюстрирует это: при изменении маршрута будет вызываться метод рендеринга нашего пользовательского компонента. Нам просто нужно вернуть vnode, который мы хотим отобразить, в методе рендеринга.
Если мы просто вернем vnode соответствующего компонента, ему еще далеко до нужных нам функций кеша страниц и просмотра стека. Логика метода рендеринга навигатора следующая:
- Внутри компонента создайте
this.cache
Object, при скачке роута (то есть вызов рендера), если страница не закэшировалась, добавляем в нее кеш vnode, код аналогиченthis.cache[routeName] = h(this.$route.matched[0].components.default)
- Внутри компонента создайте
this.history
Массив, при скачке маршрута (то есть вызов рендера) каждый раз записывать текущий маршрут - В функции рендеринга, согласно
this.history
История маршрута в , отthis.cache中
Извлеките соответствующие кэшированные vnode по очереди, чтобы сформировать vnode с каждой исторической страницей рядом. Пока vnode страницы, соответствующей текущему маршруту, находится в конце этих параллельных vnode, страница может отображаться правильно, задав соответствующий стиль css для каждой страницы.
Вот пример, объясняющий приведенную выше логику:
Когда приложение запускается, сначала необходимо отобразить содержимое домашней страницы. В настоящее время:
this.cache = { home: 组件Home.vue的vnode实例 } this.history = ['home'] // render函数所返回的vnode,最终会被渲染成如下DOM结构 <div class="navigator"> <div class="navigator-page"> <!-- home页的内容 --> </div> </div>
После запуска приложения пользователь нажимает кнопку регистрации, и необходимо отобразить содержимое страницы #register.
this.cache = { home: 组件Home.vue的vnode实例, register: 组件Register.vue的vnode实例 } this.history = ['home', 'register'] // render函数所返回的vnode,最终会被渲染成如下DOM结构 <div class="navigator"> <div class="navigator-page"> <!-- home页的内容 --> </div> <div class="navigator-page"> <!-- register页的内容 --> </div> </div>
Обратите внимание, что здесь за пределами vnode нам нужно отрисовать, обернуть класс с именемnavigator
а такжеnavigator-page
Родительский узел, это для указания того же стиля, который требуется для полноэкранного рендеринга для каждой страницы DOM, например.position: absolute
Ждать
Завершение поведения прыжка
Как упоминалось в предыдущем разделе, при переходе между различными представлениями результирующее поведение рендеринга отличается в зависимости от начального и конечного представлений, в которых происходит переход. Здесь она организована следующим образом:
оригинальный вид | новый вид | был ли посещен новый вид | поведение |
---|---|---|---|
основной вид | основной вид | будь то | Непосредственно заменить содержимое области просмотра приложения |
основной вид | вид сбоку | будь то | Новый вид входит в область просмотра справа налево, а старый вид выходит из области просмотра справа налево. |
вид сбоку | основной вид | будь то | Замените вид под текущим подвидом на целевой основной вид и сделайте так, чтобы новый вид входил в область просмотра слева направо, а старый вид выходил из области просмотра слева направо. |
вид сбоку | вид сбоку | нет | Новый вид входит в область просмотра справа налево, а старый вид выходит из области просмотра справа налево. |
вид сбоку | вид сбоку | да | Замените вид под текущим подвидом на целевой подвид и сделайте так, чтобы новый вид входил в область просмотра слева направо, а старый вид выходил из области просмотра слева направо. |
Приведенный выше завершающий контент является относительно абстрактным, и демонстрация по ссылке ниже является примером, отражающим приведенную выше логику. Среди них view1 и view3 являются основными представлениями, а view2 и view4 — дополнительными представлениями.
Благодаря приведенной выше схеме мы можем абстрагировать управление представлением всего приложения в следующем режиме (показать только часть логики):
Обработка прыжкового поведения
В предыдущем разделе мы разобрали поведение прыжков в различных ситуациях 5. Здесь мы суммируем некоторые из них и объясняем трудности реализации. За всей конкретной логикой вы можете обратиться к исходному коду навигатора.
основной вид к основному виду
Это должен быть самый простой случай, в любом случае, для перехода маршрута из основного вида в основной вид нам нужно всего лишь «заменить» содержимое страницы в области просмотра приложения. Фактическая реализация кода такова:
// fromRoute是前一个路由的key,toRoute是当前路由的key
// 从主视图
if (this.isMain(this.cache[this.fromRoute].$route)) {
// 到主视图
if (this.isMain(this.cache[this.toRoute].$route)) {
// 以下4行,如果history中有当前路由的key,则将此记录调换至最后;如果没有则新增一条
if (this.history.indexOf(this.toRoute) > -1) {
this.history.splice(this.history.indexOf(this.toRoute), 1)
}
this.history.push(this.toRoute)
// 在mainToMain方法中做一些vnode本身的修改操作,或者需要在nextTick中执行的DOM操作
this.mainToMain(this.toRoute)
}
}
// 执行至此,this.history中的历史记录已经按我们需要的层叠顺序排列
// 只需要根据历史记录取出缓存的vnode节点,并排返回即可
const children = []
for (let i = 0; i < this.history.length; i++) {
const cached = this.cache[this.history[i]]
const node = this.wrap(cached) // wrap方法为页面的vnode外围增加一个<div class="navigator-page">的父节点,方便后续的样式控制
children.push(node)
}
const composedVNode = h('div', {
class: 'navigator'
}, children)
return composedVNode
основной вид в подвид
В этом случае, поскольку подпредставление всегда является новым экземпляром при переходе к подпредставлению, поэтому дляthis.history
, нам просто нужно добавить новую запись истории.
Требуется эффект перехода от основного вида к подвиду. Чтобы улучшить настраиваемость компонента, здесь мы предоставляем интерфейс реализации анимации перехода пользователю компонента через реквизиты onBeforeEnter, onBeforeLeave, onEnter и onLeave. Использование этих интерфейсов очень похоже на использование хуков JavaScript перехода в vue, вы можете обратиться кv UE JS.org/V2/expensive/suddenly…
// onBeforeEnter回调为即将进入的元素在进入前的状态
// el为即将进入的元素,done为动画执行完毕后需要执行的回调
onBeforeEnter(el, done) {
const h = () => {
done()
el.removeEventListener('transitionend', h)
}
el.addEventListener('transitionend', h)
el.style.transform = 'translateX(100%)'
el.style.transition = 'all 0.3s'
},
// onEnter回调为即将进入的元素在进入后的状态
// el为进入的元素,done为动画执行完毕后需要执行的回调
onEnter(el, done) {
const h = () => {
done()
el.removeEventListener('transitionend', h)
}
el.addEventListener('transitionend', h)
el.style.transform = 'translateX(0%)'
el.style.transition = 'all 0.3s'
},
Методы реализации этих интерфейсов в компоненте следующие:
// 由于需要将生成的DOM暴露出去,这里的查找元素的方法需要在nextTick中执行,否则无法找到节点
setTimeout(() => {
// 我们在wrap方法中已经实现了为页面vnode包裹一个我们需要的父节点
// wrap也可以为页面vnode的父节点添加类似于id: 'navigator-page-path-name'这样的属性
// 方便了我们在这里直接获取对应的DOM
const leaveEl = document.querySelector('#' + this.getNavigatorPageId(fromRoute))
const enterEl = document.querySelector('#' + this.getNavigatorPageId(toRoute))
// 先调用onBefore系列方法
this.onBeforeLeave(leaveEl, this.transitionEndCallback)
// 稍作间隔后,调用on系列方法
setTimeout(() => {
this.onLeave(leaveEl, this.transitionEndCallback)
}, 50);
this.onBeforeEnter(enterEl, this.transitionEndCallback)
setTimeout(() => {
this.onEnter(enterEl, this.transitionEndCallback)
}, 50);
}, 0)
См. следующий подраздел, чтобы узнать, что такое this.transitionEndCallback.
Подвид к основному виду
По сравнению с двумя вышеприведенными случаями, в этой ситуации есть еще один «очищающий» шаг.
Так называемая «очистка» заключается в том, что после того, как маршрут от подвида к основному виду заканчивается, вышедший подвид должен быть полностью уничтожен. Поэтому, когда анимация перехода закончена, нам нужно «зачистить» следующие аспекты:
-
this.history
Записи для среднего подвида -
this.cache
кеш vnode для просмотра в середине - DOM отображаемого вторичного вида в компоненте
Среди них при реализации финальной очистки DOM я не использовал DOM API напрямую, а выбрал способ сравнения vue: снова вызвать метод рендера, и вернуть очищенный vnode для реализации.
упоминалось в предыдущем разделеthis.transitionEndCallback
Метод будет вызываться, когда нам понадобится очистка DOM, его реализация очень проста, а именно:
transitionEndCallback() {
this.clear = true
}
просто измените компонентthis.$data.clear
, метод рендеринга будет запущен снова. мы можем ориентироватьсяclear=true
В случае реализации логики очистки DOM:
// this.clear是预先在this.data中设定的一个响应的属性
if (this.clear) {
this.clear = false
// 清理this.history的内容,并相应地清理this.cache的内容
const toClear = this.history.splice(this.history.indexOf(this.toRoute) + 1)
for (let i = 0; i < toClear.length; i++) {
delete this.cache[toClear[i]]
}
// 组合出最后的vnode树
const children = []
for (let i = 0; i < this.history.length; i++) {
const cached = this.cache[this.history[i]]
const node = this.wrap(cached)
children.push(node)
}
const composedVNode = h('div', {
class: 'navigator',
on: {
touchmove: this.handleTouchMove,
touchstart: this.handleTouchStart,
touchend: this.handleTouchEnd
}
}, children)
return composedVNode
}
Давайте поговорим о времени, когда вызывается метод рендеринга.
Согласно предыдущей статье, время вызова метода рендеринга компонента vue выглядит следующим образом:
- render вызывается при изменении источника данных, от которого зависит собственный рендеринг компонента. Например
this.$data
Свойство объявляется измененным, когда -
vm.$route
При модификации (то есть при использовании плагина vue-router маршрутизация меняется)
Позже в процессе разработки автор обнаружил, что поскольку наш проект импортировал vuex, когдаvm.$store
При изменении любого из состояний также будет запущен метод рендеринга. В настоящее время нам не нужно отображать новый контент, поэтому его можно игнорировать с помощью следующего кода:
// 因为render方法是由其他全局状态的改变引起的,这时路由不会变化
if (this.toRoute === this.fromRoute) {
// vue组件的旧vnode保存在_vnode这个属性上,返回它即可
return this._vnode
}
мы также можем использоватьthis._vnode
Для обработки ошибок, если приложение случайно переходит на адрес маршрутизации без правил маршрутизации, оно возвращаетthis._vnode
, просто оставьте страницу как есть.
Реализация левого свайпа назад
Добавление этой функции означает, что нам нужно прослушивать события touchstart, touchmove, touchend в элементе-контейнере.
Как видно из вышеприведенного, предполагается, что главная страница главного представления загружается при запуске приложения, а затем пользователь нажимает кнопку регистрации, и приложение загружает регистр вспомогательного представления. На данный момент структура vnode внутри нашего компонента выглядит следующим образом
<div class="navigator"> <!-- 应该在这个节点上绑定触摸事件 -->
<div class="navigator-page">
<!-- home页的内容 -->
</div>
<div class="navigator-page">
<!-- register页的内容 -->
</div>
</div>
Это событие касания должно быть привязано к самому внешнему корневому узлу компонента, так как это фиксируется при каждом рендеринге.
При использовании метода h для создания vnode директива v-on для привязки событий становится атрибутом on, например:
render(h) {
return h('div', {
class: 'navigator',
on: {
touchmove: this.handleTouchMove,
touchstart: this.handleTouchStart,
touchend: this.handleTouchEnd
}
}, children)
}
При использовании метода h для создания vnode, если вам нужно указать различные атрибуты узла, вы можете обратиться к определению класса VNode в vue. ВидетьGitHub.com/v UE JS/v UE/нет…
Затем мы можем реализовать логику handleTouchMove, handleTouchStart, handleTouchEnd соответственно.
Здесь, чтобы улучшить настраиваемость компонента, мы используем реквизит с именем onTouch, чтобы позволить пользователю компонента настраивать изменения страницы при касании и перетаскивании. Вот пример использования:
// enterEl表示即将进入的元素(对于左滑返回来说即是位于下方的页面)
// leaveEl表示即将离开的元素(对于左滑返回来说即是位于上方的页面)
onTouch(enterEl, leaveEl, x, y) {
const screenWidth = window.document.documentElement.clientWidth
const touchXRatio = x / screenWidth
// 由于在之前的onBeforeLeave等回调中,此元素可能被设定了transition样式的值,这里改回none
enterEl.style.transition = 'none'
leaveEl.style.transition = 'none'
enterEl.style.transform = `translate(${touchXRatio * 100}%)`
leaveEl.style.transform = `translate(${touchXRatio * 50 - 50}%)`
}
Реализация этого интерфейса также очень проста:
handleTouchMove(e) {
if (this.touching) {
// 由于touchmove事件被触发时,组件的DOM已经被渲染,因此可以用this.$el直接访问需要的DOM
const childrenEl = this.$el.children
const enterEl = Array.prototype.slice.call(childrenEl, -1)[0]
const leaveEl = Array.prototype.slice.call(childrenEl, -2, -1)[0]
this.onTouch(enterEl, leaveEl, e.touches[0].pageX, e.touches[0].pageY)
}
}
Чуть сложнее реализация handleTouchEnd.При возникновении события touchend, если горизонтальное положение касания больше порога, нам нужно продолжить воспроизведение возвращенного эффекта анимации перехода и вызватьthis.$router.go(-1)
Сделано обратно. Но проблема в том, что изменение $router вызовет повторный вызов метода рендеринга.
Здесь мы используем управляющую переменнуюbackInvokedByGesture
Чтобы указать, что этот рендеринг вызван завершением операции смахивания влево и изменением маршрута. На этом этапе нам нужно вручную очиститьthis.history
Последний элемент в (то есть история, соответствующая просмотру, оставленному при смахивании влево), и очистить соответствующийthis.cache
Кэшировать, а затем вернуться к финальному дереву vnode. код показывает, как показано ниже:
handleTouchEnd(e) {
if (this.touching) {
const childrenEl = this.$el.children
const el = Array.prototype.slice.call(childrenEl, -1)[0]
const leaveEl = Array.prototype.slice.call(childrenEl, -2, -1)[0]
const x = e.changedTouches[0].pageX
const y = e.changedTouches[0].pageY
// 当触摸结束时的水平位置大于阈值
if (x / window.document.documentElement.clientWidth > this.swipeBackReleaseThreshold) {
// 手动控制路由回退
this.onBeforeLeave(leaveEl, () => {
this.backInvokedByGesture = true
this.transitionEndCallback()
this.$router.go(-1)
})
this.onBeforeEnter(el, () => {})
} else {
// 停留在原页面
this.onLeave(leaveEl, () => {})
this.onEnter(el, () => {})
}
}
this.touching = false
}
// render方法中针对backInvokedByGesture的逻辑
if (this.backInvokedByGesture) {
this.backInvokedByGesture = false
// 删除this.history中的最后一条,并清除this.cache中相应的缓存
const toDelete = this.history.pop()
delete this.cache[toDelete]
// 组合出最后的vnode树
const children = []
for (let i = 0; i < this.history.length; i++) {
const cached = this.cache[this.history[i]]
const node = this.wrap(cached)
children.push(node)
}
const composedVNode = h('div', {
class: 'navigator',
on: {
touchmove: this.handleTouchMove,
touchstart: this.handleTouchStart,
touchend: this.handleTouchEnd
}
}, children)
return composedVNode
}
ты закончил
Готовый компонент навигатора имеет богатый интерфейс:
- Вы можете использовать isMain, чтобы определить, какие страницы должны быть размещены в основном представлении, а какие — во вспомогательном представлении.
- Ряд хуков перехода, таких как onBeforeEnter, onEnter, onBeforeLeave, onLeave, можно использовать для достижения эффектов перехода.
- Метод onTouch можно использовать для реализации эффекта движения при касании.
- Можно использовать положения swipeBackEdgeThreshold. Срабатывает левое скользящее сенсорное действие, требуемое расстояние от левого края пальца.
- Вы можете использовать swipeBackReleaseThreshold, чтобы указать диапазон, который оценивается как обратная операция при отпускании левого свайпа.
Пример использования компонента навигатора выглядит следующим образом:
// template
<navigator
:on-before-enter="transitionBeforeEnter"
:on-before-leave="transitionBeforeLeave"
:on-enter="transitionEnter"
:on-leave="transitionLeave"
:is-main="isMain"
:on-touch="onTouch"
:swipe-back-edge-threshold="0.05"
:swipe-back-release-threshold="0.5"
>
</navigator>
// script
transitionBeforeEnter(el, done) {
el.style.transition = 'all ' + this.transitionDuration + 'ms'
const h = () => {
done()
el.removeEventListener('transitionend', h)
}
el.addEventListener('transitionend', h)
this.setElementTranslateX(el, '100%')
},
transitionBeforeLeave(el, done) {
el.style.transition = 'all ' + this.transitionDuration + 'ms'
const h = () => {
done()
el.removeEventListener('transitionend', h)
}
el.addEventListener('transitionend', h)
this.setElementTranslateX(el, '0%')
},
transitionEnter(el, done) {
el.style.transition = 'all ' + this.transitionDuration + 'ms'
const h = () => {
done()
el.removeEventListener('transitionend', h)
}
el.addEventListener('transitionend', h)
this.setElementTranslateX(el, '0%')
},
transitionLeave(el, done) {
el.style.transition = 'all ' + this.transitionDuration + 'ms'
const h = () => {
done()
el.removeEventListener('transitionend', h)
}
el.addEventListener('transitionend', h)
this.setElementTranslateX(el, '-50%')
},
// route相当于vm.$route,即当前的路由
// 这里将几个特定名字的路由设定为主视图
isMain(route) {
const list = ['Card', 'Rewards', 'Profile', 'Home', 'Coupons']
return list.indexOf(route.name) > -1
},
onTouch(enterEl, leaveEl, x, y) {
const screenWidth = window.document.documentElement.clientWidth
const touchXRatio = x / screenWidth
enterEl.style.transition = 'none'
leaveEl.style.transition = 'none'
enterEl.style.transform = `translate(${touchXRatio * 100}%)`
leaveEl.style.transform = `translate(${touchXRatio * 50 - 50}%)`
}
постскриптум
Изначально vue и vue-router предоставляли встроенные компоненты router-view, keep-alive и transition, которые соответствовали трем основным функциям: представлению маршрутизации, кэшированию страниц и эффектам входа и выхода. , они всегда Ожидаемого эффекта не добиться, а взломать, прочитав исходный код, сложно. В отчаянии я решил реализовать элементы управления самостоятельно и полностью контролировать эту логику. Код продолжал усложняться и подвергался серьезной переделке или двум по мере того, как функции добавлялись шаг за шагом.
Навигатор, реализованный на этот раз, по-прежнему имеет много недостатков, например, метод рендеринга компонентов слишком прост, чтобы соответствовать вложенным маршрутам. Но в процессе реализации я углубил свое понимание роли функции рендеринга, времени запуска и создания vnodes, что можно расценивать как большой выигрыш.