Это 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. Определяет, какой маршрут использует текущий маршрут.mode
2. Создайте соответствующий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
,abstract
,вabstract
это режим маршрутизации, используемый в небраузерной средеАдрес источника.
Эта часть инициализируется ранее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 очень сложен.
Текущие основные методы управления разрешениями на маршрутизацию:
- Получите при входе в систему
token
Сохраните его локально, а затем внешний интерфейс будет нести егоtoken
Затем вызовите интерфейс для получения информации о пользователе, чтобы получить информацию о роли текущего пользователя. - Затем внешний интерфейс вычисляет соответствующую таблицу маршрутизации в соответствии с текущей ролью и соединяет ее с задней частью обычной таблицы маршрутизации.
Весь процесс входа в систему для создания динамической маршрутизации
После понимания того, как управлять динамической маршрутизацией, ниже представлена блок-схема всего процесса.
внешний интерфейс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 (длинный текст из тысячи слов)
Рекомендуемое чтение
Говорить о XSS-атаках в реакции
Написание качественного поддерживаемого кода: обзор комментариев
Карьера
ZooTeam, молодая, увлеченная и творческая команда, связанная с отделом исследований и разработок продукции Zhengcaiyun, базируется в живописном Ханчжоу. В настоящее время в команде более 40 фронтенд-партнеров, средний возраст которых составляет 27 лет, и почти 30% из них — инженеры полного стека, настоящая молодежная штурмовая группа. В состав членов входят «ветераны» солдат из Ali и NetEase, а также первокурсники из Чжэцзянского университета, Университета науки и технологий Китая, Университета Хандянь и других школ. В дополнение к ежедневным деловым связям, команда также проводит технические исследования и фактические боевые действия в области системы материалов, инженерной платформы, строительной платформы, производительности, облачных приложений, анализа и визуализации данных, а также продвигает и внедряет ряд внутренних технологий. Откройте для себя новые горизонты передовых технологических систем.
Если вы хотите измениться, вас забрасывают вещами, и вы надеетесь начать их бросать; если вы хотите измениться, вам сказали, что вам нужно больше идей, но вы не можете сломать игру; если вы хотите изменить , у вас есть возможность добиться этого результата, но вы не нужны; если вы хотите изменить то, чего хотите достичь, вам нужна команда для поддержки, но вам некуда вести людей; если вы хотите изменить установившийся ритм, это будет "5 лет рабочего времени и 3 года стажа работы"; если вы хотите изменить исходный Понимание хорошее, но всегда есть размытие того слоя оконной бумаги.. , Если вы верите в силу веры, верьте, что обычные люди могут достичь необыкновенных вещей, и верьте, что они могут встретить лучшего себя. Если вы хотите участвовать в процессе становления бизнеса и лично способствовать росту фронтенд-команды с глубоким пониманием бизнеса, надежной технической системой, технологиями, создающими ценность, и побочным влиянием, я думаю, что мы должны говорить. В любое время, ожидая, пока вы что-нибудь напишете, отправьте это наZooTeam@cai-inc.com