принцип vue-router в соответствии с передовой практикой

Vue.js

Эта статья является серии Vue-маршрутизатора. Будут подробные объяснения браузеров в принципах Vue-Router для лучших практик. Из-за длины статьи рекомендуется выбрать интересующий вас каталог.

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

  • Анализ pushState/replaceState/popstate
  • Принцип реализации Vue-маршрутизатора
  • Разница между маршрутом и маршрутизатором
  • Настройка входа через метаинформацию маршрутизации
  • Установить поведение прокрутки
  • vue маршрутизация по требованию
  • смотреть прослушивает изменения маршрута
  • Как обнаружить возврат физического ключа
  • Как сделать эффект перевернутой книги
  • Как сделать элегантный раздел маршрутизации

Анализ pushState/replaceState/popstate

HTML5 предоставляет операции с содержимым стека истории. Добавляйте адреса в стек истории через history.pushState/replaceState.

Методы pushState/replaceState() pushState() принимает три параметра:

  • государственный объект
  • название (в настоящее время игнорируется)
  • (необязательно) URL-адрес.

Поясним детали этих трех параметров:

  • Объект состояния — состояние объекта состояния представляет собой объект JavaScript, который создает новые записи истории с помощью pushState(). Всякий раз, когда пользователь переходит к новому состоянию, запускается событие popstate, а свойство состояния этого события содержит копию объекта состояния записи истории.

    Объектом состояния может быть что угодно, что может быть сериализовано. Причина в том, что Firefox сохраняет объект состояния на диске пользователя для использования, когда пользователь перезапускает браузер, и мы указываем ограничение размера 640 КБ для объекта состояния после сериализованного представления. Если вы передадите сериализованный объект состояния размером более 640 КБ методу pushState(), метод выдаст исключение. Если вам нужно больше места, рекомендуется использовать sessionStorage и localStorage.

  • title — этот параметр в настоящее время игнорируется Firefox, но может использоваться в будущем. Передача здесь пустой строки должна обезопасить от будущих изменений этого метода. Кроме того, вы можете передать короткий заголовок для состояния перехода.

  • URL-адрес — этот параметр определяет новую историческую запись URL-адреса. Обратите внимание, что браузер не загружает URL-адрес сразу после вызова pushState(), но может загружать URL-адрес позже в некоторых случаях, например, когда пользователь повторно открывает браузер. Новый URL-адрес не обязательно должен быть абсолютным путем. Если новый URL-адрес является относительным, он будет рассматриваться как относительный к текущему URL-адресу. Новый URL-адрес должен иметь то же происхождение, что и текущий URL-адрес, иначе pushState() вызовет исключение. Этот параметр является необязательным и по умолчанию соответствует текущему URL-адресу.

Запись истории изменений:

@clickA
history.pushState({ page: 1 }, "", "a.html");

@clickB
history.pushState({ page: 2 }, "", "b.html");

Событие popstate запускается при изменении записи истории. Если активированная запись истории была создана вызовом history.pushState() или на нее повлиял вызов history.replaceState(), свойство состояния события popstate содержит копию объекта состояния записи истории.

Обратите внимание, что вызов history.pushState() или history.replaceState() не вызовет событие popstate. Это событие запускается только тогда, когда выполняется действие браузера, например, когда пользователь нажимает кнопку «Назад» в браузере (или вызывает history.back() в коде Javascript).

Активируйте кнопку возврата в браузере:

window.addEventListener('popstate', ()=>{
  console.log(location.href)
})

Принцип реализации Vue-маршрутизатора

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

Установить vue-маршрутизатор

install.js

Смонтируйте _router на прототипе Vue через Object.definePropertyrouter 属性的 get 函数上。 这样可以通过 this.router для вызова _router. Преимущество использования get заключается в том, что гарантируется безопасность, а $router можно только прочитать и нельзя изменить.

// 项目内可以通过 this.$router 来获取到
Object.defineProperty(Vue.prototype, '$router', {
  get () { return this._routerRoot._router }
})

Затем внедрите хук-функцию beforeCreate в Vue.mixin, каждый компонент будет вызывать registerInstance и прослушивать _route через Vue.util.defineReactive, так что каждый раз, когда вы входите на новую страницу, будет установлен текущий маршрут.

// 在 `beforeCreate` 中调用了 `registerInstance`
// 其实就是调用了 router-view 组件中的 registerRouteInstance 方法
const registerInstance = (vm, callVal) => {
  let i = vm.$options._parentVnode
  if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
    i(vm, callVal)
  }
}
Vue.mixin({
  beforeCreate () {
    if (isDef(this.$options.router)) {
        this._routerRoot = this
        this._router = this.$options.router
        // 初始化设置监听 popstate
        // 并将 this._route = route
        this._router.init(this)
        // 亮点在这!!!
        // 将 _route 添加监听,当修改 history.current 时就可以触发更新了
        Vue.util.defineReactive(this, '_route', this._router.history.current)
    } else {
      this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
    }
    // 注册实例,调用 router-view 中的方法,修改 route 值,从而更新视图
    registerInstance(this, this)
  },
  destroyed () {
    // 销毁注册实例,因为注册的实例是 undefined
    registerInstance(this)
  }
})

router-view реализует обновление представления

router-view — это функциональный компонент. Хук beforeCreate на странице вызывает registerRouteInstance для изменения текущего экземпляра маршрута. Поскольку _route отслеживается, при изменении matched.instances[name] он повторно запускает рендеринг для обновления представления.

components/view.js

data.registerRouteInstance = (vm, val) => {
  const current = matched.instances[name]
  // 注册路由实例,如果与当前路由与原来路由相等则不变,如果不相等则更新实例
  if (
    (val && current !== vm) ||
    (!val && current === vm)
  ) {
    // 修改当前路由实例
    matched.instances[name] = val
  }
}

Создать объект маршрута

Создание маршрута createRoute, возврат объекта маршрута путем анализа местоположения и других операций

src/util/route.js

export function createRoute (
  record: ?RouteRecord,
  location: Location,
  redirectedFrom?: ?Location,
  router?: VueRouter
): Route {
  const stringifyQuery = router && router.options.stringifyQuery

  let query: any = location.query || {}
  try {
    query = clone(query)
  } catch (e) {}

  const route: Route = {
    name: location.name || (record && record.name),
    meta: (record && record.meta) || {},
    path: location.path || '/',
    hash: location.hash || '',
    query,
    params: location.params || {},
    fullPath: getFullPath(location, stringifyQuery),
    matched: record ? formatMatch(record) : []
  }
  if (redirectedFrom) {
    route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)
  }
  return Object.freeze(route)
}

Здесь мы в основном говорим об установке vue-router, router-view для обеспечения рендеринга представления, create-route для создания экземпляров маршрутизации и о том, как реализовать комбинацию с vue и привязкой данных. Из-за недостатка места я не буду рассказывать подробности, если вам интересно, вы можете просмотреть исходный код.

Разница между маршрутом и маршрутизатором

После объяснения принципа я расскажу вам о разнице между route и router, разницу легко увидеть по исходному коду.

  • $ Router является объектом VueRouter, получить экземпляр объекта маршрутизатора с помощью Vue.use (VueRouter) и конструктора VueRouter, этот объект является глобальным объектом, он содержит всю ключевую маршрутизацию, содержит множество объектов и атрибутов.
  • $route – это объект маршрута. Объект маршрута, который мы создаем с помощью createRoute, включает в себя:
    • Строка пути, равная пути текущего объекта маршрутизации, будет проанализирована как абсолютный путь, например "/home/news" .
    • Объект params, который содержит пары ключ-значение динамических фрагментов и фрагментов с полным соответствием в маршруте.
    • Объект запроса, который содержит пары "ключ-значение" параметров запроса в маршруте. Например, для /home/news/detail/01?favorite=yes вы получите $route.query.favorite == 'yes' .
    • router Маршрутизатор, которому принадлежит правило маршрутизации (и компонент, которому оно принадлежит).
    • Массив matched содержит объекты параметров конфигурации, соответствующие всем фрагментам, содержащимся в текущем совпавшем пути.
    • Название имени текущего пути, если нет пути с именем, имя пустое.

Настройка входа через метаданные маршрутизации

Принцип заключается в том, чтобы установить атрибут auth в мета маршрута.Перед входом в маршрут, оцените, является ли meta.auth истинным.Если это правда, то оцените, вошли ли вы в систему.Если вы не вошли в систему, вызовите метод входа для входа. После успешного входа код обратного вызова === 0 переходит на страницу

const beforeEnter = (to, from, next) => {
  if (to.meta && to.meta.auth) {
    // 未登陆走登陆逻辑
    if (!isLogin()) {
      const nextPage = (res) => {
        if (res.code === 0) {
          next(true)
        } else {
          next(false)
        }
      };
      let targetUrl = location.href.split('#')[0] + '#' + to.fullPath
      // 这里是你的登陆逻辑
      login({
        // 回调后进入页面
        callback: nextPage,
        // 目标页面,登陆成功后进入目标页面
        targetUrl: targetUrl
      });
    } else {
      next(true)
    }
  } else {
    next(true)
  }
}

Вход в настройки компонента Foo

const routes = [
  {
    path: '/Foo',
    name: 'Foo',
    meta: {
      auth: true,
    },
    component: () => ('Foo.vue'),
  },
  {
    path: '/Bar',
    name: 'Bar',
    component: () => ('Bar.vue'),
  },
]

Установить поведение прокрутки

Установите поведение прокрутки и добавьте маршрут.Если есть savePosition, это означает, что это вторая запись и сработала прокрутка, поэтому она будет прокручиваться до ранее открытой позиции.Если это первая запись без savePosition, она будет прокрутите вверх.

const router = new Router({
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    } else {
      return { x: 0, y: 0 }
    }
  },
  routes
})

vue маршрутизация по требованию

<keep-alive>При обертывании динамических компонентов неактивные экземпляры компонентов кэшируются, а не уничтожаются. а также<transition>сходство,<keep-alive>является абстрактным компонентом: он не отображает элемент DOM сам по себе и не появляется в цепочке родительских компонентов.

когда компонент находится в<keep-alive>переключен, его активированные и деактивированные функции ловушки жизненного цикла будут выполняться соответствующим образом.

<!-- 需要缓存的视图组件 -->
<router-view v-if="$route.meta.keepAlive">
  </router-view>
</keep-alive>

<!-- 不需要缓存的视图组件 -->
<router-view v-if="!$route.meta.keepAlive">
</router-view>

конфигурация маршрутизации

const routers = [
  {
    path: '/list',
    name: 'list',
    component: () => import('./views/keep-alive/list.vue'),
    meta: {
      keepAlive: true
    }
  }
]

Поскольку в нашем проекте часто бывают случаи, когда список перепрыгивает детали, а затем детали возвращаются в список, поэтому мы можем судить, нужно ли его кэшировать в соответствии с требованиями проекта.Если он кэшируется, следующие ситуации будут происходить.

смотреть прослушивает изменения маршрута

Иногда нам нужно передать параметры на страницу, чтобы определить, какой контент отображается на странице, например, страница сведений #/detail?infoId=123456, нам нужно отображать различный контент в соответствии с infoId

Обычно мы так пишем

async created() {
  const res = await this.pullData()
}

async pullData () {
  return this.$http.get('http://test.m.com/detail', { infoId })
}

Когда мы снова заходим на страницу сведений через список, несмотря на то, что infoId изменился на infoId=234567, страница не изменилась, потому что страница активна, созданная не будет запускаться снова, созданная выполняется только один раз, когда она созданный.

Чтобы решить эту проблему, нам нужно отслеживать $route и обновлять страницу при изменении маршрута.

watch: {
  '$route': {
    // 页面初始化时立即触发一次
    immediate: true,
    handler(to, from) {
      // 只有进入当前页面的时候,拉取数据
      if(to.path === '/detail') {
        this.pullData();
      }
    }
  }
}

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

Как обнаружить возврат физического ключа

Зачем обнаруживать физический ключ возврата? Например, если у вас есть такая страница списка, нажмите, чтобы перейти на страницу сведений, а затем, когда вы вернетесь, список будет обновлен, и исходное положение не может быть найдено, что очень плохо для пользователя. Давайте посмотрим на пример.

Итак, как мы его оптимизируем? Идея состоит в том, чтобы не обновлять данные, когда пользователь возвращается на страницу списка, а обновлять данные только тогда, когда пользователь активно входит в список.Давайте посмотрим на эффект

Ниже приведен реализованный код. Принцип заключается в отслеживании состояния всплывающих окон. Когда браузер вернется, он вызовет состояние всплывающих окон. В настоящее время мы помечаем isBack как true. После setTimeout 0 оцените isBack (возвращен ли он браузером), и если он не возвращен браузером, то обновите данные.

@Component
export default {
  data() {
    return {
      // 用来判断是否是通过返回键返回的
      isBack: false
    }
  },
  created () {
    // 如果是物理键返回的就设置 isBack = true
    this.$_onBack(()=>{
      this.isBack = true;
    });
  },
  watch: {
    '$route': {
      immediate: true,
      handler(to, from) {
        // 每次进入路由重置 isBack = false
        this.isBack = false;
        if(to.path === '/list') {
          // 等待路由的 popstate 监听结束
          setTimeout(()=>{
            !this.isBack && this.pullData();
          })
        }
      }
    }
  }
}

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


_onBack(cb) {
  window.addEventListener(
    "popstate",
    (e) => {
      if(typeof cb  === 'function') {
        if(e.state) {
          cb(true)
        }
      }
    },
    false
  );
};

Как сделать эффект перевернутой книги

Он использует компонент перехода vue в сочетании с vue-router для выполнения некоторых эффектов перехода на маршруте. Смотри на картинку и говори

<template>
  <div class="wrap">
    <transition :name="transitionName">
      <router-view class="child-view"></router-view>
    </transition>
  </div>
</template>
<script>

export default {
  data() {
    return {
      transitionName: 'turning-down'
    }
  },
  watch: {
    '$route' (to, from) {
      if(to.path > from.path) {
        // 进入下一页
        this.transitionName = 'turning-up';
      }else{
        // 返回上一页
        this.transitionName = 'turning-down';
      }
    }
  }
}
</script>

<style scoped lang="scss">
.child-view {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  transition: all 4s ease;
  transform-origin: 0% center;
}

.turning-down-enter{
  opacity: 1;
  transform-origin: left;
  transform-style: preserve-3d;
  -webkit-transform: perspective(1000px) rotateY(-180deg);
  transform: perspective(1000px) rotateY(-180deg);
}

.turning-up-leave-active {
  transform-style: preserve-3d;
  transform: perspective(1000px) rotateY(-180deg);
  z-index: 100;
}
</style>

Настроить маршрутизацию

export default [
  {
    path: '/Home',
    name: 'home',
    component: () =>
      import('../views/vue/vue-router/Home.vue'),
    children: [
      {
        path: '/Home/First',
        name: 'Home-First',
        component: () =>
          import('../views/vue/vue-router/First.vue'),
      },
      {
        path: '/Home/Second',
        name: 'Home-Second',
        component: () =>
          import('../views/vue/vue-router/Second.vue'),
      }
    ]
  }
]

Измените имя перехода, отслеживая изменение маршрута на домашней странице, и переключите стиль входа/выхода из активного компонента перехода при переключении маршрута, чтобы при переключении маршрута можно было добиться эффекта переворачивания книги.

Как сделать элегантный раздел маршрутизации

По мере роста проекта страницы в проекте могут достигать десятков и даже больше, так как же управлять этими страницами? Наш подход заключается в дифференциации маршрутов по функциям.

Например, мы разделены на 5 секций, в каждой секции разное количество маршрутов

-- a.js
-- b.js
-- c.js
-- d.js
-- e.js

Нам нужно ввести эти пять маршрутов по отдельности и объединить их

import a from 'routers/a'
import b from 'routers/b'
import c from 'routers/c'
import d from 'routers/d'
import e from 'routers/e'

const routers = [].concat(a, b, c, d, e)

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

Ниже приведен раздел маршрутизации, который я сделал, используя метод require.context веб-пакета для экспорта всех необходимых путей. require.context имеет три параметра:

  • Первый параметр, соответствующий каталог пути (начиная с текущего каталога)
  • Второй параметр, требуется ли глубокая обход
  • Третий параметр, обычное совпадение, соответствует нужному вам пути.
  • Следует отметить, что require не может напрямую экспортировать имена переменных.

Например, следующий пример вызовет ошибку

const a = './route/a.js'
// 会报错,a 不是一个模块
require(a)

Таким образом, require может добавлять только строки или использовать конкатенацию строк.

const a = 'a.js'
require('./route/' + a)

Таким образом, webpack упакует все файлы в ./route/ в модули, и вы можете использовать require для ссылки

Ниже приведен полный пример

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

const routes = []
const context = require.context('./router', true, /\/[\w]+\.(js|ts)$/)

context.keys().forEach(_ => {
  const path = _.replace('./', '')
  routes.push(...require('./router/' +  path).routes)
})

export default new Router({
  routes: [
    { path: '/', redirect: '/Home' },
    ...routes
  ]
})

Эта статья относится к:Билеты. Qqq.com / есть? __ автор = m za ...