• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

理解vue-router原理,并实现小demo

武飞扬头像
海绵泡泡
帮助2

插件编写的基本方法

推荐大家先看看官方给出的插件使用和开发方法 vuejs.bootcss.com/guide/plugi…

需求分析

我们先看看vue-router的使用步骤

使用官方的vue脚手架初始化的项目中 ,我们来看看下面两个文件:

src/router/index.js

import Vue from "vue";
import VueRouter from "vue-router";

import Home from "../views/Home.vue";

Vue.use(VueRouter);
// Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home,
  },
  {
    path: "/about",
    name: "About",
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/About.vue"),
  },
];

const router = new VueRouter({
  // mode: "hash",
  routes,
});

export default router;

src/main.js

import Vue from "vue";
import App from "./App.vue";
import router from "./router";

Vue.config.productionTip = false;

new Vue({
  router,
  render: (h) => h(App),
}).$mount("#app");

vue-router的调用顺序

  1. use

    Vue.use(VueRouter) 注意⚠️: Vue.use()主要是调用插件内部的install方法,并将Vue实例作为参数传入 ​

  2. new 一个router实例

    const router = new VueRouter({
      // 实例化router传入的参数
      mode: 'history',
      base: process.env.BASE_URL,
      routes
    })
    
  3. new Vue() ,把实例放在vue的配置项里面

    new Vue({
      router, // 注意router的实例也往里传
      render: h => h(App)
    }).$mount('#app')
    
  4. 使用路由组件<router-view/><router-link></router-link>或者在组件中使用this.$router

由此我们看看vue-router内部做了什么?

  1. 将$router挂载到全局上
  2. 实现并声明了两个组件:<router-view/><router-link></router-link>

实现思路

首先我们看看如何将$router挂载到组件上

let Vue; // 保存vue的构造函数,避免打包将其打进去
VueRouter.install = function (_Vue) {
  Vue = _Vue;
  console.log("options", Vue.$options);
  Vue.mixin({
    beforeCreate() {
      console.log("inner", this);
      console.log(" this.$options.router", this.$options.router);
      if (this.$options.router) {
        Vue.prototype.$router = this.$options.router;
      }
    },
  });
  console.log("end");
};

学新通

可以看到: 1、第一次执行的时候,即在Vue.use(Router)时,还没有实例化vue(因为Vue.use() 发生在 new Vue()之前),所以Vue.$option本身是拿不到的(ps: option就是new Vue()时传入的参数,router也往里面传),此时既然拿不到router的实例,所以不能直接在install方法里面挂载;

2、我们可以在use的时候做一个全局混入,在合适的时间点,获取到Vue根实例配置项中的router实例, 执行挂载。紧接着在new Vue()根实例创建的时候,因为注入了router实例,所以再执行全局混入(mixin)中的生命周期时,这个时候根实例的配置项this.$options已经包含了router实例,可以此时把router挂载到Vue的原型上。之后所有Vue实例扩展来的VueCompont都可以通过this.$router访问到这个属性

为什么要定义一个局部vue

这里我再解释一下第一行代码;

因为我们要在这个router类里面用到Vue的实例,按照最简单的想法,当然就是直接把vue引入进来用;

但是这样我们打包发布的时候,就包含了vue的包;

一个vue的插件包含了vue的包,是不是很奇怪;所以我们创建一个局部变量,在Vue.use()的时候,第一个参数传入的就是vue的实例,此时我们将这个实例复赋值给我们的局部变量, 就完成了我们的目的:

既可以调用vue实例,又不会将其打包;

所以这也是我们在调用很多插件时,必须先use的原因之一吧(还有一个原因,就是挂载实例);


比如:vuex,你new store()之前也得先use; 用来挂在实例和保存vue实例

如何实现那两个路由组件

先看看路由组件如何使用

<div id="app">
  <div id="nav">
    <!-- a标签控制跳转 -->
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link>
  </div>
  <!-- 路由出口 -->
  <router-view />
</div>

由上面可以看出,点击router-link,就相当于点了a标签,然后a标签的href属性控制页面路由发生了变化;监听路由变化,然后仔router-view里面输出不同的模板; ​

而路由变化有两种方式

hash模式 :

使用 URL 的 hash 来模拟一个完整的 URL, 其显示的网络路径中会有 “#” 号 hash 虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash后刷新, 也不会有问题 hash模式示例: http://localhost:8080/#/home http://localhost:8080/#/user hash详解: hashChange ​

history模式:

美化后的hash模式,路径中不包含“#”。依赖于Html5 的** history api** 由于改变了地址, 刷新时会按照修改后的地址请求后端, 需要后端配置处理, 将地址访问做映射, 否则会404; history模式示例: http://localhost:8080/home http://localhost:8080/user history 监听路由: popState, pushState() history模式监听路由分两种情况,一种是直接监听popstate事件刷新页面组件,这个只适合网页首次访问、浏览器的前进和后退的情况,因为history.pushState()或history.replaceState()不会触发popstate事件。另一种就是通过router.push、router.replace来改变url地址,这时候,在这两个api的内部实际上也分了两步走:

  1. 页面组件更新(即更新router的current属性)
  2. 底层调用history.pushState/history.replaceState改变url

我们来实现一个简单的hash式的路由变化

大伙儿不知道怎么注册全局组件的话,最好先看看下面这个 组件基础搭建

先来看看router-link

// 注册并且实现两个组件
Vue.component("router-link", {
  // 有一个必传的to值,即a标签里面的href,用于改变url
  props: {
    to: {
      required: true,
    },
  },
  render(h) {
    return h(
      "a",
      {
        attrs: { href: "#"   this.to },
      },
      // 默认插槽
      this.$slots.default
    );
  },
  });

再来看看router-view

class VueRouter {
  constructor(options) {
    // 接受传入的参数
    this.$options = options;
    const initial = "/";
    // 将current变成响应式数据,
    //这样在hashchange的回掉中修改curent时,
    //用到current的router-view的render函数就会重新渲染
    Vue.util.defineReactive(this, "current", initial);
    // 监听路由变化
    window.addEventListener("hashchange", () => {
      // 获取当前url中的hash
      this.current = window.location.hash.slice(1);
    });
  }
}

VueRouter.install = function (_Vue) {
  Vue = _Vue;
  Vue.component("router-view", {
    render(h) {
      // 获取当前路由所对应的组件,然后把它渲染出来
      const { current, $options } = this.$router;
      // 这里要注意 我们传进来的routes是一个路由表,如下图一
      // 所以这里我们是找出匹配到当前current路由的项,然后直接渲染组件
      const route = $options.routes.find((item) => {
        return item.path === current;
      });
      let component = route ? route.component : null;

      return h(component);
    },
  });
}

图一学新通

完整demo代码

// 我们要实现什么
// 1、插件
// 2、两个组件

// 保存vue的构造函数,避免打包将其打进去
let Vue;
class VueRouter {
  constructor(options) {
    this.$options = options;
    const initial = "/";
    Vue.util.defineReactive(this, "current", initial);
    this.current = "/";
    window.addEventListener("hashchange", () => {
      // 获取当前url中的hash
      this.current = window.location.hash.slice(1);
    });
  }
}

// 参数1在Vue.use()调用时传进来,
VueRouter.install = function (_Vue) {
  Vue = _Vue;
  console.log("options", this);

  // 全局混入
  // 目的:延迟下面的逻辑 到 router创建完毕并且附加到选项上时才执行
  Vue.mixin({
    // 在每个组件创建实例时都会执行
    beforeCreate() {
      // this.$options.router ;即new Vue时放进去的router实例
      if (this.$options.router) {
        Vue.prototype.$router = this.$options.router;
      }
    },
  });

  // 注册并且实现两个组件
  Vue.component("router-link", {
    props: {
      to: {
        required: true,
      },
    },
    render(h) {
      return h(
        "a",
        {
          attrs: { href: "#"   this.to },
        },
        this.$slots.default
      );
    },
  });
  Vue.component("router-view", {
    render(h) {
      // 获取当前路由所对应的组件,然后把它渲染出来
      const { current, $options } = this.$router;
      const route = $options.routes.find((item) => {
        return item.path === current;
      });
      let component = route ? route.component : null;

      return h(component);
    },
  });
};

export default VueRouter;

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhbgibbi
系列文章
更多 icon
同类精品
更多 icon
继续加载