Анализ исходного кода vue-router и назначение разрешений динамической маршрутизации

внешний интерфейс
Анализ исходного кода vue-router и назначение разрешений динамической маршрутизации

Это 72-я оригинальная статья без воды.Если вы хотите получить больше оригинальных статей, выполните поиск в официальном аккаунте и подпишитесь на нас~ Эта статья была впервые опубликована в блоге Zhengcaiyun:Анализ исходного кода vue-router и назначение разрешений динамической маршрутизации

задний план

Я установил флаг в прошлом месяце, прочитайте егоvue-routerИсходный код , можно будет найти постепенно позжеvue-routerИсходный код не так прост для понимания, как многие сводные статьи, прочитав его, вы обнаружите, что во многих местах есть много слоев отношений вызова функций, и много этого, указывающего на проблемы, и много вспомогательного. функции, которые необходимо понять. Но я все же настоял на том, чтобы проглотить ее (конечно, я ее еще не читал, содержания действительно много), ниже приведены некоторые выводы и итоги чтения исходного кода в свободное время в Чжэн Цайюнь (стажировка), и проанализировано использование третьего года.vue-element-adminПринцип управления разрешениями динамической маршрутизации этой всеведущей фоновой структуры vuer. Кстати, практический демо-адрес этой статьи прилагается: Разработано на основе фонового фреймворкасистема управления студентами.

Анализ исходного кода Vue-маршрутизатора

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

Предварительные требования: синтаксис потока

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

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

Сначала установите среду потока и инициализируйте среду.

npm install flow-bin -g
flow init

существуетindex.jsВведите этот код ошибки в

/*@flow*/
function add(x: string, y: number): number {
  return x + y
}
add(2, 11)

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

Конкретное использование также должно относиться кофициальный сайт потока, кроме того, этот синтаксис что-то вродеTypeScriptиз.

регистр

мы обычно используемvue-routerобычно требуется, когдаmain.jsинициализирован вVueэкземпляр будетvue-routerОбъект экземпляра передается в качестве параметра

Например:

import Router from 'vue-router'
Vue.use(Router)
const routes = [
   {
    path: '/student',
    name: 'student',
    component: Layout,
    meta: { title: '学生信息查询', icon: 'documentation', roles: ['student'] },
    children: [
      {
        path: 'info',
        component: () => import('@/views/student/info'),
        name: 'studentInfo',
        meta: { title: '信息查询', icon: 'form' }
      },
      {
        path: 'score',
        component: () => import('@/views/student/score'),
        name: 'studentScore',
        meta: { title: '成绩查询', icon: 'score' }
      }
    ]
  }
  ...
];
const router = new Router({
  mode: "history",
  linkActiveClass: "active",
  base: process.env.BASE_URL,
  routes
});
new Vue({
    router,
    store,
    render: h => h(App)
}).$mount("#app");

Vue.use

ТакVue.use(Router)что вы делаете

проблема находится вVueв исходном кодеsrc/core/global-api/use.js Адрес источника

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    // 拿到 installPlugins 
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    // 保证不会重复注册
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }
    // 获取第一个参数 plugins 以外的参数
    const args = toArray(arguments, 1)
    // 将 Vue 实例添加到参数
    args.unshift(this)
    // 执行 plugin 的 install 方法 每个 insatll 方法的第一个参数都会变成 Vue,不需要额外引入
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    // 最后用 installPlugins 保存 
    installedPlugins.push(plugin)
    return this
  }
}

можно увидетьVueизuseметод будет приниматьpluginПараметры, затем используютinstallPluginsМассив содержит зарегистрированныеplugin. Гарантия перваяpluginне проходить двойную регистрацию, тоVueУбери его из параметров функции, поставь весьVueтак какpluginизinstallПервый параметр метода, преимущество которого в том, что нет необходимости вводить хлопотные дополнительныеVue, прост в эксплуатации. Тогда судитеpluginСуществует ли наinstallметод. Если он существует, назначенные параметры передаются в выполнение, и, наконец, все существующиеinstallметодpluginсдаватьinstallPluginsподдерживать.

install

ясно пониматьVue.useПосле структуры мы можем получитьVueРегистрация плагина фактически выполняет плагинinstallметод, первый параметрVue, поэтому мы находим код дляvue-routerв исходном кодеsrc/install.js Адрес источника

// 保存 Vue 的局部变量
export let _Vue
export function install (Vue) {
  // 如果已安装
  if (install.installed && _Vue === Vue) return
  install.installed = true
 // 局部变量保留传入的 Vue
  _Vue = Vue
  const isDef = v => v !== undefined
  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)) {
        // new Vue 时传入的根组件 router router对象传入时就可以拿到 this.$options.router
        // 根 router
        this._routerRoot = this
        this._router = this.$options.router
        this._router.init(this)
        // 变成响应式
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      } else {
        // 非根组件访问根组件通过$parent
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      registerInstance(this, this)
    },
    destroyed () {
      registerInstance(this)
    }
  })
  // 原型加入 $router 和 $route
  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })
  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this._routerRoot._route }
  })
// 全局注册
  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)
// 获取合并策略
  const strats = Vue.config.optionMergeStrategies
  // use the same hook merging strategy for route hooks
  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}

Вы можете видеть, что основная часть этого кода выполняетсяinstallметод использованияmixinспособ смешивания каждого компонентаbeforeCreate,destroyedЭти два крючка жизненного цикла. существуетbeforeCreateФункция будет оценивать текущий входящийrouterЯвляется ли экземпляр корневым компонентом, если да, то_routerRootНазначается текущему экземпляру компонента,_routerназначить как переданныйVueRouterобъект экземпляра, затем выполнитеinitинициализация методаrouter,Потомthis_routeОтзывчивый. Для некорневых компонентов_routerRootнаправление$parentродительский экземпляр. затем выполнитьregisterInstance(this,this)метод, метод будет следовать, а затем будет добавлен прототип$routerа также$route, последняя регистрацияRouterViewа такжеRouterLink, это всеinstallпроцесс.

резюме

Vue.use(plugin)Собственно выполнение плагина наinstallметод,insatllМетод имеет важный шаг:

  • использоватьmixinмиксин в компонентеbeforeCreate , destoryЭти два крючка жизненного цикла
  • существуетbeforeCreateЭтот хук инициализирован.
  • Глобальная регистрацияrouter-view,router-linkкомпоненты

VueRouter

Тогда это самое главноеclass : VueRouter. Эта часть кода больше, поэтому я не буду перечислять их по очереди, а сосредоточусь на анализе.исходный адрес vueRouter.

Конструктор

  constructor (options: RouterOptions = {}) {
    this.app  = null
    this.apps = []
    // 传入的配置项
    this.options = options
    this.beforeHooks = []
    this.resolveHooks = []
    this.afterHooks = []
    this.matcher = createMatcher(options.routes || [], this)
    // 一般分两种模式 hash 和 history 路由 第三种是抽象模式
    let mode = options.mode || 'hash'
    // 判断当前传入的配置是否能使用 history 模式
    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
    // 降级处理
    if (this.fallback) {
      mode = 'hash'
    }
    if (!inBrowser) {
      mode = 'abstract'
    }
    this.mode = mode
    // 根据模式实例化不同的 history,history 对象会对路由进行管理 继承于history class
    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}`)
        }
    }
  }

Первый в инициализацииvueRouterМногие переменные определяются, когда весь объект,appпредставлятьVueпример,optionsПредставляет входящие параметры конфигурации, а затем используется для перехвата маршрута.hooksи важноmatcher(будет написано позже). Конструктор на самом деле делает две вещи: 1. Определяет, какой маршрут использует текущий маршрут.mode2. Создайте соответствующийhistoryобъект.

init

Затем завершите создание экземпляраvueRouterПосле этого, если этот экземпляр будет передан, то есть будет сказано в началеvueRouterэкземпляр инициализируетсяVueпередается, он будет выполнятьсяbeforeCreateвыполнить, когдаinitметод

init (app: any) {
  ...
  this.apps.push(app)
  // 确保后面的逻辑只走一次
  if (this.app) {
    return
  }
  // 保存 Vue 实例
  this.app = app
  const history = this.history
  // 拿到 history 实例之后,调用 transitionTo 进行路由过渡
  if (history instanceof HTML5History) {
    history.transitionTo(history.getCurrentLocation())
  } else if (history instanceof HashHistory) {
    const setupHashListener = () => {
      history.setupListeners()
    }
    history.transitionTo(
      history.getCurrentLocation(),
      setupHashListener,
      setupHashListener
    )
  }
}

initметод передан вVueэкземпляр, сохранить вthis.appsсреди.Vue实例удалит текущийthis.history, если это хэш-маршрут, идем первымsetupHashListenerфункцию, а затем вызвать ключевую функциюtransitionToМаршрутный переход, эта функция фактически вызываетthis.matcher.matchсоответствовать.

резюме

первый вvueRouterПосле выполнения конструктора выбор режима трассировки будет завершен, и сгенерированныйmatcher, а затем необходимо передать маршрут инициализации вvueRouterЭкземплярный объект, выполняемый на этапе инициализации компонентаbeforeCreateкрючок, звонокinitметод, затем получитеthis.historyзвонитьtransitionToВыполнение переходов маршрутизации.

Matcher

доvueRouterинициализируется в конструктореmacther, в этом разделе будет подробно проанализировано, что делает следующий код, иmatchчто делает методАдрес источника.

 this.matcher = createMatcher(options.routes || [], this)

Сначала найдите код вcreate-matcher.js

export function createMatcher (
  routes: Array<RouteConfig>,
  router: VueRouter
): Matcher {
  // 创建映射表
  const { pathList, pathMap, nameMap } = createRouteMap(routes)
  // 添加动态路由
  function addRoutes(routes){...}
  // 计算新路径
  function match (
    raw: RawLocation,
    currentRoute?: Route,
    redirectedFrom?: Location
  ): Route {...}
  // ... 后面的一些方法暂不展开
  
   return {
    match,
    addRoutes
  }
}

createMatcherПринимает два параметра, которыеroutes, это то, что мы обычноrouter.jsОпределите конфигурацию таблицы маршрутизации, а затем параметр, которыйrouterонnew vueRouterВозвращенный экземпляр.

createRouteMap

Следующий код создаетpath-record,name-record, мы находим код дляcreate-route-map.js Адрес источника

export function createRouteMap (
  routes: Array<RouteConfig>,
  oldPathList?: Array<string>,
  oldPathMap?: Dictionary<RouteRecord>,
  oldNameMap?: Dictionary<RouteRecord>
): {
  pathList: Array<string>,
  pathMap: Dictionary<RouteRecord>,
  nameMap: Dictionary<RouteRecord>
} {
  // 记录所有的 path
  const pathList: Array<string> = oldPathList || []
  // 记录 path-RouteRecord 的 Map
  const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
   // 记录 name-RouteRecord 的 Map
  const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)
  // 遍历所有的 route 生成对应映射表
  routes.forEach(route => {
    addRouteRecord(pathList, pathMap, nameMap, route)
  })
  // 调整优先级
  for (let i = 0, l = pathList.length; i < l; i++) {
    if (pathList[i] === '*') {
      pathList.push(pathList.splice(i, 1)[0])
      l--
      i--
    }
  }
  return {
    pathList,
    pathMap,
    nameMap
  }
}

createRouteMapНеобходимо передать конфигурацию маршрутизации, поддерживать передачу старого массива путей и старыхMapЭтот шаг предназначен для следующей рекурсии иaddRoutesбудь готов. Первая запись с тремя переменнымиpath,pathMap,nameMap, тогда посмотримaddRouteRecordэтот основной метод. Этот фрагмент кода слишком велик, перечислите несколько важных шагов.

// 解析路径
const pathToRegexpOptions: PathToRegexpOptions =
    route.pathToRegexpOptions || {}
// 拼接路径
const normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict)
// 记录路由信息的关键对象,后续会依此建立映射表
const record: RouteRecord = {
  path: normalizedPath,
  regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
  // route 对应的组件
  components: route.components || { default: route.component },
  // 组件实例
  instances: {},
  name,
  parent,
  matchAs,
  redirect: route.redirect,
  beforeEnter: route.beforeEnter,
  meta: route.meta || {},
  props: route.props == null
    ? {}
    : route.components
      ? route.props
      : { default: route.props }
}

использоватьrecodОбъект Запись конфигурации маршрутизации полезна для расчета нового пути при переключении последующего пути.pathФактически, передавая родительскийrecordобъектpathи текущийpathраспараллелен. потомregexИспользование библиотеки будетpathРазбирать как регулярное выражение. еслиrouteРекурсивный вызов, если есть дочерние узлыaddRouteRecord

 // 如果有 children 递归调用 addRouteRecord
    route.children.forEach(child => {
      const childMatchAs = matchAs
        ? cleanPath(`${matchAs}/${child.path}`)
        : undefined
      addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
    })

Наконец, сопоставьте две таблицы и установитеrecord·pathсохранить вpathList,nameMapЛогическое сходство не указано

  if (!pathMap[record.path]) {
    pathList.push(record.path)
    pathMap[record.path] = record
  }

Бросил такого сильного генералаpathListа такжеpathMapа такжеnameMapПочему его вынесли? первыйpathListКонфигурация маршрутизации записи полностьюpath,ПотомpathMapа такжеnameMapНам удобно проходить вpathилиnameбыстро найтиrecord, а затем помогите последующему переключению пути рассчитать маршрут.

addRoutes

Это вvue2.2.0недавно добавленный послеapi, Во многих случаях маршрутизация не является жестко закодированной, и маршрутизацию необходимо добавлять динамически. с предыдущимcreateRouteMapНа основании нам нужно только пройти вroutesВсе, он может модифицировать его на исходной основе

function addRoutes (routes) {
  createRouteMap(routes, pathList, pathMap, nameMap)
}

и увидеть вcreateMathcerНаконец, этот метод возвращается, поэтому мы можем использовать этот метод

return {
    match,
    addRoutes
  }

match

function match (
  raw: RawLocation,
  currentRoute?: Route,
  redirectedFrom?: Location
): Route {
  ...
}

Далееmatchметод, который принимает 3 параметра, гдеrawдаRawLocationтипа, это может бытьurlстрока, также может бытьLocationобъект;currentRouteдаRouteтип, представляющий текущий путь;redirectedFromсвязанные с переадресацией.matchМетод возвращает путь, и его роль основана на входящемrawи текущий путьcurrentRouteРассчитать новый путь и вернуться. Что касается того, как он вычислил этот путь, то вы можете подробно посмотреть, как вычислитьlocationизnormalizeLocationМетоды и_createRouteметод.

резюме

  • createMatcher: Создайте таблицу сопоставления в соответствии с описанием конфигурации маршрута, включая пути, имена к маршрутам.recordКартографические отношения, самое главноеcreateRouteMapЭтот метод, здесь также принцип динамического сопоставления маршрутов и вложенной маршрутизации.
  • addRoutes: динамически добавлять конфигурацию маршрутизации
  • match: по поступающимrawи текущий путьcurrentRouteРассчитать новый путь и вернуться.

режим маршрутизации

vue-routerПоддерживаются три режима маршрутизации:hash,history,abstractabstractэто режим маршрутизации, используемый в небраузерной средеАдрес источника.

Эта часть инициализируется ранееvueRouterКак указано в объекте, сначала получите режим элемента конфигурации, а затем оцените, поддерживает ли текущий браузер этот режим в соответствии с текущей входящей конфигурацией, по умолчаниюie9Следующее будет понижено доhash. Затем по разным режимам инициализировать разныеhistoryпример.

    // 一般分两种模式 hash 和 history 路由 第三种是抽象模式不常用
    let mode = options.mode || 'hash'
    // 判断当前传入的配置是否能使用 history 模式
    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
    // 降级处理
    if (this.fallback) {
      mode = 'hash'
    }
    if (!inBrowser) {
      mode = 'abstract'
    }
    this.mode = mode
    // 根据模式实例化不同的 history history 对象会对路由进行管理 继承于 history class
    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}`)
        }
    }

резюме

vue-routerПоддержка трех режимов маршрутизации,hash,historyа такжеabstract. По умолчаниюhash, если текущий браузер не поддерживаетhistoryвыполнит процесс понижения версии, а затем завершитhistoryинициализация.

коммутатор маршрутизации

Переключение URL в основном называетсяpushметод, следующий берет режим хеширования в качестве примера для анализаpushПринцип реализации метода.pushПринцип реализации маршрутизации с коммутацией методовАдрес источника

первый вsrc/index.jsнайдено подvueRouterОпределенныйpushметод

  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    // $flow-disable-line
    if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
      return new Promise((resolve, reject) => {
        this.history.push(location, resolve, reject)
      })
    } else {
      this.history.push(location, onComplete, onAbort)
    }
  }

Затем нам нужно найтиhistory/hash.js. Здесь сначала получите текущий путь, а затем вызовитеtransitionToСделайте переключение пути и выполните его в функции обратного вызоваpushHashэтот основной метод.

push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const { current: fromRoute } = this
    // 路径切换的回调函数中调用 pushHash
    this.transitionTo(
      location,
      route => {
        pushHash(route.fullPath)
        handleScroll(this.router, route, fromRoute, false)
        onComplete && onComplete(route)
      },
      onAbort
    )
  }

а такжеpushHashМетод вызывается после завершения оценки совместимости браузера.pushStateметод, будетurlвходящий

export function pushState (url?: string, replace?: boolean) {
  const history = window.history
  try {
   // 调用浏览器原生的 history 的 pushState 接口或者 replaceState 接口,pushState 方法会将 url 入栈
    if (replace) {
      history.replaceState({ key: _key }, '', url)
    } else {
      _key = genKey()
      history.pushState({ key: _key }, '', url)
    }
  } catch (e) {
    window.location[replace ? 'replace' : 'assign'](url)
  }
}

Его можно найти,pushНижний уровень вызывает собственный уровень браузера.historyизpushStateа такжеreplaceStateметод, неreplaceШаблон поместит URL-адрес в стек истории.

Также упомянем принцип сращивания хэша

Местоположение источника

инициализацияHashHistory, конструктор выполнитensureSlashСюда

export class HashHistory extends History {
  constructor (router: Router, base: ?string, fallback: boolean) {
    ...
    ensureSlash()
  }
  ...
  }

Этот метод вызывается первымgetHashА затем выполнитьreplaceHash()

function ensureSlash (): boolean {
  const path = getHash()
  if (path.charAt(0) === '/') {
    return true
  }
  replaceHash('/' + path)
  return false
}

Вот методы

export function getHash (): string {
  const href = window.location.href
  const index = href.indexOf('#')
  return index === -1 ? '' : href.slice(index + 1)
}
// 真正拼接哈希的方法 
function getUrl (path) {
  const href = window.location.href
  const i = href.indexOf('#')
  const base = i >= 0 ? href.slice(0, i) : href
  return `${base}#${path}`
}
function replaceHash (path) {
  if (supportsPushState) {
    replaceState(getUrl(path))
  } else {
    window.location.replace(getUrl(path))
  }
}
export function replaceState (url?: string) {
  pushState(url, true)
}

Например: предположим, что текущий URL-адресhttp://localhost:8080,pathпустой, выполнитьreplcaeHash('/' + path), а затем внутренне выполнитьgetUrlРассчитатьurlдляhttp://localhost:8080/#/Окончательное исполнениеpushState(url,true), готово!

резюме

hashмодальныйpushметод вызовет метод переключения путиtransitionTo, а затем вызовите функцию обратного вызоваpushHashметод, который вызываетсяpushStateНижний уровень метода — вызов нативного браузера.historyМетоды.pushа такжеreplaceРазница в том, чтоurlЗапихнул в стек истории, одного нет, самое интуитивное проявление этоreplaceВ этом режиме браузер не вернется к предыдущему маршруту, нажав назад, а другой может.

router-view & router-link

vue-routerсуществуетinstallКогда два компонента зарегистрированы глобально, одинrouter-viewодинrouter-link, оба из которых являются типичными функциональными компонентами.Адрес источника

router-view

первый вrouterвыполнение компонентаbeforeCreateКогда этот крючок, положитьthis._routeПреобразован в реактивный объект

 Vue.util.defineReactive(this, '_route', this._router.history.current)

Таким образом, каждый переключатель маршрута сработаетrouter-viewопять такиrenderЭто рендерит новый вид.

основнойrenderСм. комментарии к коду функции.

  render (_, { props, children, parent, data }) {
    ...
    // 通过 depth 由 router-view 组件向上遍历直到根组件,遇到其他的 router-view 组件则路由深度+1 这里的 depth 最直接的作用就是帮助找到对应的 record
    let depth = 0
    let inactive = false
    while (parent && parent._routerRoot !== parent) {
      // parent.$vnode.data.routerView 为 true 则代表向上寻找的组件也存在嵌套的 router-view 
      if (parent.$vnode && parent.$vnode.data.routerView) {
        depth++
      }
      if (parent._inactive) {
        inactive = true
      }
      parent = parent.$parent
    }
    data.routerViewDepth = depth
    if (inactive) {
      return h(cache[name], data, children)
    }
   // 通过 matched 记录寻找出对应的 RouteRecord 
    const matched = route.matched[depth]
    if (!matched) {
      cache[name] = null
      return h()
    }
 // 通过 RouteRecord 找到 component
    const component = cache[name] = matched.components[name]
   // 往父组件注册 registerRouteInstance 方法
    data.registerRouteInstance = (vm, val) => {     
      const current = matched.instances[name]
      if (
        (val && current !== vm) ||
        (!val && current === vm)
      ) {
        matched.instances[name] = val
      }
    }
  // 渲染组件
    return h(component, data, children)
  }

запустить обновлениеsetterзвоните, находится в г.src/index.js, при изменении_routeвызовет обновление.

history.listen(route => {
  this.apps.forEach((app) => {
    // 触发 setter
    app._route = route
  })
})

router-link

Проанализируйте несколько важных частей:

  • настраиватьactiveстиль маршрутизации

router-linkПочему можно добавитьrouter-link-activeа такжеrouter-link-exact-activeэти двоеclassЧтобы изменить стиль, потому что исполнениеrenderКогда функция основана на текущем состоянии маршрута, для выводаactiveдобавление элементаclass

render (h: Function) {
  ...
  const globalActiveClass = router.options.linkActiveClass
  const globalExactActiveClass = router.options.linkExactActiveClass
  // Support global empty active class
  const activeClassFallback = globalActiveClass == null
    ? 'router-link-active'
    : globalActiveClass
  const exactActiveClassFallback = globalExactActiveClass == null
    ? 'router-link-exact-active'
    : globalExactActiveClass
    ...
}
  • router-linkРендеринг по умолчаниюaярлык, если нет, он поднимется, чтобы найти первыйaЭтикетка
 if (this.tag === 'a') {
      data.on = on
      data.attrs = { href }
    } else {
      // find the first <a> child and apply listener and href
      const a = findAnchor(this.$slots.default)
      if (a) {
        // in case the <a> is a static node
        a.isStatic = false
        const aData = (a.data = extend({}, a.data))
        aData.on = on
        const aAttrs = (a.data.attrs = extend({}, a.data.attrs))
        aAttrs.href = href
      } else {
        // 不存在则渲染本身元素
        data.on = on
      }
    }
  • Переключение маршрутов и запуск соответствующих событий
const handler = e => {
  if (guardEvent(e)) {
    if (this.replace) {
      // replace路由
      router.replace(location)
    } else {
      // push 路由
      router.push(location)
    }
  }
}

Анализ принципа динамической маршрутизации управления разрешениями

Я считаю, что студенты, разработавшие фоновые проекты, часто сталкиваются со следующими сценариями: система разделена на разные роли, а затем разные роли соответствуют разным меню операций и разрешениям на операции. Например: Учителя могут запрашивать свою личную информацию, а затем они также могут запрашивать и управлять информацией учащегося и системой оценок учащегося.Пользователям-учащимся разрешено запрашивать только личные оценки и информацию, и им не разрешается вносить изменения. существуетvue2.2.0раньше не присоединялсяaddRoutesЭтот API очень сложен.

Текущие основные методы управления разрешениями на маршрутизацию:

  1. Получите при входе в системуtokenСохраните его локально, а затем внешний интерфейс будет нести егоtokenЗатем вызовите интерфейс для получения информации о пользователе, чтобы получить информацию о роли текущего пользователя.
  2. Затем внешний интерфейс вычисляет соответствующую таблицу маршрутизации в соответствии с текущей ролью и соединяет ее с задней частью обычной таблицы маршрутизации.

Весь процесс входа в систему для создания динамической маршрутизации

После понимания того, как управлять динамической маршрутизацией, ниже представлена ​​блок-схема всего процесса.

внешний интерфейсbeforeEachСуждение:

  • Токен JWT существует в кеше
    • доступ/login: перенаправить домой/
    • доступ/loginМаршрутизация, отличная от: сначала получить доступ, получить информацию о роли пользователя, затем сгенерировать динамическую маршрутизацию, затем получить доступ кreplaceмодальный доступ/xxxмаршрутизация. В этом режиме пользователи не будут входить в систему после входа в систему.historyвести учет
  • Токен JWT не существует
    • Маршрут в белом: нормальный доступ/xxxмаршрутизация
    • Нет в белом списке: перенаправить на/loginстраница

В сочетании с анализом исходного кода фреймворка

Объединить нижеvue-element-adminИсходный код анализирует, как логика маршрутизации обрабатывается в среде.

Анализ логики доступа к маршруту

Во-первых, вы можете найти и ввести файлmain.jsтот же уровеньpermission.js, глобальная защита маршрутизации находится здесь.Адрес источника

const whiteList = ['/login', '/register'] // 路由白名单,不会重定向
// 全局路由守卫
router.beforeEach(async(to, from, next) => {
  NProgress.start() //路由加载进度条
  // 设置 meta 标题
  document.title = getPageTitle(to.meta.title)
  // 判断 token 是否存在
  const hasToken = getToken()
  if (hasToken) {
    if (to.path === '/login') {
      // 有 token 跳转首页
      next({ path: '/' })
      NProgress.done()
    } else {
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      if (hasRoles) {
        next()
      } else {
        try {
          // 获取动态路由,添加到路由表中
          const { roles } = await store.dispatch('user/getInfo')
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
          router.addRoutes(accessRoutes)
          //  使用 replace 访问路由,不会在 history 中留下记录,登录到 dashbord 时回退空白页面
          next({ ...to, replace: true })
        } catch (error) {
          next('/login')
          NProgress.done()
        }
      }
    }
  } else {
    // 无 token
    // 白名单不用重定向 直接访问
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      // 携带参数为重定向到前往的路径
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

Я добавил комментарии к коду здесь, чтобы всем было легче его понять, резюмируя в одном предложении: маршрутизация доступа/xxx, сначала нужно проверитьtokenСуществует ли он, если есть способ определить, следует ли получить доступ к маршруту, прогулка не регистрируется в маршруте, если пользователь является первым домом доступа, то сгенерируйте динамический маршрут, если прогулка является маршрутом входа, найдите его напрямую, если неtokenПросто зайдите, чтобы проверить, есть ли маршрут в белом списке (маршруты, к которым можно получить доступ в любом случае), и если да, то получите к нему доступ, в противном случае перенаправьте обратно на страницу входа.

Ниже скриншот изменений маршрутизации после прохождения глобалгарда

Создание динамической маршрутизации с помощью Vuex

Ниже приводится анализ этого шага.const accessRoutes = await store.dispatch('permission/generateRoutes', roles)Как генерируется маршрут.Адрес источника

первыйvue-element-adminСуществует два типа маршрутов:

  • ConstantRoutes: маршруты, которые не требуют оценки разрешений.
  • asyncRoutes: маршруты, которым необходимо динамически определять разрешения.
// 无需校验身份路由
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  }
  ...
  ],
 // 需要校验身份路由 
export const asyncRoutes = [
  // 学生角色路由
  {
    path: '/student',
    name: 'student',
    component: Layout,
    meta: { title: '学生信息查询', icon: 'documentation', roles: ['student'] },
    children: [
      {
        path: 'info',
        component: () => import('@/views/student/info'),
        name: 'studentInfo',
        meta: { title: '信息查询', icon: 'form' }
      },
      {
        path: 'score',
        component: () => import('@/views/student/score'),
        name: 'studentScore',
        meta: { title: '成绩查询', icon: 'score' }
      }
    ]
  }]
  ...

Исходный код для создания динамических маршрутов находится по адресуsrc/store/modules/permission.jsсерединаgenerateRoutesметод, исходный код выглядит следующим образом:

 generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      let accessedRoutes
      if (roles.includes('admin')) {
        accessedRoutes = asyncRoutes || []
      } else {
      // 不是 admin 去遍历生成对应的权限路由表
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      // vuex 中保存异步路由和常规路由
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  }

отroute.jsчитатьasyncRoutesа такжеconstantRoutesЗатем сначала определите, является ли текущая рольadmin, если да, то суперадминистратор по умолчанию может получить доступ ко всем роутам, разумеется, здесь тоже можно настроить, в противном случае отфильтруйте таблицу маршрутизации разрешений на маршрутизацию, а затем сохраните ее вVuexсередина. Наконец, отфильтрованноеasyncRoutesа такжеconstantRoutesОбъединить. Исходный код фильтрации маршрутизации разрешений выглядит следующим образом:

export function filterAsyncRoutes(routes, roles) {
  const res = []
  routes.forEach(route => {
    // 浅拷贝
    const tmp = { ...route }
    // 过滤出权限路由
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })
  return res
}

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

function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    // roles 有对应路由元定义的 role 就返回 true
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    return true
  }
}

Далее необходимо оценить ситуацию вторичной маршрутизации, третичной маршрутизации и т. д., а затем выполнить слой итерационной обработки, и, наконец, протолкнуть отфильтрованные маршруты в массив и вернуться. а затем добавить кconstantRoutesПозади

 SET_ROUTES: (state, routes) => {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  }

Весь процесс динамической генерации маршрута

Суммировать

  • vue-routerЧасть анализа исходного кода

    • Регистрация: Выполнитьinstallметод, ввод инициализации хука жизненного цикла
    • vueRouter: когда компонент выполняетсяbeforeCreateвходящийrouterэкземпляр, выполнитьinitфункцию, а затем выполнитьhistory.transitionToпереход маршрута
    • сопоставитель: по входящемуroutesНастроить, чтобы создать соответствующийpathMapа такжеnameMap, новая позиция может быть рассчитана в соответствии с входящей позицией и путем и соответствует соответствующемуrecord
    • режим маршрутизации: режим маршрутизации инициализируетсяvueRouterсоответствует или понижается, если браузер не поддерживает его
    • Переключатель маршрутизации: в хеш-режиме нижний уровень использует собственные настройки браузера.pushStateа такжеreplaceStateметод
    • router-view: вызов хранится в родительском компоненте$route.matchУправляет отрисовкой компонентов, соответствующих маршрутам, и поддерживает вложенность.
    • ссылка на роутер: черезtoДля определения целевого компонента маршрутизации перехода по событию клика и поддержки рендеринга в разныеtag, вы также можете изменить стиль активного маршрута.
  • Раздел динамической маршрутизации управления разрешениями

    • Логика роутинга: глобальный перехват роута, получить токен из кеша, если он есть, если вы заходите в роут первый раз, то нужно получить информацию о пользователе и сгенерировать динамический роут, который здесь нужно обработать/loginВ особых случаях, если он не существует, оцените белый список, а затем следуйте соответствующей логике.
    • Динамически генерируемые маршруты: входящие потребностиrouter.jsОпределены два маршрута. Определить, является ли текущая личность администратором, и склеить его напрямую, в противном случае необходимо отфильтровать маршруты с разрешениями и окончательно склеить их с обратной стороной обычного маршрута.addRoutesДобавить.

После чтения

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

Если вы вслепую запоминаете некоторые так называемые фейсбуки или напрямую запоминаете связанные с ними поведения фреймворков или API, вам будет сложно быстро найти проблему и понять, как ее решить, когда вы столкнетесь с более сложной проблемой, и я нашел много Когда люди сталкиваются с проблемой после использования нового фреймворка, они сразу же упоминают о соответствующейIssues, так что многие популярные фреймворкиIssuesИх более сотни или тысячи, но многие проблемы вызваны ошибками, потому что мы не следовали изначально заданному проектировщиками направлению, и большинство из них вызваны небрежностью.

Справочная статья

Попросите вас всесторонне проанализировать исходный код vue-router (длинный текст из тысячи слов)

Анализ исходного кода Vuejs

Рекомендуемое чтение

Говорить о XSS-атаках в реакции

Написание качественного поддерживаемого кода: обзор комментариев

Карьера

ZooTeam, молодая, увлеченная и творческая команда, связанная с отделом исследований и разработок продукции Zhengcaiyun, базируется в живописном Ханчжоу. В настоящее время в команде более 40 фронтенд-партнеров, средний возраст которых составляет 27 лет, и почти 30% из них — инженеры полного стека, настоящая молодежная штурмовая группа. В состав членов входят «ветераны» солдат из Ali и NetEase, а также первокурсники из Чжэцзянского университета, Университета науки и технологий Китая, Университета Хандянь и других школ. В дополнение к ежедневным деловым связям, команда также проводит технические исследования и фактические боевые действия в области системы материалов, инженерной платформы, строительной платформы, производительности, облачных приложений, анализа и визуализации данных, а также продвигает и внедряет ряд внутренних технологий. Откройте для себя новые горизонты передовых технологических систем.

Если вы хотите измениться, вас забрасывают вещами, и вы надеетесь начать их бросать; если вы хотите измениться, вам сказали, что вам нужно больше идей, но вы не можете сломать игру; если вы хотите изменить , у вас есть возможность добиться этого результата, но вы не нужны; если вы хотите изменить то, чего хотите достичь, вам нужна команда для поддержки, но вам некуда вести людей; если вы хотите изменить установившийся ритм, это будет "5 лет рабочего времени и 3 года стажа работы"; если вы хотите изменить исходный Понимание хорошее, но всегда есть размытие того слоя оконной бумаги.. , Если вы верите в силу веры, верьте, что обычные люди могут достичь необыкновенных вещей, и верьте, что они могут встретить лучшего себя. Если вы хотите участвовать в процессе становления бизнеса и лично способствовать росту фронтенд-команды с глубоким пониманием бизнеса, надежной технической системой, технологиями, создающими ценность, и побочным влиянием, я думаю, что мы должны говорить. В любое время, ожидая, пока вы что-нибудь напишете, отправьте это наZooTeam@cai-inc.com