Vue-Router


# 路由基础

路由的切换其实就是切换 div, 前提是看页面地址变不变化
路由相关的信息如果跟页面挂钩了,如果路由改变的话页面也会改变

# 单页面应用

  • 不会新建一个窗口,不同内容在地址栏中改变 (查看),但不刷新当前页面

  • vue-router 使用 npm i vue-router 安装

# 如何制作和添加

  • src 文件夹下自创的全局 router.js

    // 创建路由
    import Vue from "vue";
    import VueRouter from "vue-router";
    import Home from "./components/Home.vue";
    import Pins from "./components/Pins.vue";
    import Books from "./components/Books.vue";
    
    // 1.需要将路由功能制作成 vue 中的全局功能,需要使用 Vue 的use方法
    
    Vue.use(VueRouter); // 将所有的路由组件制作成了全局组件
    
    // 2.制作路由(一个路由代表一个页面(是一个对象))
    // 1). 创建路由数组
    // 路由的地址不要写成大写的
    const routes = [
      {
        path: "/", //代表根地址 http://localhost:8080/
        component: Home,
      },
      {
        path: "/pins", // 代表 http://localhost:8080/pins
        component: Pins,
      },
      {
        path: "/books", // 代表 http://localhost:8080/books
        component: Books,
      },
    ];
    
    // 2). 根据路由数组创建 整体的路由
    const router = new VueRouter({
      routes,
      // 路由模式
      // history(完全仿照浏览器历史记录模式)
      // 以后项目上线的话必须给服务器添加配置文件,支持 history 模式
      // hash(锚点模式)
      mode: "history",
      // 将所有的 router-link 默认链接激活的类名修改成了 active,一般不用
      // linkActiveClass: "active",
    });
    
    // 3). 导出使用,在main.js 导入,添加到整个 vue 项目中
    
    export default router;
  • main.js

    import Vue from "vue";
    import App from "./App.vue";
    
    // 路由的使用 虽然我们使用了 Vue.use 方法将 VueRouter 制作成了全局功能,
    // 但是想要使用我们创建好的路由还需要将创建好的路由加载到整个 vue 项目中
    // 导入 js 文件的时候如果名字叫 index.js 可以省略直接写路径就行
    // import router from "./router/index";
    import router from "./router";
    Vue.config.productionTip = false;
    
    new Vue({
      router, // 其实添加 router 属性,主要是跟路由相关组件匹配,还有将$route 和$router 挂载到Vue实例当作公共属性,
      // 也就是说所有 Vue 组件都会有这两个属性
      render: (h) => h(App),
    }).$mount("#app");
  • App.vue

    <template>
      <div id="app">
        <!-- 见下面的 router-view 和 router-link -->
        <div class="nav">
          <router-link :to="{path:'/'}">首页</router-link>|
          <router-link :to="{name:'pins',query:{userId:123}}">沸点</router-link>|
          <router-link to="/books">小册</router-link>
        </div>
        <router-view />
      </div>
    </template>
    
    <script>
      export default {
        name: "App",
      };
    </script>
    
    <style>
      .router-link-exact-active {
        color: red;
      }
    </style>
  • Home.vue,Books.vue,Pins.vue

    <template>
      <div>
        <h2>我是Home</h2>
        <!-- Books和Pins只是这里的英文不一样,别的都一样 -->
      </div>
    </template>
    
    <script>
      export default {
        name: "Pins",
      };
    </script>

# router-view

用来展示页面组件的 , 页面地址和某个路由对象的 path 匹配的话就会展示对应的页面组件
匹配的规则是在 routes 中 从上到下严格完全匹配(不包含查询部分?和锚点部分#),只能匹配一个

(https://router.vuejs.org/zh/api/#router-link)

router-link 用来实现路由的跳转,也就是页面跳转
router-link 必须传递 to prop,值是跳转的地址,当地址和 to 匹配的时候,默认会添加一些激活的类名
匹配的规则是包含匹配,比如当地址是 /pins 的时候,匹配 /pins 和 /
to 属性的属性值也可以是一个对象 {path,name,params,query}
path:路径 name:命名路由的名字
query 是地址栏的查询部分,也就是 ? 部分
params 动态路由参数,只有路由的 path 设置了动态参数的时候才可以传递
  • to :表示目标路由的链接,这个值可以是一个字符串或者是描述目标位置的对象。
  • replace :当点击时导航不会留下 history 记录。
  • append :设置 append 属性后,则在当前 (相对) 路径前添加基路径。
    例如 ,我们从 /a 导航到一个相对路径 b,如果没有配置 append,则路径为 /b,如果配了,则为 /a/b
  • tag : 有时候想要 渲染成某种标签
  • exact :精确匹配模式,比如当地址是 /pins 的时候,只匹配 /pins, 不再匹配 /
  • active-class :设置链接激活时使用的 CSS 类名。默认值可以通过路由的构造选项 linkActiveClass 来全局配置。(.router-link-exact-active)
  • exact-active-class :配置当链接被精确匹配的时候应该激活的 class。注意默认值也是可以通过路由构造函数选项 linkExactActiveClass 进行全局配置的。
<router-link :to="{name:'pins',query:{userId:123}}" append>沸点</router-link>|
<router-link :to="{path:'/'}">首页</router-link>|
<router-link :to="{name:'pins',params:{type:'hot'}}">沸点</router-link>|
<router-link to="/books">小册</router-link>
  • App.vue

    <template>
      <div id="app">
        <div class="nav">
          <router-link :to="{name:'pins',query:{type:'hot'}}">沸点</router-link>|
        </div>
        <router-view />
      </div>
    </template>
    <script>
      export default {
        name: "App",
      };
    </script>
  • Pins.vue

    这里是使用 query 来展示的

    <template>
      <div>
        <h2>我是Pins</h2>
        <h3>现处于 {{type}} 类别页面</h3>
      </div>
    </template>
    
    <script>
      export default {
        name: "Pins",
        // 我是路由组件,路由跳转的时候传递了一些参数或者查询条件,需要根据这些条件,去展示不同的内容
        // 传递方式有两种
        // 1.query 是地址栏的查询部分,也就是 ?部分
        // 2.params 动态路由参数,只有路由的 path 设置了动态参数的时候才可以传递
    
        // 在路由组件内 如何获取当前路由的相关信息
        // 只要是路由组件,那么该组件就会得到一个当前路由信息,存储在 this.$route 内
        data() {
          return {
            type: "",
          };
        },
        created() {
          // console.log(this.$route);
          this.type = this.$route.query.type;
        },
      };
    </script>

# 动态路由

一个页面渲染不同的内容 (不同的路径要展示到相同的组件叫动态路由)

  • App.vue

    <template>
      <div id="app">
        <div class="nav">
          <router-link :to="{name:'pins',params:{type:'hot'}}">沸点</router-link>|
        </div>
        <router-view />
      </div>
    </template>
    
    <script>
      export default {
        name: "App",
      };
    </script>
  • Pins.vue

    <template>
      <div>
        <h2>我是Pins</h2>
        <h3>现处于 {{type}} 类别页面</h3>
      </div>
    </template>
    
    <script>
      export default {
        name: "Pins",
        data() {
          return {
            type: "",
          };
        },
        created() {
          this.type = this.$route.params.type;
        },
      };
    </script>
  • src 文件夹下自创的全局 router.js

    import Vue from "vue";
    import VueRouter from "vue-router";
    import Pins from "./components/Pins.vue";
    Vue.use(VueRouter); // 将所有的路由组件制作成了全局组件
    const routes = [
      // 动态路由 就是多个地址都展示一个页面,就需要设置动态路由参数
      {
        // :type 就是地址栏内的变量(路由参数 路由参数可以有多个) 一定要带冒号才是变量
        // 如果是 /pins 那么页面出不来;必须是 /pins/变量 页面才可以出来
        path: "/pins/:type", // 表示 /pins/任何 都会匹配
        component: Pins,
        name: "pins",
      },
    ];
    
    // 2). 根据路由数组创建 整体的路由
    const router = new VueRouter({
      routes,
      mode: "history",
    });
    
    export default router;

# router 实例

$route 取得是当前路由 (页面) 的信息 $router 是整体路由

$router 是路由添加给所有组件的属性,可以直接访问 (注意模板语法),这个属性指的是整个路由实例,路由实例下有许多方法

  1. push("地址") 实现路由的跳转 ,当你在当前页使用 push 方法跳转到当前页会有警告,是不允许重复导航,所以需要提前判断一下
  2. back() (什么也不用加) 实现 历史记录返回
  3. go(数字) 实现 历史记录的跳转 ,前进后退都可以 go(-1)就是 back
  4. replace('地址') 替换 , 和 push 一样,但是跳转没有历史记录
  5. forward() 往前走 ,跟 back 相反的
  • logo 图跳转首页小案例

    <div class="logo">
      <!-- 点击logo图片跳转首页,使用 <router-link>包裹就能实现 -->
      <!-- 还可以直接绑定点击事件内执行 $router.push('/') -->
      <img
        @click="goHome"
        src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/logo.a7995ad.svg"
        alt
      />
    </div>
    
    <script>
      export default {
        name: "App",
        components: {},
        methods: {
          goHome() {
            // 当你在当前页使用 push 方法跳转到当前页会有警告,是不允许重复导航
            this.$route.path !== "/" && this.$router.push("/");
          },
        },
      };
    </script>

# 子页面 (嵌套路由)

  • 嵌套动态子路由案例

    • router.js

      const routes = [
        {
          path: "/pins/:type",
          component: Pins,
          name: "pins",
        },
        {
          path: "/books",
          component: Books,
          name: "books",
        },
        // 这里因为books和下面children的path重了,所以让books先匹配,后匹配 / 的孩子
        // 动态路由和一些默认路由冲突的话,要把动态路由放到后边匹配
        {
          path: "/",
          component: Home,
          // name: "home", // 命名路由
      
          // 嵌套路由
          // 当父路由匹配的时候,默认某一个子路由也需要匹配
          children: [
            {
              // 当嵌套的子路由地址是空的时候,父路由匹配的时候,这个会默认展示
              // 当给了 父路由设置了 name 的时候,如果有默认子路由,请把 name 给默认子路由设置
              path: "",
              component: List,
              name: "home", // 命名路由
            },
            {
              // 嵌套路由的 path 前面不要加 /,或者说前面不要加父路由path,默认会自动添加
              path: ":category",
              component: List,
            },
          ],
        },
      ];
    • Home.vue 父组件

      <template>
        <div>
          <h2>我是Home</h2>
          <div class="home-head">
            <router-link to="/recommended">推荐</router-link>
            <router-link to="/frontend">前端</router-link>
            <router-link to="/backend">后端</router-link>
          </div>
          <!-- 这里要展示页面,因为是不同的路径要展示到一个页面,所以使用动态路由 -->
          <!-- 嵌套路由的展示就是直接在 父路由 组件内使用 router-view 即可 -->
          <router-view />
        </div>
      </template>
    • List.vue 子组件

      <template>
        <div>我是{{$route.params.category || 'recommended'}}文章列表组件</div>
      </template>
      
      <script>
        export default {
          name: "List",
        };
      </script>

# 重定向和别名跳转页面

  • 案例 router.js

    const routes = [
      {
        // /user/:username/post/:post_id  /user/evan/post/123 {username:'evan',post_id:"123"}
        // 路由参数会被设置到 this.$route.params 中, 它负责动态路由的参数
        path: "/pin/:type",
        component: Pins,
        alias: "/pins/:type", // 别名方式
        name: "pins",
      },
      // 网站改版了,沸点页的网址不能是 pins 了,需要是 pin,但是偶尔也会有一些用户输入 pins 网址,我们也希望可以访问沸点页
      // 1. 重定向实现 pins/hot ---> pin/hot ,用户看不到pins,改成了pin
      // 2. 别名,用户可以看到pins
      // {
      //   path: "/pins/:type",
      //   redirect: "/pin/:type",
      //   redirect: { name: "pins" }, // 重定向实现
      // },
    ];

# 路由组件传参

  • router.js 文件

    import Vue from "vue";
    import VueRouter from "vue-router";
    import Home from "./components/Home.vue";
    import List from "./components/List";
    import NotFound from "./components/NotFound";
    
    Vue.use(VueRouter);
    const routes = [
      {
        path: "/",
        // 命名视图,不是命名路由
        components: {
          default: Home,
          error: NotFound,
        },
        children: [
          {
            path: "",
            component: List,
            name: "home", // 命名路由
            // 当这个路由被匹配的时候是没有动态路由参数的 其实组件内获取的是 undefined,我们想让组件获取 recommended
            // 直接将 props 写成对象类型即可,props 要写成静态的
            // 2.对象类型
            props: {
              category: "recommended",
            },
          },
          {
            path: ":category",
            component: List,
            // 当路由对象给props属性设置了true,那么 $route.params 将会被设置为组件属性
            // 也就是说params对象下的属性会当作prop传递给组件
            // 所以我们可以使用 props 替代 $route 在组件内获取动态路由参数
            // 这里加 true 是给 List 组件添加 prop,会把 $route.params(动态路由参数) 当作prop
            // 在哪里加 true 就会给哪个组件添加prop
            // 1. 布尔值类型
            // props: true,
    
            // 3.可以使用 函数代替 true,函数默认接收当前的路由为参数
            props: (route) => ({
              category: route.params.category,
            }),
          },
        ],
      },
      // NotFound 页面
      {
        path: "*",
        component: NotFound,
      },
    ];
  • List.vue 文件

    <template>
      <div>
        <!-- 这里的 category 是 prop -->
        <h3>我是{{category || 'recommended'}}文章列表组件</h3>
    
        <div>
          <!-- 展示列表内容 -->
          <div v-for="item in list" :key="item.id">{{item.title}}</div>
        </div>
      </div>
    </template>
    
    <script>
      export default {
        name: "List",
        data() {
          return {
            list: [],
            isNotFound: false,
          };
        },
        props: ["category"], // 这里接收的 prop 是 router.js 中动态路由的参数名字
        watch: {
          // 通过监听路由参数的变化,从而更新数据
          // "$route.params.category": {
          // 这个 category 是 prop
          category: {
            handler(newValue) {
              // newValue 取的是 $route.params.category 的值
              // 当动态路由参数并不是你的文章分类,请求肯定获取不到数据
              setTimeout(() => {
                if (newValue === "recommended") {
                  // 要避免 /   <----> /recommended 产生重复的请求
                  // 1. 就需要 newValue, oldValue 进行判断
                  // 2. 可以直接给组件设置默认的 category prop
                  console.log(111111111, "请求推荐类的文章");
                  this.list = [{ id: 1, title: "推荐类文章1" }];
                } else if (newValue === "frontend") {
                  this.list = [{ id: 1, title: "前端类文章1" }];
                } else if (newValue === "backend") {
                  this.list = [{ id: 1, title: "后端类文章1" }];
                }
              }, 50);
            },
            immediate: false,
          },
        },
      };
    </script>

# 路由导航守卫

  • 路由全局前置导航守卫

    • Login.vue

      <template>
        <div class="login">
          用户名:
          <input type="text" v-model.lazy.trim="username" />
          <br />密码:
          <input type="text" v-model.lazy.trim="password" />
          <br />
          <button @click="login">登录</button>
        </div>
      </template>
      
      <script>
        export default {
          name: "Login",
          data() {
            return {
              username: "",
              password: "",
            };
          },
          methods: {
            login() {
              if (this.username === "admin" && this.password === "123456") {
                // 浏览器的本地存储 localStorage 永久存 sessionStorage 页面关了就存不了了,只能存数值,布尔值,字符串
                sessionStorage.setItem("isLogin", true);
                this.$router.push("/");
              }
            },
          },
        };
      </script>
    • index.js

      import Vue from "vue";
      import VueRouter from "vue-router";
      import Home from "../views/Home.vue";
      
      Vue.use(VueRouter);
      
      const routes = [
        {
          path: "/",
          name: "Home",
          component: Home,
        },
        {
          path: "/about",
          name: "About",
          // 路由懒加载,当路由不匹配到 About 页面的时候,About 组件不会导入
          component: () => import("../views/About.vue"),
        },
        {
          path: "/login",
          name: "Login",
          component: () => import("../views/Login.vue"),
        },
      ];
      
      const router = new VueRouter({
        mode: "history",
        routes,
      });
      
      // 查看登录状态,我们的想法是在每个组件的 created 中获取登录状态
      
      // 更好的方法是路由全局导航守卫
      // router.beforeEach 全局前置守卫,路由跳转就触发,还没有跳转过去,可以控制跳不跳转,必须设置next(路由地址)
      // to 目标路由地址信息
      // from 是来源路由地址信息
      router.beforeEach((to, from, next) => {
        // 获取登录状态,控制路由跳转
        const isLogin = Boolean(sessionStorage.getItem("isLogin"));
        if (to.path !== "/login" && !isLogin) next("/login");
        next();
      });
      
      export default router;

# 路由元信息

  • index.js 文件

    import Vue from "vue";
    import VueRouter from "vue-router";
    import Home from "../views/Home.vue";
    
    Vue.use(VueRouter);
    
    const routes = [
      {
        path: "/",
        component: Home,
        children: [
          {
            path: "",
            name: "Home",
            component: () => import("../views/Index.vue"),
          },
          {
            path: "about",
            name: "About",
            component: () => import("../views/About.vue"),
            // 路由元信息
            meta: { requireAuth: true },
          },
        ],
      },
    ];
    
    const router = new VueRouter({
      mode: "history",
      routes,
    });
    
    export default router;
  • About.vue 文件

    <template>
      <div class="about">
        <h1>This is an about page</h1>
      </div>
    </template>
    <script>
      export default {
        created() {
          console.log(this.$route);
          // 匹配的路由 $route 下有一个 matched 属性,该属性是一个数组,记录了当前地址匹配到的路由记录
          // 路由记录可以直接使用 $route 访问到当前路由
          // 在路由记录内可以访问到对应的路由的 路由元信息(meta字段)
          // 通过路由元信息,添加权限属性,可以实现权限处理问题
          // 实现的就是当前地址会匹配到很多路由(包括嵌套的子路由),只要某一个路由通过路由元信息设置了权限,
          // 那么就需要遍历 $route.matched 去查询
          // 在这的判断最好去路由的全局前置守卫去判断,在这只是做个测试
          if (this.$route.matched.some((item) => item.meta.requireAuth)) {
            // 说明需要权限 需要判断当前是否有权限,没有的话跳转到首页
            const auth = "普通用户"; // 去不了about页面,值为超级管理员就可以去
            if (auth !== "超级管理员") {
              this.$router.push("/login");
            }
          }
        },
      };
    </script>

# 滚动行为

切换路由时的滚动行为,是路由里边的

  • index.js

    const router = new VueRouter({
      mode: "history",
      routes,
      // 滚动行为
      scrollBehavior(to, from, savedPosition) {
        // 该函数的返回值就是滚动条的位置
        // 控制的是 window 的滚动条
        // savedPosition 当且仅当(通过浏览器的 前进/后退 按钮触发) 时才可用。
        // (1.浏览器左上角的返回按键 2.history 的 back 或 go),保留原来页面的滚动条位置
        if (savedPosition) {
          return { ...savedPosition, behavior: "smooth" };
          // behavior: "smooth" 平滑滚动
        } else {
          return { x: 0, y: 0 };
        }
      },
    });
  • List.vue

    <template>
      <div class="list">
        <button class="back" @click="back">返回</button>
      </div>
    </template>
    <script>
      export default {
        name: "List",
        methods: {
          back() {
            // 返回上一页
            this.$router.back();
          },
        },
      };
    </script>
    <style scoped>
      .list {
        height: 2000px;
      }
      .back {
        position: fixed;
        bottom: 50px;
        right: 50px;
      }
    </style>

文章作者: Mrr-cxh
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mrr-cxh !
赏
 上一篇
Vuex Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
2021-06-08
下一篇 
Vue 4.0 Vue 4.0
Vue 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
2021-05-16
  目录