предисловие
Всем привет, я Линь Сансинь,Используйте самые простые и понятные слова, чтобы рассказать о самых сложных вопросах знаний, я считаю, что все, должно быть, использовали его в проекте Vue.Vue-router
, который выполняет маршрутизацию. Поэтому в этой статье я не буду слишком много объяснятьvue-router
Основное объяснение, я не буду вам объяснятьvue-router
Исходный код, я покажу вамС нуля реализовать vue-routerБар! ! !
Основное использование маршрутизации
Обычно мы часто используем vue-router, в основном каждый проект будет использовать его, потому что Vue — это одностраничное приложение, вы можете переключать компоненты через маршрутизацию, чтобы добиться эффекта переключения страниц. Мы обычно используем это, на самом деле оно разделено на 3 шага.
- 1. Представьте
vue-router
и использоватьVue.use(VueRouter)
- 2. Определите массив маршрутизации и передайте массив в
VueRouter实例
, и выставить экземпляр - 3. будет
VueRouter
Экземпляр вводится в main.js и регистрируется в корневом экземпляре Vue.
// src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import home from '../components/home.vue'
import hello from '../components/hello.vue'
import homeChild1 from '../components/home-child1.vue'
import homeChild2 from '../components/home-child2.vue'
Vue.use(VueRouter) // 第一步
const routes = [
{
path: '/home',
component: home,
children: [
{
path: 'child1',
component: homeChild1
},
{
path: 'child2',
component: homeChild2
}
]
},
{
path: '/hello',
component: hello,
children: [
{
path: 'child1',
component: helloChild1
},
{
path: 'child2',
component: helloChild2
}
]
},
]
export default new VueRouter({
routes // 第二步
})
// src/main.js
import router from './router'
new Vue({
router, // 第三步
render: h => h(App)
}).$mount('#app')
router-view和router-link
Распределение
// src/App.vue
<template>
<div id="app">
<router-link to="/home">home的link</router-link>
<span style="margin: 0 10px">|</span>
<router-link to="/hello">hello的link</router-link>
<router-view></router-view>
</div>
</template>
// src/components/home.vue
<template>
<div style="background: green">
<div>home的内容哦嘿嘿</div>
<router-link to="/home/child1">home儿子1</router-link>
<span style="margin: 0 10px">|</span>
<router-link to="/home/child2">home儿子2</router-link>
<router-view></router-view>
</div>
</template>
// src/components/hello.vue
<template>
<div style="background: orange">
<div>hello的内容哦嘿嘿</div>
<router-link to="/hello/child1">hello儿子1</router-link>
<span style="margin: 0 10px">|</span>
<router-link to="/hello/child2">hello儿子2</router-link>
<router-view></router-view>
</div>
</template>
// src/components/home-child1.vue 另外三个子组件大同小异,区别在于文本以及背景颜色不一样,就不写出来了
<template>
<div style="background: yellow">我是home的1儿子home-child1</div>
</template>
Чего мы можем достичь после трех вышеуказанных шагов?
- 1. Введите соответствующий путь в URL-адресе, и соответствующие компоненты будут отображаться
- 2, доступен в любом используемом компоненте
$router和$router
и использовать для него методы или свойства - 3. Можно использовать
route-link
Компонент для перехода по пути - 4. Можно использовать
router-view
Компоненты для маршрутизации отображения соответствующего контента
Ниже приведена анимация достигнутого эффекта.
Давай сделаем это! ! !
Класс VueRouter
В папке src создайтеmy-router.js
Параметр options класса VueRouter на самом делеnew VueRouter(options)
Когда объект параметра передан, иinstall
является методом и должен сделатьVueRouter类
Имея этот метод, почему? Мы поговорим об этом ниже.
// src/my-router.js
class VueRouter {
constructor(options) {}
init(app) {}
}
VueRouter.install = (Vue) => {}
export default VueRouter
метод установки
почему необходимо определитьinstall
метод и дайте емуVueRouter
Шерстяная ткань? На самом деле этоVue.use
Метод связан, вы помните, как Vue использует VueRouter?
import VueRouter from 'vue-router'
Vue.use(VueRouter) // 第一步
export default new VueRouter({ // 传入的options
routes // 第二步
})
import router from './router'
new Vue({
router, // 第三步
render: h => h(App)
}).$mount('#app')
На самом деле, второй и третий шаги очень понятны, то есть создать экземпляр объекта VueRouter и повесить этот объект VueRouter на приложение корневого компонента, тогда возникает вопрос, каково использование Vue.use (VueRouter) в первый шаг? фактическиVue.use(XXX)
, это выполнитьXXX
Вверхinstall
метод, то естьVue.use(VueRouter) === VueRouter.install(), но на данный момент мы знаемinstall
Это работает, но я до сих пор не знаюinstall
Что он делает и какая польза?
Мы знаем, что объект VueRouter подключен к приложению корневого компонента, поэтому приложение может напрямую использовать методы объекта VueRouter, но мы знаем, что определенно хотим每一个用到的组件
Можно использовать методы VueRouter, такие какthis.$router.push
, но теперь только приложение может использовать эти методы, что мне делать? Как можно использовать каждый компонент? В настоящее времяinstall
Метод пригодится, сначала обсудим идею реализации, а потом напишем код.
Очки знаний:Vue.use(XXX)
, будет выполнен метод установки XXX, иVue
в виде参数
входящийinstall
метод
// src/my-router.js
let _Vue
VueRouter.install = (Vue) => {
_Vue = Vue
// 使用Vue.mixin混入每一个组件
Vue.mixin({
// 在每一个组件的beforeCreate生命周期去执行
beforeCreate() {
if (this.$options.router) { // 如果是根组件
// this 是 根组件本身
this._routerRoot = this
// this.$options.router就是挂在根组件上的VueRouter实例
this.$router = this.$options.router
// 执行VueRouter实例上的init方法,初始化
this.$router.init(this)
} else {
// 非根组件,也要把父组件的_routerRoot保存到自身身上
this._routerRoot = this.$parent && this.$parent._routerRoot
// 子组件也要挂上$router
this.$router = this._routerRoot.$router
}
}
})
}
метод createRouteMap
Для чего этот метод? Как следует из названия, он пройдет вroutes数组
превратиться в одногоMap结构
структура данных, ключ-это путь, а значение-это соответствующая информация о компоненте.Что касается того, почему вам нужно его преобразовать? Об этом мы поговорим ниже. Давайте сначала реализуем преобразование.
// src/my-router.js
function createRouteMap(routes) {
const pathList = []
const pathMap = {}
// 对传进来的routes数组进行遍历处理
routes.forEach(route => {
addRouteRecord(route, pathList, pathMap)
})
console.log(pathList)
// ["/home", "/home/child1", "/home/child2", "/hello", "/hello/child1"]
console.log(pathMap)
// {
// /hello: {path: xxx, component: xxx, parent: xxx },
// /hello/child1: {path: xxx, component: xxx, parent: xxx },
// /hello/child2: {path: xxx, component: xxx, parent: xxx },
// /home: {path: xxx, component: xxx, parent: xxx },
// /home/child1: {path: xxx, component: xxx, parent: xxx }
// }
// 将pathList与pathMap返回
return {
pathList,
pathMap
}
}
function addRouteRecord(route, pathList, pathMap, parent) {
const path = parent ? `${parent.path}/${route.path}` : route.path
const { component, children = null } = route
const record = {
path,
component,
parent
}
if (!pathMap[path]) {
pathList.push(path)
pathMap[path] = record
}
if (children) {
// 如果有children,则递归执行addRouteRecord
children.forEach(child => addRouteRecord(child, pathList, pathMap, record))
}
}
export default createRouteMap
режим маршрутизации
Существует три режима маршрутизации
- 1,
hash模式
, наиболее часто используемый режим - 2,
history模式
, режим, требующий внутреннего сотрудничества - 3.
abstract模式
, режим для небраузерных сред
И как установить режим? Устанавливается так, через опцииmode
поле прошло в
export default new VueRouter({
mode: 'hash' // 设置模式
routes
})
А если не пройдено, то по умолчаниюhash模式
, который также является наиболее часто используемым режимом в нашей разработке, поэтому в этой главе реализуется толькоhash模式
// src/my-router.js
import HashHistory from "./hashHistory"
class VueRouter {
constructor(options) {
this.options = options
// 如果不传mode,默认为hash
this.mode = options.mode || 'hash'
// 判断模式是哪种
switch (this.mode) {
case 'hash':
this.history = new HashHistory(this)
break
case 'history':
// this.history = new HTML5History(this, options.base)
break
case 'abstract':
}
}
init(app) { }
}
HashHistory
Создал в папке srchashHistory.js
По сути, принцип работы хеш-режима заключается в том, чтобы следить за изменением значения хеша в url браузера и переключать соответствующий компонент
class HashHistory {
constructor(router) {
// 将传进来的VueRouter实例保存
this.router = router
// 如果url没有 # ,自动填充 /#/
ensureSlash()
// 监听hash变化
this.setupHashLister()
}
// 监听hash的变化
setupHashLister() {
window.addEventListener('hashchange', () => {
// 传入当前url的hash,并触发跳转
this.transitionTo(window.location.hash.slice(1))
})
}
// 跳转路由时触发的函数
transitionTo(location) {
console.log(location) // 每次hash变化都会触发,可以自己在浏览器修改试试
// 比如 http://localhost:8080/#/home/child1 最新hash就是 /home/child1
}
}
// 如果浏览器url上没有#,则自动补充/#/
function ensureSlash() {
if (window.location.hash) {
return
}
window.location.hash = '/'
}
// 这个先不讲,后面会用到
function createRoute(record, location) {
const res = []
if (record) {
while (record) {
res.unshift(record)
record = record.parent
}
}
return {
...location,
matched: res
}
}
export default HashHistory
метод createMmatcher
Как упоминалось выше, при каждом изменении хэша можно получить последнее значение хеш-функции, но это не является нашей конечной целью. Наша конечная цель — отображать разные страницы компонентов в соответствии с изменениями хэша. Что нам делать?
помни раньшеcreateRouteMap
метод? мы будемroutes数组
превратился вMap
Структура данных, с этой картой мы можем получить соответствующий компонент и отобразить его в соответствии с хеш-значением.
Но действительно ли это возможно? На самом деле это не работает.Если следовать вышеуказанному методу, когда хэш/home/child1
, только рендеритhome-child1.vue
Это компонент, но это точно невозможно, когда хеш/home/child1
, это определенно рендерингhome.vue
иhome-child1.vue
эти два компонента
Итак, мы должны написать метод, чтобы найти, каким компонентам соответствует хеш.createMmatcher
// src/my-router.js
class VueRouter {
// ....原先代码
// 根据hash变化获取对应的所有组件
createMathcer(location) {
// 获取 pathMap
const { pathMap } = createRouteMap(this.options.routes)
const record = pathMap[location]
const local = {
path: location
}
if (record) {
return createRoute(record, local)
}
return createRoute(null, local)
}
}
// ...原先代码
function createRoute(record, location) {
const res = []
if (record) {
while (record) {
res.unshift(record)
record = record.parent
}
}
return {
...location,
matched: res
}
}
// src/hashHistory.js
class HashHistory {
// ...原先代码
// 跳转路由时触发的函数
transitionTo(location) {
console.log(location)
// 找出所有对应组件,router是VueRouter实例,createMathcer在其身上
let route = this.router.createMathcer(location)
console.log(route)
}
}
Это просто гарантированоhash变化
Когда мы можем найти все соответствующие компоненты, но одну вещь мы проигнорировали, то есть, если мы вручную обновим страницу, она не сработаетhashchange
событие, то есть компонент не может быть найден, так что мне делать? Обновление страницы определенно приведет к повторной инициализации маршрута, нам просто нужно初始化函数init
Просто выполните прыжок на месте в начале.
// src/my-router.js
class VueRouter {
// ...原先代码
init(app) {
// 初始化时执行一次,保证刷新能渲染
this.history.transitionTo(window.location.hash.slice(1))
}
// ...原先代码
}
Отзывчивые изменения хэша
Выше мы достиглиhash值
Находим все компоненты, которые нужно отрендерить, но финальная ссылка на отрисовку еще не реализована, но не торопимся, перед отрисовкой давайте сначала выполним одну вещь, то есть пустьhash值改变
эта вещь становится响应式的事
,Зачем? Мы просто получаем последнюю информацию каждый раз, когда меняется хеш.组件合集
, но это бесполезно, повторный рендеринг компонента Vue может быть вызван только ответным изменением некоторых данных. Итак, мы должны создать переменную для хранения этого组件合集
, и эта переменная должна быть отзывчивой, эта переменная$route
, обратите внимание, чтобы следовать$router
Дифференцировать О! ! ! но это$route
Для получения необходимо использовать две промежуточные переменные, а именноcurrent和_route
Здесь может быть немного запутанно, и я надеюсь, что у всех есть немного терпения. Я показал сложный код в его простейшей форме.
// src/hashHistory.js
class HashHistory {
constructor(router) {
// ...原先代码
// 一开始给current赋值初始值
this.current = createRoute(null, {
path: '/'
})
}
// ...原先代码
// 跳转路由时触发的函数
transitionTo(location) {
// ...原先代码
// hash更新时给current赋真实值
this.current = route
}
// 监听回调
listen(cb) {
this.cb = cb
}
}
// src/my-router.js
class VueRouter {
// ...原先代码
init(app) {
// 把回调传进去,确保每次current更改都能顺便更改_route触发响应式
this.history.listen((route) => app._route = route)
// 初始化时执行一次,保证刷新能渲染
this.history.transitionTo(window.location.hash.slice(1))
}
// ...原先代码
}
VueRouter.install = (Vue) => {
_Vue = Vue
// 使用Vue.mixin混入每一个组件
Vue.mixin({
// 在每一个组件的beforeCreate生命周期去执行
beforeCreate() {
if (this.$options.router) { // 如果是根组件
// ...原先代码
// 相当于存在_routerRoot上,并且调用Vue的defineReactive方法进行响应式处理
Vue.util.defineReactive(this, '_route', this.$router.history.current)
} else {
// ...原先代码
}
}
})
// 访问$route相当于访问_route
Object.defineProperty(Vue.prototype, '$route', {
get() {
return this._routerRoot._route
}
})
}
рендеринг компонента router-view
На самом деле ключом к рендерингу компонентов является<router-view>
компонент, мы можем реализовать его самостоятельно<my-view>
существуетsrc
Создать подview.js
, старые правила, сначала рассказывай об идеях, а потом реализуй код
// src/view.js
const myView = {
functional: true,
render(h, { parent, data }) {
const { matched } = parent.$route
data.routerView = true // 标识此组件为router-view
let depth = 0 // 深度索引
while(parent) {
// 如果有父组件且父组件为router-view 说明索引需要加1
if (parent.$vnode && parent.$vnode.data.routerView) {
depth++
}
parent = parent.$parent
}
const record = matched[depth]
if (!record) {
return h()
}
const component = record.component
// 使用render的h函数进行渲染组件
return h(component, data)
}
}
export default myView
переход по маршрутизатору
На самом деле его сущность — это всего лишь ярлык.
существуетsrc
Создать подlink.js
const myLink = {
props: {
to: {
type: String,
required: true,
},
},
// 渲染
render(h) {
// 使用render的h函数渲染
return h(
// 标签名
'a',
// 标签属性
{
domProps: {
href: '#' + this.to,
},
},
// 插槽内容
[this.$slots.default]
)
},
}
export default myLink
окончательный эффект
Наконец, измените введение в router/index.js.
import VueRouter from '../Router-source/index2'
затем поместите всеrouter-view和router-link
заменить все наmy-view和my-link
Эффект
Эпилог
Если вы считаете, что эта статья вам немного поможет, поставьте лайк и поддержите Линь Сансиня, ха-ха. Или вы можете присоединиться к моей рыбацкой группе Если вы хотите присоединиться к учебной группе и прикоснуться к группе рыб, нажмите здесь.рыбы, я буду регулярно транслировать пробные интервью, чтобы ответить на вопросы
полный код
/src/my-router.js
import HashHistory from "./hashHistory"
class VueRouter {
constructor(options) {
this.options = options
// 如果不传mode,默认为hash
this.mode = options.mode || 'hash'
// 判断模式是哪种
switch (this.mode) {
case 'hash':
this.history = new HashHistory(this)
break
case 'history':
// this.history = new HTML5History(this, options.base)
break
case 'abstract':
}
}
init(app) {
this.history.listen((route) => app._route = route)
// 初始化时执行一次,保证刷新能渲染
this.history.transitionTo(window.location.hash.slice(1))
}
// 根据hash变化获取对应的所有组件
createMathcer(location) {
const { pathMap } = createRouteMap(this.options.routes)
const record = pathMap[location]
const local = {
path: location
}
if (record) {
return createRoute(record, local)
}
return createRoute(null, local)
}
}
let _Vue
VueRouter.install = (Vue) => {
_Vue = Vue
// 使用Vue.mixin混入每一个组件
Vue.mixin({
// 在每一个组件的beforeCreate生命周期去执行
beforeCreate() {
if (this.$options.router) { // 如果是根组件
// this 是 根组件本身
this._routerRoot = this
// this.$options.router就是挂在根组件上的VueRouter实例
this.$router = this.$options.router
// 执行VueRouter实例上的init方法,初始化
this.$router.init(this)
// 相当于存在_routerRoot上,并且调用Vue的defineReactive方法进行响应式处理
Vue.util.defineReactive(this, '_route', this.$router.history.current)
} else {
// 非根组件,也要把父组件的_routerRoot保存到自身身上
this._routerRoot = this.$parent && this.$parent._routerRoot
// 子组件也要挂上$router
this.$router = this._routerRoot.$router
}
}
})
Object.defineProperty(Vue.prototype, '$route', {
get() {
return this._routerRoot._route
}
})
}
function createRouteMap(routes) {
const pathList = []
const pathMap = {}
// 对传进来的routes数组进行遍历处理
routes.forEach(route => {
addRouteRecord(route, pathList, pathMap)
})
console.log(pathList)
// ["/home", "/home/child1", "/home/child2", "/hello", "/hello/child1"]
console.log(pathMap)
// {
// /hello: {path: xxx, component: xxx, parent: xxx },
// /hello/child1: {path: xxx, component: xxx, parent: xxx },
// /hello/child2: {path: xxx, component: xxx, parent: xxx },
// /home: {path: xxx, component: xxx, parent: xxx },
// /home/child1: {path: xxx, component: xxx, parent: xxx }
// }
// 将pathList与pathMap返回
return {
pathList,
pathMap
}
}
function addRouteRecord(route, pathList, pathMap, parent) {
// 拼接path
const path = parent ? `${parent.path}/${route.path}` : route.path
const { component, children = null } = route
const record = {
path,
component,
parent
}
if (!pathMap[path]) {
pathList.push(path)
pathMap[path] = record
}
if (children) {
// 如果有children,则递归执行addRouteRecord
children.forEach(child => addRouteRecord(child, pathList, pathMap, record))
}
}
function createRoute(record, location) {
const res = []
if (record) {
while (record) {
res.unshift(record)
record = record.parent
}
}
return {
...location,
matched: res
}
}
export default VueRouter
src/hashHistory.js
class HashHistory {
constructor(router) {
// 将传进来的VueRouter实例保存
this.router = router
// 一开始给current赋值初始值
this.current = createRoute(null, {
path: '/'
})
// 如果url没有 # ,自动填充 /#/
ensureSlash()
// 监听hash变化
this.setupHashLister()
}
// 监听hash的变化
setupHashLister() {
window.addEventListener('hashchange', () => {
// 传入当前url的hash
this.transitionTo(window.location.hash.slice(1))
})
}
// 跳转路由时触发的函数
transitionTo(location) {
console.log(location)
// 找出所有对应组件
let route = this.router.createMathcer(location)
console.log(route)
// hash更新时给current赋真实值
this.current = route
// 同时更新_route
this.cb && this.cb(route)
}
// 监听回调
listen(cb) {
this.cb = cb
}
}
// 如果浏览器url上没有#,则自动补充/#/
function ensureSlash() {
if (window.location.hash) {
return
}
window.location.hash = '/'
}
export function createRoute(record, location) {
const res = []
if (record) {
while (record) {
res.unshift(record)
record = record.parent
}
}
return {
...location,
matched: res
}
}
export default HashHistory
src/view.js
const myView = {
functional: true,
render(h, { parent, data }) {
const { matched } = parent.$route
data.routerView = true // 标识此组件为router-view
let depth = 0 // 深度索引
while(parent) {
// 如果有父组件且父组件为router-view 说明索引需要加1
if (parent.$vnode && parent.$vnode.data.routerView) {
depth++
}
parent = parent.$parent
}
const record = matched[depth]
if (!record) {
return h()
}
const component = record.component
// 使用render的h函数进行渲染组件
return h(component, data)
}
}
export default myView
src/link.js
const myLink = {
props: {
to: {
type: String,
required: true,
},
},
// 渲染
render(h) {
// 使用render的h函数渲染
return h(
// 标签名
'a',
// 标签属性
{
domProps: {
href: '#' + this.to,
},
},
// 插槽内容
[this.$slots.default]
)
},
}
export default myLink