Vue реализует кеш страниц, навигатор возврата по свайпу влево

внешний интерфейс API Vue.js vue-router

предисловие

В этой статье будет рассказано, как реализовать элемент управления навигатором, который отображает соответствующий компонент маршрута без использования представления маршрутизатора, предоставляемого vue-router, и постепенно увеличивать различие между основным и второстепенным этапами, кэшированием страниц, анимацией переключения страниц, возвратом влево. поддержка и другие функции.

Исходный код этого компонента находится у меня на гитхабе:GitHub.com/coolingtower0223/thatv…

Демонстрация этого компонента:navigator-demo.herokuapp.com/#/view1(Рекомендуется открывать его на мобильном устройстве (вы можете столкнуться с проблемой возврата левого пролистывания при использовании браузера, такого как Safari, на устройстве iOS) или использовать инструмент chrome dev, чтобы открыть его в мобильном режиме для поддержки сенсорного ввода. Мероприятия)

нужно

Компания автора разработала фреймворк одностраничного веб-приложения, изначально использовавшийся для Backbone.
В этом приложении используется популярный верхний заголовок, средний контент и нижняя панель вкладок.

tabbar的例子
Пример панели вкладок

Этот макет обычно требует, чтобы заголовок, контент и панель вкладок имели следующую логику рендеринга.

  • панель вкладок создается только один раз во время запуска приложения
  • Существует однозначное соответствие между заголовком и содержимым, и разные представления соответствуют разным заголовкам.
  • После нажатия кнопки на панели вкладок представлен вид в приложении.основной вид
    • Например, на рисунке ниже на панели вкладок пять кнопок, тогда в приложении 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.cacheObject, при скачке роута (то есть вызов рендера), если страница не закэшировалась, добавляем в нее кеш 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 — дополнительными представлениями.

navigator-demo.herokuapp.com/#/view1

Благодаря приведенной выше схеме мы можем абстрагировать управление представлением всего приложения в следующем режиме (показать только часть логики):

page_stack_draft
page_stack_draft

Обработка прыжкового поведения

В предыдущем разделе мы разобрали поведение прыжков в различных ситуациях 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
}

ты закончил

Готовый компонент навигатора имеет богатый интерфейс:

  1. Вы можете использовать isMain, чтобы определить, какие страницы должны быть размещены в основном представлении, а какие — во вспомогательном представлении.
  2. Ряд хуков перехода, таких как onBeforeEnter, onEnter, onBeforeLeave, onLeave, можно использовать для достижения эффектов перехода.
  3. Метод onTouch можно использовать для реализации эффекта движения при касании.
  4. Можно использовать положения swipeBackEdgeThreshold. Срабатывает левое скользящее сенсорное действие, требуемое расстояние от левого края пальца.
  5. Вы можете использовать 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, что можно расценивать как большой выигрыш.