Разбор принципа работы vue-router

внешний интерфейс браузер Vue.js vue-router

Использование vue-router

Весь контент на странице состоит из компонентов, вам нужно только сопоставить путь к компоненту и отобразить компонент на странице.

  1. Реализация страницы: в vue-router он определяет две метки и соответствует частям клика и отображения. Это должно определить часть страницы, по которой щелкнули, и определить часть отображения.

  2. Настройте маршрутизацию в js: во-первых, определите маршрут, реализацию маршрута, который представляет собой объект, состоящий из пути и компонента.

    Два маршрута здесь образуют маршруты:

const routes = [
	{// 首页
		path: '/',
		component: () => import('src/pages/home/index.vue'),
	},
	{// 首页更多功能
		path: '/functionManage',
		component: () => import('src/pages/home/functionManage.vue'),
	},
]
  1. Создайте маршрутизатор для управления маршрутом, он создается конструктором new vueRouter() и принимает параметр маршрутов.

    router.jsв файле

const router = new VueRouter({
	routes,
})
  1. После завершения настройки внедрите экземпляр маршрутизатора в корневой экземпляр vue.

    main.jsв файле

window.vm = new Vue({
	router,
})

Процесс выполнения: когда пользователь щелкает тег router-link, он будет искать его атрибут to, а его атрибут to соответствует пути {path: '/home', component: Home}, настроенному в js, чтобы найти совпадающие компоненты и, наконец, визуализировать компонент, на котором находится метка.

Внешняя маршрутизация обновляет представление страницы без повторного запроса страницы путем изменения URL-адреса.

В настоящее время в среде браузера для достижения этой функциональности существует два вида:

  • Используйте хеш URL;
  • Используйте историю в H5;

vue-router — это плагин маршрутизации фреймворка vue.js, который управляет режимом реализации маршрутизации через параметр режима.

const router = new VueRouter({
	// HTML5 history 模式
	mode: 'history',
	base: process.env.NODE_ENV === 'production' ? process.env.PROXY_PATH : '',
	routes,
})

В файле ввода вам нужно создать экземпляр объекта экземпляра VueRouter, а затем передать его в параметры экземпляра Vue.

var VueRouter = function VueRouter (options) {
    if ( options === void 0 ) options = {};

    this.app = null;
    this.apps = [];
    this.options = options;
    this.beforeHooks = [];
    this.resolveHooks = [];
    this.afterHooks = [];
    // 创建 matcher 匹配函数
    this.matcher = createMatcher(options.routes || [], this);
    // 根据 mode 实例化具体的 History,默认为'hash'模式
    var mode = options.mode || 'hash';
    // 通过 supportsPushState 判断浏览器是否支持'history'模式
    // 如果设置的是'history'但是如果浏览器不支持的话,'history'模式会退回到'hash'模式
    // fallback 是当浏览器不支持 history.pushState 控制路由是否应该回退到 hash 模式。默认值为 true。
    this.fallback = mode === 'history' && !supportsPushState &&   options.fallback !== false;
    if (this.fallback) {
        mode = 'hash';
    }
    if (!inBrowser) {
        // 不在浏览器环境下运行需强制为'abstract'模式
        mode = 'abstract';
    }
    this.mode = mode;

    // 根据不同模式选择实例化对应的 History 类
    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:
        {
            assert(false, ("invalid mode: " + mode));
        }
    }
};
VueRouter.prototype.init = function init (app /* Vue component instance */) {
    
    ...
    
    var history = this.history;

    // 根据history的类别执行相应的初始化操作和监听
    if (history instanceof HTML5History) {
        history.transitionTo(history.getCurrentLocation());
    } else if (history instanceof HashHistory) {
        var setupHashListener = function () {
            history.setupListeners();
        };
        history.transitionTo(
          history.getCurrentLocation(),
          setupHashListener,
          setupHashListener
        );
    }

    history.listen(function (route) {
        this$1.apps.forEach(function (app) {
            app._route = route;
        });
    });
};

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

modehistory:
    'history': HTML5History;
    'hash': HashHistory;
    'abstract': AbstractHistory;
  1. Перед инициализацией соответствующей истории будут выполнены некоторые проверки режима: если браузер не поддерживает метод HTML5History (судя по переменной supportsPushState), режим устанавливается в хэш, если он не запущен в среде браузера, режим установлен на абстрактный;
  2. Методы onReady(), push() и другие в классе VueRouter являются просто прокси, которые на самом деле являются соответствующим методом вызываемого конкретного объекта истории.При инициализации в методе init() различные операции также выполняются в соответствии с конкретная категория объекта истории.

HashHistory

  • Хотя хеш отображается в URL, он не будет включен в HTTP-запрос. Он используется для управления действиями браузера и не влияет на сервер. Поэтому изменение хеша не приведет к перезагрузке страницы.

  • Вы можете добавить события слушателя для изменений хэша:

    window.addEventListener("hashchange",funcRef,false)

  • При каждом изменении хеша (window.location.hash) в историю доступа браузера будет добавляться запись.

function HashHistory (router, base, fallback) {
    History$$1.call(this, router, base);
    // 如果是从history模式降级来的,需要做降级检查
    if (fallback && checkFallback(this.base)) {
        // 如果降级且做了降级处理,则返回
        return
    }
    ensureSlash();
}

function checkFallback (base) {
    // 得到除去base的真正的 location 值
    var location = getLocation(base);
    if (!/^\/#/.test(location)) {
        // 如果此时地址不是以 /# 开头的
        // 需要做一次降级处理,降为 hash 模式下应有的 /# 开头
        window.location.replace(
            cleanPath(base + '/#' + location)
        );
    return true
    }
}

function ensureSlash () {
    // 得到 hash 值
    var path = getHash();
    // 如果是以 / 开头的,直接返回即可
    if (path.charAt(0) === '/') {
        return true
    }
    // 不是的话,需要手动保证一次 替换 hash 值
    replaceHash('/' + path);
    return false
}

function getHash () {
    // 因为兼容性的问题,这里没有直接使用 window.location.hash
    // 因为 Firefox decode hash 值
    var href = window.location.href;
    var index = href.indexOf('#');
    return index === -1 ? '' : href.slice(index + 1)
}

HashHistory.push()

HashHistory.prototype.push = function push (location, onComplete, onAbort) {
    var this$1 = this;

    var ref = this;
    var fromRoute = ref.current;
    this.transitionTo(location, function (route) {
        pushHash(route.fullPath);
        handleScroll(this$1.router, route, fromRoute, false);
        onComplete && onComplete(route);
    }, onAbort);
};

Метод transitionTo() используется для работы с базовой логикой маршрутизации изменений.Самое важное в методе push() — это прямое присвоение хэша окна:

function pushHash (path) {
    window.location.hash = path
}

Изменения хэша автоматически добавляются в историю доступа браузера. Итак, как реализовано обновление представления, посмотрите на метод transitionTo():

History.prototype.transitionTo = function transitionTo (location, onComplete, onAbort) {
    var this$1 = this;

    var route = this.router.match(location, this.current);
    this.confirmTransition(route, function () {
        this$1.updateRoute(route);
        ...
    });
};

History.prototype.updateRoute = function updateRoute (route) {
    var prev = this.current;
    this.current = route;
    this.cb && this.cb(route);
    this.router.afterHooks.forEach(function (hook) {
        hook && hook(route, prev);
    });
};

History.prototype.listen = function listen (cb) {
    this.cb = cb;
};

Видно, что при изменении маршрута вызывается метод this.cb, а метод this.cb задается через History.listen(cb), который задается в init():

В качестве прогрессирующей фронтальной структуры, Vue не должен иметь встроенный атрибут _rooute в его определении компонента. Если в компоненте имеется этот атрибут, он должен быть там, где плагин загружен, то есть объект Vue смешивается в Установите () Метод Vuerooter. Да, исходный код Install.js:

function install (Vue) {
  
  ...

  Vue.mixin({
    beforeCreate: function 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);
    },
    destroyed: function destroyed () {
      registerInstance(this);
    }
  });
}

С помощью метода Vue.mixin() миксин регистрируется глобально, влияя на каждый экземпляр Vue, созданный после регистрации.Миксин определяет адаптивное свойство _route через Vue.util.defineReactive() в хуке beforeCreate. Так называемое адаптивное свойство, то есть при изменении значения _route автоматически вызывается метод render() экземпляра Vue для обновления представления.

$router.push()-->HashHistory.push()-->History.transitionTo()-->History.updateRoute()-->{app._route=route}-->vm.render()

HashHistory.replace()

Разница между методом replace() и методом push() заключается в том, что вместо добавления нового маршрута в начало стека истории доступа браузера он заменяет текущий маршрут:

HashHistory.prototype.replace = function replace (location, onComplete, onAbort) {
    var this$1 = this;

    var ref = this;
    var fromRoute = ref.current;
    this.transitionTo(location, function (route) {
      replaceHash(route.fullPath);
      handleScroll(this$1.router, route, fromRoute, false);
      onComplete && onComplete(route);
    }, onAbort);
};
  
function replaceHash (path) {
    const i = window.location.href.indexOf('#')
    // 直接调用 replace 强制替换 以避免产生“多余”的历史记录
    // 主要是用户初次跳入 且hash值不是以 / 开头的时候直接替换
    // 其余时候和push没啥区别 浏览器总是记录hash记录
    window.location.replace(
        window.location.href.slice(0, i >= 0 ? i : 0) + '#' + path
    )
}

Видно, что он в основном похож на структуру реализации push(), разница в том, что он не присваивает напрямую значение window.location.hash, а вызывает метод window.location.replace для замены маршрута.

Слушайте адресную строку

Вышеупомянутые VueRouter.push() и VueRouter.replace() могут быть вызваны непосредственно в логическом коде компонента vue. Кроме того, в браузере пользователь также может напрямую войти в адресную строку браузера, чтобы изменить маршрут, поэтому он также необходимо прослушивать изменения маршрута в адресной строке браузера и иметь то же поведение ответа, что и вызов через код.В HashHistory эта функция реализована путем прослушивания хэш-изменения через setupListeners:

setupListeners () {
    window.addEventListener('hashchange', () => {
        if (!ensureSlash()) {
            return
        }
        this.transitionTo(getHash(), route => {
            replaceHash(route.fullPath)
        })
    })
}

Этот метод настроен на отслеживание хешрейта события браузера, а вызываемая функция — replaceHash, то есть непосредственный ввод маршрута в адресную строку браузера эквивалентен вызову метода replace() в коде.

HTML5History

Интерфейс истории — это интерфейс, предоставляемый стеком истории браузера.С помощью методов back(), forward(), go() и других методов мы можем читать информацию из стека истории браузера и выполнять различные операции перехода.

HTML5 представил методы history.pushState() и history.replaceState(), которые могут соответственно добавлять и изменять записи истории. Эти методы обычно используются в сочетании с window.onpopstate.

window.history.pushState(stateObject,title,url)
window.history,replaceState(stateObject,title,url)
  • stateObject: когда браузер переходит в новое состояние, будет запущено событие popState, которое будет содержать копию этого параметра stateObject.
  • title: название добавляемой записи
  • url: URL-адрес добавленной записи (необязательно)

Общая черта двух методов pushState и replaceState: когда они вызываются для изменения стека истории браузера, несмотря на то, что текущий URL-адрес изменился, браузер не будет сразу отправлять запрос на URL-адрес, что является внешней маршрутизацией для однократного использования. страница приложения, обновление представления, но не Страница повторного запроса обеспечивает основу.

export function pushState (url?: string, replace?: boolean) {
    saveScrollPosition()
    // 加了 try...catch 是因为 Safari 有调用 pushState 100 次限制
    // 一旦达到就会抛出 DOM Exception 18 错误
    const history = window.history
    try {
        if (replace) {
            // replace 的话 key 还是当前的 key 没必要生成新的
            history.replaceState({ key: _key }, '', url)
        } else {
            // 重新生成 key
            _key = genKey()
            // 带入新的 key 值
            history.pushState({ key: _key }, '', url)
        }
    } catch (e) {
        // 达到限制了 则重新指定新的地址
        window.location[replace ? 'replace' : 'assign'](url)
    }
}

// 直接调用 pushState 传入 replace 为 true
export function replaceState (url?: string) {
    pushState(url, true)
}

Структура кода и логика обновления представления в основном аналогичны хеш-режиму, за исключением того, что window.location.replace() будет напрямую назначено window.location.hash() вместо вызова history.pushState() и history. методы замены состояния().

Добавление слушателя popstate для изменения URL-адреса адресной строки браузера в HTML5History выполняется непосредственно в конструкторе:

constructor (router: Router, base: ?string) {
  
  window.addEventListener('popstate', e => {
    const current = this.current
    this.transitionTo(getLocation(this.base), route => {
      if (expectScroll) {
        handleScroll(router, route, current, true)
      }
    })
  })
}

Выше приведены два режима «хэш» и «история», которые реализуются через интерфейс браузера.

Сравнение двух режимов

В общих сценариях спроса режим хеширования аналогичен режиму истории.Согласно введению MDN, вызов history.pushState() имеет следующие преимущества по сравнению с прямым изменением хэша:

  • Новый URL-адрес, установленный pushState, может быть любым URL-адресом того же происхождения, что и текущий URL-адрес, а хэш может изменять только часть после #, поэтому может быть установлен только URL-адрес того же документа, что и текущий.
  • Новый URL-адрес, установленный pushState, может точно совпадать с текущим URL-адресом, который также добавит запись в стек, а новое значение, установленное хэшем, должно отличаться от исходного, чтобы инициировать добавление записи в стек.
  • Pushstate может добавить любой тип записи данных через STOVETOBJECT, а хэш может добавлять только короткие строки
  • pushState может дополнительно установить атрибут title для последующего использования

AbstractHistory

"Абстрактный" режим не включает записи, связанные с адресами браузера. Процесс такой же, как и в "HashHistory". Принцип заключается в моделировании функции стека истории браузера через массив.

// 对于 go 的模拟
    go (n: number) {
        // 新的历史记录位置
        const targetIndex = this.index + n
        // 超出返回了
        if (targetIndex < 0 || targetIndex >= this.stack.length) {
            return
        }
        // 取得新的 route 对象
        // 因为是和浏览器无关的 这里得到的一定是已经访问过的
        const route = this.stack[targetIndex]
        // 所以这里直接调用 confirmTransition 了
        // 而不是调用 transitionTo 还要走一遍 match 逻辑
        this.confirmTransition(route, () => {
            // 更新
            this.index = targetIndex
            this.updateRoute(route)
        })
    }

Проблемы с режимом истории

Режим хеширования изменяет только содержимое хэш-части, а хэш-часть не будет включена в http-запрос (хэш со знаком #):

http://oursite.com/#/user/id //如请求,只会发送http://oursite.com/

Поэтому в хеш-режиме не будет проблем запросить страницу по url

В режиме истории URL-адрес изменяется так же, как и URL-адрес обычного бэкэнда запроса (история без #)

http://oursite.com/user/id

Если такой запрос отправляется на серверную часть, серверная часть не настраивает обработку маршрута получения, соответствующую /user/id, и будет возвращена ошибка 404.

Официально рекомендуемое решение — добавить ресурс-кандидат, который охватывает все ситуации на стороне сервера: если URL-адрес не соответствует ни одному из статических ресурсов, он должен возвращать ту же страницу index.html, от которой зависит ваше приложение. Кроме того, при этом сервер больше не возвращает страницу ошибки 404, поскольку файл index.html возвращается для всех путей. Чтобы этого избежать, переопределите все случаи маршрутизации в приложении Vue, а затем дайте страницу 404. Или, если вы используете Node.js в качестве фона, вы можете использовать маршрут на стороне сервера для сопоставления URL-адреса и возвращать 404, если маршрут не соответствует, тем самым реализуя резервный вариант.