10 минут, чтобы полностью понять маршрутизацию одностраничных приложений.

Апплет WeChat
10 минут, чтобы полностью понять маршрутизацию одностраничных приложений.

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

Возможности одностраничного приложения

"Предполагать:"На веб-странице есть кнопка, нажмите, чтобы перейти на другие страницы сайта.

"Многостраничное приложение:"Нажатие на кнопку перезагрузит html-ресурс и обновит всю страницу;

"Одностраничное приложение:"Нажмите кнопку, никакого нового html-запроса, только частичное обновление, которое может создать почти родной опыт, гладкий, как шелк.

Почему одностраничные приложения SPA могут практически не обновляться? Из-за своего SP—single-page. При первом входе в приложение уникальныйhtmlСтраница и ее общедоступные статические ресурсы, последующий так называемый «скачок», больше не берутся с сервераhtmlфайл, простоDOMОперация замены является модульной (цзя) (чжуан).

Так как же js фиксирует время переключения компонентов и меняет URL-адрес браузера без обновления? Зависит отhashа такжеHTML5History.

хэш-маршрутизация

характерная черта

  1. аналогичныйwww.xiaoming.html#barхэш-маршрутизация, когда#При изменении последующего хеш-значения данные не будут запрашиваться с сервера.hashchangeсобытия для отслеживания изменений URL,DOMДействия для имитации перехода по страницам
  2. Не требуется взаимодействие с сервером
  3. Не подходит для SEO

принцип

hash
hash

Маршрутизация истории HTML5

характерная черта

  1. HistoryРежим — это новая функция HTML5, которая более интуитивно понятна, чем хеш-маршрутизация, и выглядит следующим образом.www.xiaoming.html/bar, симуляция перехода на страницу выполненаhistory.pushState(state, title, url)Чтобы обновить маршрут браузера, прослушивайте изменения маршрута.popstateсобытие для работыDOM
  2. Требуется внутреннее сотрудничество для перенаправления
  3. Относительно SEO дружественный

принцип

HTML5History
HTML5History

Интерпретация исходного кода Vue-router

кVueмаршрутизацияvue-routerНапример, давайте вместе посмотрим на его исходный код.

Советы: Поскольку основное внимание в этой статье уделяется объяснению двух режимов одностраничной маршрутизации, ниже перечислены только некоторые ключевые коды, в основном для объяснения:

  1. Зарегистрировать плагин
  2. Конструктор VueRouter, отличающийся режимом маршрутизации
  3. Глобальная регистрация компонентов
  4. методы push и listen для режима hash/HTML5History
  5. метод перехода

Зарегистрировать плагин

Прежде всего, в качестве плагина необходимо выставитьinstallСознание метода, дающегоVueпапа идиuse.

исходный кодinstall.jsВ файле определяется способ регистрации и установки плагиновinstall, смешанные методы для функции ловушки каждого компонента иbeforeCreateИнициализировать маршрут при выполнении хука:

Vue.mixin({
  beforeCreate () {
    if (isDef(this.$options.router)) {
      this._routerRoot = this
      this._router = this.$options.router
      this._router.init(this)
      Vue.util.defineReactive(this, '_route', this._router.history.current)
    } else {
      this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
    }
    registerInstance(this, this)
  },
  // 全文中以...来表示省略的方法
  ...
});

различать режим

Затем мы начинаем сindex.jsНайдите базовый класс для всего плагинаVueRouter, нетрудно заметить, что он находится вconstructor, в зависимости отmodeИспользование разных экземпляров маршрутизации.

...
import {install} from './install';
import {HashHistory} from './history/hash';
import {HTML5History} from './history/html5';
...
export default class VueRouter {
  static install: () => void;
  constructor (options: RouterOptions = {}) {
    if (this.fallback) {
      mode = 'hash'
    }
    if (!inBrowser) {
      mode = 'abstract'
    }
    this.mode = mode
           
    switch (mode) {
      case 'history':
        this.history = new HTML5History(this, options.base)
        break
      case 'hash':
        this.history = new HashHistory(this, options.base, this.fallback)
        break
     case 'abstract':
        this.history = new AbstractHistory(this, options.base)
        break
     default:
      if (process.env.NODE_ENV !== 'production') {
        assert(false, `invalid mode: ${mode}`)
      }
    }
  }
}

Зарегистрируйте компонент router-link глобально

В настоящее время мы можем спросить: при использовании vue-router общий<router-link/>,<router-view/>Где он был введен?

назадinstall.jsфайл, который вводит и глобально регистрирует компоненты router-view и router-link:

import View from './components/view';
import Link from './components/link';
...
Vue.component('RouterView', View);
Vue.component('RouterLink', Link);

существует./components/link.jsсередина,<router-link/>Компонент привязан по умолчаниюclickсобытие, нажмите триггерhandlerметод для выполнения соответствующей операции маршрутизации.

const handler = e => {
  if (guardEvent(e)) {
    if (this.replace) {
      router.replace(location, noop)
    } else {
      router.push(location, noop)
    }
 }
};

Так же, как начало упомянуто,VueRouterотличается в конструктореmodeИнициализированные экземпляры истории для разных режимов, таким образомrouter.replace、router.pushметоды не одинаковы. Далее мы вытащим исходный код этих двух режимов соответственно.

хэш-режим

В файле history/hash.js определяетсяHashHistoryкласс, наследуемый от history/base.jsHistoryбазовый класс.

этоprototypeопределено вышеpushМетод: в среде браузера, поддерживающей режим HTML5History (supportsPushStateправда) звонитеhistory.pushStateдля изменения адреса браузера; в других средах браузера он будет использоваться напрямуюlocation.hash = pathчтобы заменить его новым хеш-адресом.

На самом деле, когда я впервые прочитал это, я немного сомневался: поскольку это уже режим хеширования, почему я должен его осуждать?supportsPushState? Это было для поддержкиscrollBehavior,history.pushStateВы можете передать ключ параметра в прошлом, чтобы у каждой истории URL был ключ, и ключ использовался для сохранения информации о местоположении каждого маршрута.

В то же время прототип связанsetupListenersметод, отвечающий за отслеживание времени изменения хэша: в среде браузера, поддерживающей режим HTML5History, отслеживатьpopstateсобытия; в других браузерах слушатьhashchange. После прослушивания изменения запуститеhandleRoutingEventметод, который вызывает родительский классtransitionToЛогика перехода для выполнения операции замены DOM.

import { pushState, replaceState, supportsPushState } from '../util/push-state'
...
export class HashHistory extends History {
  setupListeners () {
    ...
    const handleRoutingEvent = () => {
        const current = this.current
        if (!ensureSlash()) {
          return
        }
        // transitionTo调用的父类History下的跳转方法,跳转后路径会进行hash化
        this.transitionTo(getHash(), route => {
          if (supportsScroll) {
            handleScroll(this.router, route, current, true)
          }
          if (!supportsPushState) {
            replaceHash(route.fullPath)
          }
        })
      }
      const eventType = supportsPushState ? 'popstate' : 'hashchange'
      window.addEventListener(
        eventType,
        handleRoutingEvent
      )
      this.listeners.push(() => {
        window.removeEventListener(eventType, handleRoutingEvent)
      })
  }
  
  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const { current: fromRoute } = this
    this.transitionTo(
      location,
      route => {
        pushHash(route.fullPath)
        handleScroll(this.router, route, fromRoute, false)
        onComplete && onComplete(route)
      },
      onAbort
    )
  }
}
...

// 处理传入path成hash形式的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}`
}
...

// 替换hash
function pushHash (path) {
  if (supportsPushState) {
    pushState(getUrl(path))
  } else {
    window.location.hash = path
  }
}

// util/push-state.js文件中的方法
export const supportsPushState =
  inBrowser &&
  (function () {
    const ua = window.navigator.userAgent

    if (
      (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
      ua.indexOf('Mobile Safari') !== -1 &&
      ua.indexOf('Chrome') === -1 &&
      ua.indexOf('Windows Phone') === -1
    ) {
      return false
    }
    return window.history && typeof window.history.pushState === 'function'
  })()

Режим истории HTML5

похожий,HTML5HistoryКласс определен в history/html5.js.

определениеpushМетод прототипа, вызовhistory.pusheStateИзмените путь браузера.

В то же время прототипsetupListenersпара методовpopstateОсуществляется мониторинг событий, своевременная замена DOM.

import {pushState, replaceState, supportsPushState} from '../util/push-state';
...
export class HTML5History extends History {

  setupListeners () {

    const handleRoutingEvent = () => {
    const current = this.current;
    const location = getLocation(this.base);
    if (this.current === START && location === this._startLocation) {
      return
    }

    this.transitionTo(location, route => {
      if (supportsScroll) {
        handleScroll(router, route, current, true)
      }
    })
    }
    window.addEventListener('popstate', handleRoutingEvent)
    this.listeners.push(() => {
      window.removeEventListener('popstate', handleRoutingEvent)
    })
  }
  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const { current: fromRoute } = this
    this.transitionTo(location, route => {
      pushState(cleanPath(this.base + route.fullPath))
      handleScroll(this.router, route, fromRoute, false)
      onComplete && onComplete(route)
    }, onAbort)
  }
}

...

// util/push-state.js文件中的方法
export function pushState (url?: string, replace?: boolean) {
  saveScrollPosition()
  const history = window.history
  try {
    if (replace) {
      const stateCopy = extend({}, history.state)
      stateCopy.key = getStateKey()
      history.replaceState(stateCopy, '', url)
    } else {
      history.pushState({ key: setStateKey(genStateKey()) }, '', url)
    }
  } catch (e) {
    window.location[replace ? 'replace' : 'assign'](url)
  }
}

transitionTo обрабатывает логику смены маршрута

Два упомянутых выше режима маршрутизации запускаются при прослушиванииthis.transitionTo, что это? На самом деле это метод-прототип, определенный в базовом классе history/base.js для обработки логики изменения маршрутизации. пройти первымconst route = this.router.match(location, this.current)Сравните входящее значение с текущим значением и верните соответствующий объект маршрутизации, затем оцените, совпадает ли новый маршрут с текущим маршрутом, и если он такой же, верните его напрямую;this.confirmTransitionВыполните обратный вызов, чтобы обновить объект маршрутизации и заменить DOM, связанный с представлением.

export class History {
 ...
 transitionTo (
    location: RawLocation,
    onComplete?: Function,
    onAbort?: Function
  ) {
    const route = this.router.match(location, this.current)
    this.confirmTransition(
      route,
      () => {
        const prev = this.current
        this.updateRoute(route)
        onComplete && onComplete(route)
        this.ensureURL()
        this.router.afterHooks.forEach(hook => {
          hook && hook(route, prev)
        })

        if (!this.ready) {
          this.ready = true
          this.readyCbs.forEach(cb => {
            cb(route)
          })
        }
      },
      err => {
        if (onAbort) {
          onAbort(err)
        }
        if (err && !this.ready) {
          this.ready = true
          // https://github.com/vuejs/vue-router/issues/3225
          if (!isRouterError(err, NavigationFailureType.redirected)) {
            this.readyErrorCbs.forEach(cb => {
              cb(err)
            })
          } else {
            this.readyCbs.forEach(cb => {
              cb(route)
            })
          }
        }
      }
    )
  }
  ...
}

наконец

Что ж, вышеизложенное — это небольшое знание одностраничной маршрутизации, я надеюсь, что мы сможем работать вместе с самого начала и никогда не сдаваться~~