От использования до самостоятельной реализации простого Vue Router — просто взгляните на это

внешний интерфейс vue-router
От использования до самостоятельной реализации простого Vue Router — просто взгляните на это

предисловие

Это обучение СяоланVue RouterИтоговые заметки не очень хорошо написаны в некоторых местах, дайте, пожалуйста, еще совет

  • Здесь, чтобы вы зналиVue Routerосновное использование
  • почерк базовыйVue RouterПочувствуйте реализацию основы
  • С предыдущей основой, потом написать другую по официальным исходникамVue RouterБар

рукописный в двухVue RouterЯ написал подробные заметки везде

Надеюсь, эта статья будет вам полезна, рекомендуется набирать ее вручную

Наконец: Сяолан ОнлайнНравится

Основы маршрутизатора Vue

Давайте сначала разберемсяVue RouterОн прост в использовании, сначала поймите, как его использовать, а затем выясните, как его реализовать.

1. Введение

Маршрутизация: по сути корреспонденция

классифицируется вВнешняя маршрутизацияа такжеВнутренняя маршрутизация

Внутренняя маршрутизация

Например, маршрутизация node.jsURLАдрес запроса на сервере иресурсСоответственно возвращать разные ресурсы по разным адресам запросов

Внешняя маршрутизация

существуетSPA(Одностраничное приложение) изменения на основе событий, инициированных пользователемURLОтображать различный контент страницы без обновления, например, мы поговорим об этом позжеVue Router

2. Самые основные шаги использования Vue-Router

1. Представьте файл Vue-Router

<!-- 使用vue router前提 vue 必不可少 -->
<script src="./js/vue.js"></script>
<!-- 引入vue-router文件 -->
<script src="./js/vue-router_3.0.2.js"></script>

2. Добавьте на страницу router-link и router-view

<!-- 添加路由 -->
<!-- 会被渲染为 <a href="#/home"></a> -->
<router-link to="/home">Home</router-link>
<router-link to="/login">Login</router-link>
<!-- 展示路由的内容 -->
<router-view></router-view>

3. Создайте компонент маршрутизации

//创建路由组件
const home = {
    template: `
<div>欢迎来到{{name}}</div>
`,
    data() {
        return {
            name: '首页',
        }
    },
}

const login = {
    template: `
		<div>欢迎来到登录页</div>
	`,
}

4. Настройте правила маршрутизации

// 配置路由规则
const router = new VueRouter({
    routes: [
        //每一个路由规则都是一个对象
        //path 路由的 hash地址
        //component 路由的所展示的组件
        {
            path: '/',
            // 当访问 '/'的时候 路由重定向 到新的地址 '/home'
            redirect: '/home',
        },     
        {
            path: '/home',
            component: home,
        },
        {
            path: '/login',
            component: login,
        },
    ],
})

5. Смонтируйте маршрут

 let vm = new Vue({
        el: '#app',
        data: {},
        methods: {},
        // 挂载到vue 上面
        router,
 })

2

3. Вложенная маршрутизация

Вложенная маршрутизация здесь основана на приведенном выше примере и продолжает писать

1. Добавьте ссылки на подмаршрут и заполнители в маршруте

//创建路由组件
const home = {
    template: `
    <div>
    欢迎来到首页
    <br>
    <!-- 子路由链接 -->
    <router-link to="/tab1">Tab1</router-link>
    <router-link to="/tab2">Tab2</router-link>

    <!-- 子路由展示 -->
    <router-view></router-view>
    </div>
}

2. Добавьте компоненты маршрутизации

// 创建两个子路由组件
const tab1 = {
    template: `
    <div>
    子路由1
    </div>
    `,
}
const tab2 = {
    template: `
    <div>
    子路由2
    </div>
    `,
}

3. Настройте правила маршрутизации

// 配置路由规则
const router = new VueRouter({
    routes: [
        {
            path: '/home',
            component: home,
            //children 表示子路由规则
            children: [
                { path: '/tab1', component: tab1 },
                { path: '/tab2', component: tab2 },
            ],
        },
    ],
})

3

4. Динамическая маршрутизация

pathатрибут плюс/:idиспользоватьrouteобъектparams.idПолучить динамические параметры

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

<div id="app">
    <!-- 添加路由 -->
    <!-- 会被渲染为 <a href="#/home"></a> -->
    <router-link to="/goods/1">goods1</router-link>
    <router-link to="/goods/2">goods2</router-link>
    <router-link to="/goods/3">goods3</router-link>
    <router-link to="/goods/4">goods4</router-link>
    <!-- 展示路由的内容 -->
    <router-view></router-view>
</div>

Тогда здесь вы можете использовать динамическую маршрутизацию для решения

<script>
    //创建路由组件
    const goods = {
        // this.$route.parms.id 可以省略 this
        template: `
        <div>欢迎来到商品 {{$route.params.id}}页</div>
        `,
        }
    // 配置路由规则
    const router = new VueRouter({
        routes: [
            {
                // 加上`/:id`
                path: '/goods/:id',
                component: goods,
            },
        ],
    })
    let vm = new Vue({
        el: '#app',
        data: {},
        methods: {},
        // 挂载到vue 上面
        router,
    })
</script>

4

Наконец, вы также можете использоватьqueryВыполнить передачу параметров.

// 比如
<router-link to="/goods?id=1">goods</router-link>

затем используйтеthis.$route.query.idможно получить в компоненте маршрутизацииid

Добавить динамическую маршрутизацию

использоватьthis.$router.addRoutes([])МогуДобавить динамическую маршрутизацию, которому передается массив иroutesтакой же внутри

5. Параметры маршрутизации

Мы можем передавать значения с помощью реквизита

Зачем использовать реквизиты для передачи значений, маршрут не подходит, маршрут недостаточно гибкий

Есть три случая значения реквизита

1. Логический тип

//创建路由组件
const goods = {
    // 使用props接收
    props: ['id'],
    template: `
    <div>欢迎来到商品 {{id}}页</div>
    `,
}
// 配置路由规则
const router = new VueRouter({
    routes: [
        {
            path: '/goods/:id',
            component: goods,
            //props为true, route.params将会被设置为组件属性
            props: true,
        },
    ],
})

2. Тип объекта

Но не могу получить это здесьidда, будетсообщить об ошибке

Идентификатор здесь должен быть$route.params.idПолучать

const goods = {
    // 使用props接收
    props: ['name', 'info', 'id'],
    // 这里的 id 是获取不到的
    template: `
    <div>{{info}}来到{{name}} {{id}}页</div>
    `,
}
// 配置路由规则
const router = new VueRouter({
    routes: [
        {
            path: '/goods/:id',
            component: goods,
            //props为对象 就会把这个对象传递的路由组件
            //路由组件使用props接收
            props: {
                name: '商品',
                info: '欢迎',
            },
        },
    ],
})

3. Функция

const goods = {
    // 使用props接收
    props: ['name', 'info', 'id'],
    template: `
    <div>{{info}}来到{{name}} {{id}}页</div>
    `,
}
// 配置路由规则
const router = new VueRouter({
    routes: [
        {
            path: '/goods/:id',
            component: goods,
            //prop是一个函数的话 就可以组合传值
            props: (route) => {
                return {
                    name: '商品',
                    info: '欢迎',
                    id: route.params.id,
                }
            },
        },
    ],
})

6.маршрут и роутер

упомянутый вышеrouteтогда иrouterкакие различия есть

  • routeдляТекущий объект перехода маршрутизатораможно получить вpath,params,hash,query,fullPath,matched,name
  • routerдляЭкземпляр vuerooter.использоватьnew VueRouterсозданный экземпляр, хотите перейти к другомуURL, затем используйтеrouter.pushметод
  • routesдаrouterЭкземпляры маршрутизации используются для настройки объектов маршрутизации (кстати)

7. Именованные маршруты

компонент маршрутизации

//创建路由组件
const goods = {
    // 使用props接收
    props: ['id'],
    template: `
    <div>商品{{id}}页</div>
    `,
}

конфигурация маршрутизации

//配置路由
const router = new VueRouter({
    routes: [
        {
            path: '/goods/:id',
            // 命名路由
            name: 'goods',
            component: goods,
        },
    ],
})

связывать:toпройти черезnameНайдите определенный маршрут Вы также можете использоватьparamsпередать параметры

<router-link :to="{name: 'goods', params: { id: 1 } }">goods1</router-link>
<!-- 展示路由的内容 -->
<router-view></router-view>

8. Программная навигация

1. Декларативное навигация

Теперь, когда упомянута программная навигация, давайте кратко поговорим о декларативной навигации.

Все показанное выше являетсядекларация навигацияНапримерrouter-link

<router-link to="/goods/1">goods1</router-link>

а такжеaЭтикетка

<a href="#/goods/1">goods1</a>

2. Программная навигация

использоватьjavaScriptконтролироватьпрыжок по маршруту

Использование на обычных веб-страницахloaction.href window.openподожди, чтобы прыгнуть

Теперь то, что я собираюсь представить, этоVue RouterПрограммная навигация в

Мы всегда используемrouter.push()** Router.go (n) ** метод прыжка

//字符串
this.$router.push('/home')

//对象
this.$ruter.push({path:'/home'})

//比如这个 /goods?id=1
this.$router.push({path:'/goods',query:{id:'1'}})

//命名路由 /goods/1
this.$router.push({name:'goods',params:{id:1}})

//后退
this.$router.go(-1)

9. Маршрутная охрана

1. Глобальная гвардия

router.beforeEachGlobal Guard работает на всех маршрутах

router.beforeEach((to, from, next) => { 
      next();//使用时,千万不能漏写next!!!
    }).catch(()=>{
      //跳转失败页面
      next({ path: '/error', replace: true, query: { back: false }}
    )
})

Три параметра глобальной защиты

to: объект целевого маршрута, который собирается войти

from: Текущая навигация покидает объект маршрута.

next: Разные параметры делают разные вещи

  • next()перейти непосредственно к следующему хуку
  • next(false)остановить текущую навигацию
  • следующий('/путь')перенаправить наpathАдрес маршрутизации Конечно, его можно записать и в виде объектаследующий({путь : '/путь'})
  • next(error): если входящий параметрErrorнапример, навигация прекращается и ошибка передается вrouter.onError()

2. Эксклюзивная защита маршрутизации

beforeEnterОхранники, эксклюзивные для объектов маршрутизации, записываются наroutesв

const router = new VueRouter({
  routes: [
    {
      path: '/goods',
      component: goods,
      beforeEnter: (to, from, next) => {
        // 一样的用法
      }
    }
  ]
})

3. Щитки в компонентах (понять)

Guard в компоненте написан внутри компонента. Ниже приводится официальное введение.

  • beforeRouteEnterПеред вводом маршрута компонент не был инстанцирован, поэтому здесь его получить нельзя
  • beforeRouteUpdate (2.2)На данном этапе можно получить это, которое срабатывает, когда роутинг повторно использует один и тот же компонент
  • beforeRouteLeaveНа данном этапе это можно получить, при выходе с соответствующего маршрута компонента его можно использовать для сохранения данных, либо для инициализации данных, либо для закрытия таймера и т.д.
const goods = {
  template: `<div>goods</div>`,
  beforeRouteEnter (to, from, next) {
    // 具体逻辑
  },
  beforeRouteUpdate (to, from, next) {
    // 具体逻辑
  },
  beforeRouteLeave (to, from, next) {
    // 具体逻辑
  }
}

10. Кэширование компонентовkeep-alive

Перезагрузка страницы приведет к повторной отрисовке страницы, например при откате и т. д. У нас есть некоторые компоненты, которые не активны (данные остаются неизменными) и не хотят, чтобы она перерисовывалась, поэтому ее можно использовать здесь<keep-alive> </keep-alive>Заверните его, чтобы он не срабатывалcreatedкрюк

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

1. Не используйте пример keep-alive

Здесь домашний компонент находится вcreatedраспечатать текущее время

<div id="app">
    <router-link to="/home">home</router-link>
	<router-link to="/login">login</router-link>

	<router-view></router-view>
</div>
<script>
      const login = {
        template: `
        <div>Login</div>
        `,
      }
      const home = {
        template: `
        <div>Home</div>
        `,
        created() {
          console.log(new Date())
        },
      }
      const router = new VueRouter({
        routes: [
          {
            path: '/',
            redirect: '/home',
          },
          {
            path: '/home',
            component: home,
          },
          {
            path: '/login',
            component: login,
          },
        ],
      })
      let vm = new Vue({
        el: '#app',
        data: {},
        methods: {},
        router,
      })
  </script>

5

Как и выше, каждый переключательhomeКомпонент маршрутизации будетрендер, Время печати

При использованииkeep-aliveкаков будет эффект

2. Используйте поддержку активности

Просто заверните это здесь

<div id="app">
    <router-link to="/home">home</router-link>
    <router-link to="/login">login</router-link>

    <keep-alive>
        <router-view></router-view>
    </keep-alive>
</div>

6

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

Конечно, вы можете взять один в компонентеnameимяkeep-aliveдобавить в тегincludeсвойства могут кэшировать соответствующие компоненты

const login = {
    name: login,
    template: `
    <div>Login</div>
    `,
}
const home = {
    name: home,
    template: `
    <div>Home</div>
    `,
    created() {
    	console.log(new Date())
    },
}
<div id="app">
    <router-link to="/home">home</router-link>
    <router-link to="/login">login</router-link>

    <keep-alive include="login,home">
        <router-view></router-view>
    </keep-alive>
</div>

3. активирован и деактивирован

порядок выполнения жизненного цикла keep-alive

При доступе к маршруту в первый раз:

  • created-->mounted -->activated
  • deactivatedЗапускается после выхода

Ввод позже вызовет только activated

11.хэш и режим истории

1.режим хеширования

в vue-маршрутизатореИспользовать по умолчаниюдаhashмодель

hashЯвляется ли якорь URL **#, через привязку в качестве адреса маршрутизации, что мы обычно меняем, так это меняем#**В последней части браузер отображает указанный компонент. Он будет запущен при изменении точки привязки.onhashchangeмероприятие

2.режим истории

Режим истории — это обычный адрес, который требует поддержки сервера.

Если доступный путь к пути не напрямую404

существуетHTML5После двух новых API

pushState(): поддержка IE10

replaceState()

существуетvue-routerесли вы хотите использоватьhistoryмодельнеобходимо указать

const router = new VueRouter({
  mode: 'history'
})

Реализовать базовый Vue Router

Просмотрите основы маршрутизации выше, поэтому давайте напишем Vue Router.

Реализация этого Vue Router основана на режиме истории.

Все шаги вынесены в комментарии кода, пишите комментарий к каждой строке

Этот простенький написан не по исходникам Vue Router, в основном реализация некоторых базовых функций

Заложить основу для последующего написания по исходному коду

1. Зарегистрируйте глобальный Vue Router

Первый — зарегистрировать собственный Vue Router.

Определить, зарегистрирован ли компонент

Зарегистрируйтесь после создания экземпляра Vue

// 保存一个全局变量 Vue
let _Vue = null

// 默认导出自己写的 VueRouter
export default class MyVueRouter {
  // 实现install 注册 MyVueRouter vue提供install可供我们开发新的插件及全局注册组件等
  // 把Vue传进去
  static install(Vue) {
    // 定义一个标识判断是否注册了 MyVueRouter ,注册了就不用下一步了
    if (MyVueRouter.install.installed) return
    // 没有就进行下面的,把标识改变true
    MyVueRouter.install.installed = true
    // 把全局变量 _Vue 保存
    _Vue = Vue
    // 为了获取Vue中的this执行这里使用 混入
    _Vue.mixin({
      // 在Vue实例创建好的时候进行操做
      beforeCreate() {
        // 判断是否是实例创建还是组件创建 ,可以判断是否挂载 了router
        if (this.$options.router) {
          // 把router注册到 _Vue上
          _Vue.prototype.$router = this.$options.router
        }
      },
    })
  }
}

2. Реализовать конструктор

optoinsсохранить входящие правила

routerMapОбратитесь и определите взаимосвязь между компонентами

currentУказывает, что текущий адрес отвечает, а затем отображает компонент, связанный с ним.

export default class MyVueRouter {
  ...
  //实现构造
  constructor(optoins) {
    // 这个保存的是  routes
    this.optoins = optoins
    // routerMap 保存路由和 组件之间的关系
    this.routerMap = {}
    // 用来记录数据 这里面的数据都是 响应式
    this.data = _Vue.observable({
      // 当前路由的地址
      current: '/',
    })
  }
}

3. Разбираем правила маршрутизации

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

export default class MyVueRouter {
  ...
  // 解析路由规则
  createRouterMap() {
    // 把之前构造函数的中的传入的 routes 规则进行遍历
    this.optoins.routes.forEach((item) => {
      // 把路由 和 组件的对应关系添加到 routerMap中
      this.routerMap[item.path] = item.component
    })
  }
}

4. Реализуйте компонент router-link

router-link — ссылка маршрутизации, отображаемая на странице

Поскольку обычное использование — это в основном работающая версия Vue, поэтому я сам конвертирую компоненты в виртуальный DOM.

Также есть проблема, что ссылка будет обновляться

Напишите функцию для перехода, чтобы предотвратить событие по умолчанию

Вы также должны обратить внимание на компоненты, которые будут отображаться соответствующими маршрутами.

export default class MyVueRouter {
  ...
  // 实现组件
  initComponents(Vue) {
    // 实现 router-link组件
    Vue.component('router-link', {
      props: {
        // router-link上面的to属性将访问的地址
        to: String,
      },
      // 由于运行版的Vue不能渲染template所以这里重新写个render 这里h 也是个函数
      // template: `<a :href="to"><slot></slot></a>`,
      render(h) {
        // 第一个参数是标签
        return h(
          'a',
          // 第二个参数是对象是 tag 里面的属性
          {
            // 设置属性
            attrs: {
              href: this.to,
            },
            // 绑定事件
            on: {
              // 重新复写点击事件,不写的话会点击会向服务器发送请求刷新页面
              click: this.myClick,
            },
          },
          // 这个是标签里面的内容 这里渲染是 默认插槽
          [this.$slots.default]
        )
      },
      methods: {
        //router-link的点击事件
        myClick(e) {
          // 因为我这里是模拟是 history的路由所以用pushState ,hash路由可以这里用 push
          // 使用history修改浏览器上面的地址
          // pushState 第一个参数是传递的参数,第二个是标题,第三个是链接
          history.pushState({}, '', this.to)
          // 渲染相应的组件
          // 渲染的页面也需要改变 data中的current是响应式的 router-view是根据current来渲染的
          this.$router.data.current = this.to
          // 阻止默认跳转事件
          e.preventDefault()
        },
      },
    })

5. Реализуйте компонент router-view

Здесь текущие соответствующие компоненты получаются из ранее проанализированных правил и преобразуются в виртуальный DOM.

наконецrouter-viewотрисовка заполнителя на странице

export default class MyVueRouter {
  ...
  // 实现组件
  initComponents(Vue) {
    // 实现 router-view组件
    Vue.component('router-view', {
      render(h) {
        // 获取的当前路径所对应的组件
        // 因为当前this是Vue,this.$router才是MyVueRouter
        const component = this.$router.routerMap[this.$router.data.current]
        // 转化为虚拟Dom
        return h(component)
      },
    })
  }
}

6. Вперед и назад

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

export default class MyVueRouter {
  ...
  // 初始化事件
  initEvent() {
    // 监听浏览器地址的改变
    window.addEventListener('popstate', () => {
      // 改变VueRouter的当前的地址 重新渲染组件
      this.data.current = window.location.pathname
    })
  }
}

7. Инициализировать после установки маршрутизатора

Наконец, напишите функцию для инициализации

Инициализировать после того, как маршрутизатор зарегистрирован в Vue

export default class MyVueRouter {
  // 初始化
  init() {
    // 解析路由规则
    this.createRouterMap()
    // 初始化组件
    this.initComponents(_Vue)
    // 初始化事件
    this.initEvent()
  }
    
  static install(Vue) {
    if (MyVueRouter.install.installed) return
    MyVueRouter.install.installed = true
    _Vue = Vue
    _Vue.mixin({
      beforeCreate() {
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
          // 注册完router后进行初始化
          this.$options.router.init()
        }
      },
    })  
  }
  ...
}

8. Ставим полный index.js

// 保存一个全局变量 Vue
let _Vue = null

export default class MyVueRouter {
  // 实现install 注册 MyVueRouter vue提供install可供我们开发新的插件及全局注册组件等
  // 把Vue传进去
  static install(Vue) {
    // 定义一个标识判断是否注册了 MyVueRouter ,注册了就不用下一步了
    if (MyVueRouter.install.installed) return
    // 没有就进行下面的,把标识改变true
    MyVueRouter.install.installed = true
    // 把全局变量 _Vue 保存
    _Vue = Vue
    // 为了获取Vue中的this执行这里使用 混入
    _Vue.mixin({
      // 在Vue实例创建好的时候进行操做
      beforeCreate() {
        // 判断是否是实例创建还是组件创建 ,可以判断是否挂载 了router
        if (this.$options.router) {
          // 把router注册到 _Vue上
          _Vue.prototype.$router = this.$options.router
          // 注册完router后进行初始化
          this.$options.router.init()
        }
      },
    })
    // 判断是否挂载
  }
  // 实现构造方法
  constructor(optoins) {
    // 这个保存的是  routes
    this.optoins = optoins
    // routerMap 保存路由和 组件之间的关系
    this.routerMap = {}
    // 用来记录数据 这里面的数据都是 响应式
    this.data = _Vue.observable({
      // 当前路由的地址
      current: '/',
    })
  }
  // 解析路由规则
  createRouterMap() {
    // 把之前构造函数的中的传入的 routes 规则进行遍历
    this.optoins.routes.forEach((item) => {
      // routes中的每一项都是一个对象 { path: '/XXX', component: XXX}
      // 把路由 和 组件的对应关系添加到 routerMap中
      this.routerMap[item.path] = item.component
    })
  }
  // 实现组件
  initComponents(Vue) {
    // 实现 router-link组件
    Vue.component('router-link', {
      props: {
        // router-link上面的to属性将访问的地址
        to: String,
      },
      // 由于运行版的Vue不能渲染template所以这里重新写个render 这里h 也是个函数
      // template: `<a :href="to"><slot></slot></a>`,
      render(h) {
        // 第一个参数是标签
        return h(
          'a',
          // 第二个参数是对象是 tag 里面的属性
          {
            // 设置属性
            attrs: {
              href: this.to,
            },
            // 绑定事件
            on: {
              // 重新复写点击事件,不写的话会点击会向服务器发送请求刷新页面
              click: this.myClick,
            },
          },
          // 这个是标签里面的内容 这里渲染是 默认插槽
          // 比如<router-link to="/">首页</router-link>
          // 插槽就是给首页两个字留位置,当前这只是个例子
          [this.$slots.default]
        )
      },
      methods: {
        //router-link的点击事件
        myClick(e) {
          // 因为我这里是模拟是 history的路由所以用pushState ,hash路由可以这里用 push
          // 使用history修改浏览器上面的地址
          // pushState 第一个参数是传递的参数,第二个是标题,第三个是链接
          history.pushState({}, '', this.to)
          // 渲染相应的组件
          // 渲染的页面也需要改变 data中的current是响应式的 router-view是根据current来渲染的
          this.$router.data.current = this.to
          // 阻止默认跳转事件
          e.preventDefault()
        },
      },
    })
    // 实现 router-view组件
    Vue.component('router-view', {
      render(h) {
        // 获取的当前路径所对应的组件
        // 因为当前this是Vue,this.$router才是MyVueRouter
        const component = this.$router.routerMap[this.$router.data.current]
        // 转化为虚拟Dom
        return h(component)
      },
    })
  }
  // 初始化事件
  initEvent() {
    // 监听浏览器地址的改变
    window.addEventListener('popstate', () => {
      // 改变VueRouter的当前的地址 重新渲染组件
      this.data.current = window.location.pathname
    })
  }

  // 初始化
  init() {
    // 解析路由规则
    this.createRouterMap()
    // 初始化组件
    this.initComponents(_Vue)
    // 初始化事件
    this.initEvent()
  }
}

На данный момент базовые функции реализации почти одинаковы.Приведенный выше пример закладывает основу для следующего.Все функции в основном реализованы в одном файле.Vue Routerисходный код для реализации самостоятельноVue Router

Реализация маршрутизатора Vue

После описанной выше простой реализации теперь мы пишем в виде исходного кода Vue Router.

1. Первый — это конструкция Vue Router.

/* index.js */

// 导出自己写的 VueRouter
export default class VueRouter {
  // 实现构造函数功能
  constructor(options) {
    // 获取options中的routes路由规则 没有就为空数组
    this._options = options.routes || []
  }
  // 初始化
  init(Vue) {}
}

2. Зарегистрируйте установку компонента

существуетinstall.jsнаписал себеVue-Routerглобальная регистрация

Создам здесь позжеrouter****router** **route

и регистрацияrouter-link router-view

/* install.js */

// 定义一个全局 的Vue
export let _Vue = null

// 导出 install方法
export default function install(Vue) {
  // 保存到全局的Vue
  _Vue = Vue
  // 混入
  _Vue.mixin({
    // Vue实例创建完毕之后操做
    beforeCreate() {
      // 这里是new Vue
      if (this.$options.router) {
        // 保存 Vue
        this._routerRoot = this
        // 保存 Vue Router 的实例,以后可以通过Vue Router构造的一些方法
        this._router = this.$options.router
        // 调用Vue Router的init(Vue) 初始化操做
        this._router.init(this)
      } else {
        // 这里是创建 Vue的组件等等
        // 判断是否有父组件 ,有的话就把父组件的 _roterRoot(也就是Vue)给 子组件
        // 没有父组件就把 this 这是也是(Vue) 给子组件
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
    },
  })
}

затем вindex.jsимпортировать вinstallдобавить для строительстваinstall

// 导入 install
import install from './install'

// 导出自己写的 VueRouter
export default class VueRouter {
	...
}
    
// 为VueRouter 添加 install方法
VueRouter.install = install

3. Напишите create-route-map.js

Основная функция этого состоит в том, чтобы разобрать пройденный маршрут, который нужно экспортировать, а затемcreate-matcher.jsиспользовать

Отмечены конкретные детали

/* create-route-map.js */

// 导出具体的路由解析
/**
 *
 * @param {*} routes 路由规则
 * @param {*} oldPathList 路由列表
 * @param {*} oldPathMap 路由和组件的对应关系
 */
export default function createRouteMap(routes, oldPathList, oldPathMap) {
  // 传入了就是添加动态路由 没有传入就默认为空
  const pathList = oldPathList || []
  const pathMap = oldPathMap || []

  // 遍历规则操作
  routes.forEach((route) => {
    // 记录路由 也是核心的解析路由 为了分工明确写的外面
    addRouteRecord(route, pathList, pathMap)
  })

  // 返回新的路由列表 和 路由对应关系
  return {
    pathList,
    pathMap,
  }
}

/**
 *
 * @param {*} route 路由规则
 * @param {*} pathList 路由列表
 * @param {*} pathMap 路由和组件之间的对应关系
 * @param {*} parentRecord  父路由
 */
function addRouteRecord(route, pathList, pathMap, parentRecord) {
  // 路由地址 判断是否存在父级的路由 有的话拼接父级路由和当前路由的path 没有就是当前route.path
  const path = parentRecord ? `${parentRecord.path}/${route.path}` : route.path
  // record作为一个路由记录 记录了路由地址,组件,父级路由   用于路由对应关系去对应相对应的path
  const record = {
    path,
    component: route.component,
    parent: parentRecord,
  }
  // 判断是否在路由列表中 存在当前路由,不存在进行添加当前路由,更新路由列表
  if (!pathList[path]) {
    // 向路由列表中添加路由
    pathList.push(path)
    // 向路由对应关系中 添加path 相对应的记录
    pathMap[path] = record
  }
  // 判断当前的 路由是否有子路由,有的话进行递归
  if (route.children) {
    route.children.forEach((childRoute) => {
      // 就简单说下最后一个参数 就是父级路由记录
      addRouteRecord(childRoute, pathList, pathMap, record)
    })
  }
}

4. Напишите create-matcher.js

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

В этом модуле достаточно вызвать указанный выше конкретный метод разбора маршрута.

При указанном выше конкретном разрешении маршрутизации этоcreate-matcher.jsЭто легко реализовать, просто назовите его

Этот модуль возвращает два метода

  • match: Создайте объект правила маршрутизации в соответствии с путем маршрутизации, затем вы можете получить всю информацию о маршрутизации через объект правила, а затем получить все компоненты для создания
  • addRoutes: добавить динамический маршрут
/* create-matcher.js */

// 导入具体的路由解析规则
import createRouteMap from './create-route-map'

// 导出解析路由规则 传入的是规则
export default function createMatcher(router) {
  // pathList 路由的列表  pathMap 路由与组件的对应关系 nameMap这里没有考虑,先完成个简单的
  // 具体的解析规则是使用  createRouteMap
  const { pathList, pathMap } = createRouteMap(router)
  // match是 从pathMap 根据path获取 相应的路由记录
  function match(path) {
      //待实现
  }
  // 添加动态路由
  function addRoutes(router) {
    // 添加动态路由肯定也要解析路由规则
    createRouteMap(router, pathList, pathMap)
  }
  // 返回match 和 addRoutes
  return {
    match,
    addRoutes,
  }
}

затем вindex.jsто естьVue Routerиспользуется при строительствеcreateMatcher.использоватьthis.matcherперенимать

// 导入 install
import install from './install'
// 导入解析路由
import createMatcher from './create-matcher'

// 导出自己写的 VueRouter
export default class VueRouter {
  // 实现构造函数功能
  constructor(options) {
    // 获取options中的routes路由规则 没有就为空数组
    this._routes = options.routes || []
    // 解析路由 传入规则 这里还返回了两个方法 match,addRoutes 用matcher接收一下之后有用
    this.matcher = createMatcher(this._routes)
  }
  // 初始化
  init(Vue) {}
}
// 为VueRouter 添加 install方法
VueRouter.install = install

5. Напишите createMatcher

см. выше вcreateMatcherопределяетmatchВсе же,

matchОтpathMapсогласно сpathПолучить соответствующую запись маршрутизации

Вышеупомянутое еще не было реализовано, теперь это реализовать

Если вам нужно реализовать это, вам нужно написатьcreateRouteметод, я написал его здесьuitl/route.jsмодуль

/* util/route.js */

// 导出 createRoute
/**
 *
 * @param {*} record 传过来的记录
 * @param {*} path 路由地址
 * @returns
 */
export default function createRoute(record, path) {
  // 保存路由的记录 里面可能有多个路由 是这种模式保存 [parentRecord,childRecord]
  const matched = []
  // 判断是否是子路由
  // 下面 record = record.parent 在不断向上找parent有继续执行
  // 没有就直接return 下面的对象
  while (record) {
    // 循环得到的 record不断插入到 数组的最前面
    matched.unshift(record)
    // 把父记录给当前record 继续循环
    record = record.parent
  }
  // 返回path 和 matched 以便之后 router-view渲染
  return {
    path,
    matched,
  }
}

написано вышеcreateRouteметод мы можемcreate-mathcer.jsЗвонок, чтобы получить запись

после этогоcreate-mathcer.jsПродолжает улучшатьсяmatchметод

/* create-matcher.js */

// 导入具体的路由解析规则
import createRouteMap from './create-route-map'
// 导入 createRoute
import createRoute from './util/route'

// 导出解析路由规则 传入的是规则
export default function createMatcher(router) {
  // pathList 路由的列表  pathMap 路由与组件的对应关系 nameMap这里没有考虑,先完成个简单的
  // 具体的解析规则是使用  createRouteMap
  const { pathList, pathMap } = createRouteMap(router)
  // match是 从pathMap 根据path获取 相应的路由记录
  function match(path) {
    // 取出path对应的记录
    const record = pathMap[path]
    // 判断记录是否存在
    if (record) {
      return createRoute(record, path)
    }
    return createRoute(null, path)
  }
  // 添加动态路由
  function addRoutes(router) {
    // 添加动态路由肯定也要解析路由规则
    createRouteMap(router, pathList, pathMap)
  }
  // 返回match 和 addRoutes
  return {
    match,
    addRoutes,
  }
}

6. Обработка исторических записей История

существуетhistoryСоздайте новый в каталогеbaseДля приготовления родительского модуля

Этот родительский класс имеетhashузор иhistory(html5) общий метод режима

Вот основная демонстрацияhashкод шаблона

/* history/base.js */

// 导入 我们上面写好的 createRoute
import createRoute from '../util/route'

// 导出 History
export default class History {
  // router 是路由对象 也就是 VUe-Router的一个实例
  constructor(router) {
    // 赋值给自己的 router
    this.router = router
    // 默认的的当前路径为 /
    this.current = createRoute(null, '/')
  }
  // 将要跳转的链接
  // path 是路由的地址, onComplete是一个回调
  transitionTo(path, onComplete) {
    // 获取当前的应该跳转的路由  调用的是 Vue-Router中 this.matcher中收到的match方法
    // 在这里 this.router就是 Vue-Router的一个实例 所以写成
    // this.router.matcher.match(path)
    this.current = this.router.matcher.match(path)
    // 回调存在触发回调
    onComplete && onComplete()
  }
}

записыватьHashHistoryРежим наследованияHistory

/* /history/hash */

// 导入 base中的 History
import History from './base'

// 继承了 History
export default class HashHistory extends History {
  constructor(router) {
    super(router)
    // 确保第一次访问的时候路由加上 #/
    ensuerSlash()
  }
  // 监听URL的改变 设置当前的current
  setUpListener() {
    // 监听 hash的变化
    window.addEventListener('hashchange', () => {
      // 改变 this.current
      this.transitionTo(this.getCurrentLocation())
    })
  }
  // 获取当前的URL的hash 当然这里要去除 #
  getCurrentLocation() {
    // 这里不建议写成这个 return window.location.hash.slice(1) 有兼容问题
    let href = window.location.href
    const index = href.indexOf('#')
    // 当没有 #的时候 直接返回 空字符串
    if (index < 0) return ''
    // 获取 #后面的地址
    href = href.slice(index + 1)
    return href
  }
}

// 确保第一次加上 #/
function ensuerSlash() {
  // 如果存在 hash的话就不行加 /
  if (window.location.hash) {
    return
  }
  // 如果没有hash值 只要给 hash 加上一个 / 它会自动加上 /#/
  window.location.hash = '/'
}

оhtml5Режим здесь не написан

затем обратно вindex.jsЯ написал это самVue RouterПродолжайте писать суждение о режиме в

Последнее - инициализировать метод init

/* index.js */

// 导入 install
import install from './install'
// 导入解析路由
import createMatcher from './create-matcher'
// 导入 HashHistory
import HashHistory from './history/hash'
// 导入 HTML5History
import HTML5History from './history/html5'

// 导出自己写的 VueRouter
export default class VueRouter {
  // 实现构造函数功能
  constructor(options) {
    // 获取options中的routes路由规则 没有就为空数组
    this._routes = options.routes || []
    // 解析路由 传入规则 这里还返回了两个方法 match,addRoutes 用matcher接收一下之后有用
    this.matcher = createMatcher(this._routes)
    // 获取模式 没有就默认为 hash 模式
    this.mode = options.mode || 'hash'
    // 使用 if 或者 分支都行 根据不同的模式执行不同的路由跳转功能等等
    switch (this.mode) {
      case 'history':
        this.history = new HTML5History(this)
        break
      case 'hash':
        // 模式的实例使用 this.history接收等下用的上
        // 传入的this是 VueRouter
        this.history = new HashHistory(this)
        break
      default:
        throw new Error('该模式不存在')
    }
  }
  // 初始化
  init(Vue) {
    // 拿到模式的实例
    const history = this.history
    // 进行跳转  第一个参数是path ,第二个是回调函数
    history.transitionTo(history.getCurrentLocation, () =>
      // 监听URL的改变 设置当前的 this.current
      history.setUpListener()
    )
  }
}
// 为VueRouter 添加 install方法
VueRouter.install = install

7. Определите значение ответа _route

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

Итак, мы пришлиinstall.jsДобавьте реактивное свойство **_route**

код, не связанный с этим...пропускать

/* install.js */

export let _Vue = null

export default function install(Vue) {
  _Vue = Vue
  Vue.mixin({
    beforeCreate() {
      if (this.$options.router) {
        ...
        // 创建一个代表当前路由 响应式的值_route
        // 其实不建议使用 defineReactive直接创建。。
        // 第一个参数是绑定在谁身上,第二是值名称,第二个是值
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      } else {
        ...
      }
    },
  })
}

тогда придется вернутьсяhistoryпоследующийbaseдобавить модификацию, отвечающую_routeзначение обратного вызоваthis.cb

/* history/base.js */

import createRoute from '../util/route'

export default class History {
  constructor(router) {
    ...
    // cb 一个回调函数,它的作用就是修改 响应式路由的值_route ,对应的视图然后就刷新
    this.cb = null
  }
  // 通过 listen来修改 cb的值
  listen(cb) {
    this.cb = cb
  }

  transitionTo(path, onComplete) {
	...
    // cb 存在就修改响应式路由的值
    this.cb && this.cb(this.current)
	...
  }
}

Наконец вindex.jsизinitВызовите метод listen и передайте обратный вызов, чтобы изменить ответное значение **_route**

/* index.js */

...

export default class VueRouter {
  ...
  init(Vue) {
    ...
    // 修改 响应式的 route
    history.listen((route) => {
      Vue._route = route
    })
  }
}
...

8. Добавить$routerа также$route

мы знаем, что вVue Routerпри условии$router(Это объект маршрутизации **Vue Router**) а также$route(объект правила маршрутизации)

мы можем прийтиinstall.jsДобавьте эти два свойства в

/* install.js */

...
export default function install(Vue) {
  ...
  // 添加 $router 路由对象  Object.defineProperty 参数分别是 为谁添加,属性名,属性值
  Object.defineProperty(Vue.prototype, '$router', {
    get() {
      // this._routerRoot代表的是 Vue ,他的_router是 Vue Router实例
      // 可以回过去看看第二点
      return this._routerRoot._router
    },
  })
  // 添加 $route
  Object.defineProperty(Vue.prototype, '$route', {
    get() {
      // 他的_route是就是刚才添加 响应式 的当前 路由
      return this._routerRoot._route
    },
  })
}

9.router-link

Я не буду много говорить об основном введении, оно было представлено ранее. А теперь сделай это снова

существуетcomponentsновый файл подlink.js

/* ./components/link.js */

// 导出 link
export default {
  props: {
    to: {
      type: String,
      required: true,
    },
  },
  // 渲染
  render(h) {
    // 转化为虚拟DOM
    return h(
      // 标签名
      'a',
      // 标签属性
      {
        domProps: {
          href: '#' + this.to,
        },
      },
      // 标签里面的内容 这里是 默认插槽
      [this.$slots.default]
    )
  },
}

10.router-view

существуетcomponentsновый файл подview.jsКонкретные шаги все написаны в комментариях.

/* ./components/link.js */

// 导出 view
export default {
  render(h) {
    // 获取路由规则对象
    const route = this.$route
    // 定义一个变量,用来等下 取 matched 中的值
    let depth = 0
    // 该组件为 router-view
    this.routerView = true
    // 尝试去获取父组件
    let parent = this.$parent
    // 判断是否有父组件
    while (parent) {
      // 判断该组件是否为 routerView
      if (parent.routerView) {
        depth++
      }
      // 继续向上判断还有无父组件
      parent = parent.$parent
    }
    // 这里的route是 this.$route 就是 _route 响应式值,也就是 current
    // 当初 current 是 调用了 match方法 获取到的 返回值是 matched 和 path
    // matched 里面是多个路由对象 是这种模式保存 [parentRecord,childRecord]
    // 通过 变量depth取出来 举个栗子 ['/login','/login/tab']
    // 因为使用的unshif添加后面的父组件添加到前面
    // depth 一直加 ,直接取出后面即可
    const record = route.matched[depth]
    // 没有记录直接渲染
    if (!record) {
      return h()
    }
    // 有的话就获取记录中的组件
    const component = record.component
    // 最后把组件渲染
    return h(component)
  },
}

Хорошо здесьVue RouterВторой раз написания завершено, хотя есть большой разрыв с официальным. . Ну потому что тут упрощенно

11. Каталог файлов

Забыл вставить последний каталог файла

image-20210723215447677

это моделированиеVue Routerизdemoпомещатьgithub, при необходимости можно нажать сюдаMyVueRouter

Здесь это достигается толькоVueRouterнебольшая часть функционала

Но в целом функции почти реализованы, реализована и вложенная маршрутизация, и динамическая маршрутизация.

На самом деле, я думаю, что это нормально, чтобы попасть сюда, но я все еще должен продолжать учиться.