Vue

VUE3之vue-router

vue-router的基本使用和配置

首先要有两个组件Home.vue和About.vue,之后将这两个组件注册到路由中:

import Home from '../pages/Home.vue';
import About from '../pages/About.vue';

// 配置映射关系
const routes = [
  {path: '/', component: Home},
  {path: '/home', component: Home},
  {path: '/about', component: About}
];

创建一个路由对象,并传入配置:

import {createRouter, createWebHashHistory, createWebHistory} from 'vue-router'
// 创建一个路由对象router
const router = createRouter({
  routes,
  history: createWebHashHistory()
})

在main.js中使用router:

import { createApp } from 'vue'
import router from './router'
import App from './App.vue'

const app=createApp(App);
app.use(router);
app.mount('#app');

此时路由已经添加成功,需要在app.vue中使用路由组件。

router-view是用于显示路由中组件的地方,router-link是一个控制路由路径的a元素,使用如下:

  <div>
    <router-link to="/home">首页</router-link>
    <router-link to="/about">关于</router-link>
    <router-view></router-view>
  </div>

vue-router知识点补充

在路由关系中:

const routes = [
  {path: '/', component: Home},
  {path: '/home', component: Home},
  {path: '/about', component: About}
];

默认的’/’,可以为其指定一个component,也可以重定向到一个页面:

  {path: '/', redirect: '/home'},

route还有两个属性:name是为一个route定义一个名字,路由是可以通过这个name跳转的;meta定义了这个路由的内部数据,可以通过route.meta来获取到。

对于route-link,默认使用的是history的popstate策略,为其增加replace属性后将使用replacestate策略。

  <div>
    <router-link to="/home" replace>首页</router-link>
    <router-link to="/about" replace>关于</router-link>
    <router-view></router-view>
  </div>

被选中的route-link,会自动添加上两个class属性:router-link-active和router-link-exact-active

image 40

可以利用这个class为其配置样式。其中router-link-exact-active与router-link-active的区别是,router-link-exact-active只有在精准匹配时才会加上这个class(嵌套组件的嵌套路由),而router-link-active是任何父组件也会加上。

这个默认的class可以通过active-class和extra-active-class来修改:

  <div>
    <router-link to="/home" replace active-class="home-active">首页</router-link>
    <router-link to="/about" replace active-class="about-active">关于</router-link>
    <router-view></router-view>
  </div>
image 41

路由懒加载

在路由配置的component属性中,将原来的组件替换为一个function(import的promise):

// import Home from '../pages/Home.vue';
// import About from '../pages/About.vue';

const routes = [
  {path: '/', redirect: '/home'},
  {path: '/home', component: ()=>{
    return import("../pages/Home.vue")
  }},
  {path: '/about', component: ()=>{
    return import("../pages/About.vue")
  }}
];
image 42

利用webpack的魔法注释来为组件分包文件命名,格式为/* webpackChunkName: “” */:

const routes = [
  {path: '/', redirect: '/home'},
  {path: '/home', component: ()=>{
    return import(/* webpackChunkName: "home-chunk" */"../pages/Home.vue")
  }},
  {path: '/about', component: ()=>{
    return import(/* webpackChunkName: "page-chunk" */"../pages/About.vue")
  }}
];
image 43

动态路由

在route中可以加上/:params来表示动态路由:

// route
  {
    path: "/user/:username",
    component: ()=>{
      return import("../pages/User.vue")
    }
  }

在route-link中,必须要匹配到对应的格式才能正确路由:

// route-link
    <router-link :to="'/user/'+name">用户</router-link>

在组件中获取动态路由的参数:一是从this中获取,在this.$route中:

<template>
  <div>
    <h2>User: {{$route.params.username}}</h2>
  </div>
</template>

<script>
  export default {
    created() {
      console.log(this.$route.params.username);
    },
  }
</script>

二是使用vue-route 4的钩子函数useRoute直接获取这个组件的route:

<script>
import {useRoute, useRouter} from "vue-router"
  export default {
    setup(props) {
      const route = useRoute();
      console.log(route.params.username);
    }
  }
</script>

嵌套路由同理。

NotFound

定义一个类似于动态路由中的route,不过path是固定写法:

  {
    // 固定写法pathMatch
    path: "/:pathMatch(.*)",
    component: ()=>{
      return import("../pages/NotFound.vue")
    }
  }

由此可以导向我们编写的NotFound的组件。

获取NotFound时的路径的方法:从route.params.pathMatch中获取:

<h2>{{$route.params.pathMatch}}</h2>

如果在/:pathMatch(.*)后面再加上一个*,就会将路径按照每一个”/”为分隔符进行分割并返回一个数组。

嵌套路由

现在home组件下还有两个子组件HomeShops和HomeMessage,分别应该使用/home/shops和/home/message来访问之,在配置路由时,在home的路由下的children属性来为这两个组件配置。

  {
    path: '/home', 
    component: ()=>{
      return import(/* webpackChunkName: "home-chunk" */"../pages/Home.vue")
    },
    name: "home",
    meta: {
      name: "shopkeeper"
    },
    children: [
      {
        path:'shops',
        component: ()=> import("../pages/HomeShops.vue")
      },
      {
        path:'message',
        component:()=> import("../pages/HomeMessage.vue")
      }
    ]
  },

可以发现在children中的route,path缺省了父组件的”/home”,并且也不需要加上”/”。

在children中设置重定向,path直接填写空字符串即可,但是redirect内容要填写完整的重定向路径:

    children: [
      {
        path:"",
        redirect:"/home/message"
      },
      {
        path:'shops',
        component: ()=> import("../pages/HomeShops.vue")
      },
      {
        path:'message',
        component:()=> import("../pages/HomeMessage.vue")
      }
    ]

编程式导航

编程式导航是指通过代码来实现跳转逻辑。例如我们需要点击一个按钮来跳转到“关于”页,假设有如下的button:

    <button @click="jumpToAbout">关于</button>

对于jumpToAbout方法,如果是在methods的options中定义,可以直接使用this.$router对象:

  methods: {
    jumpToAbout(){
      // 首先要拿到router对象(不推荐直接导入)
      this.$router.push("/about");
    }
  },

如果是在setup中,需要使用一个和useRoute相似的钩子函数useRouter:

import {useRouter} from 'vue-router'
export default {
  components: {
  },
  setup(props) {
    const router = useRouter();
    function jumpToAbout(){
      // 首先要拿到router对象(不推荐直接导入)
      router.push("/about");
    }
    return {
      name: "shopkeeper"
    }
  },

在这个push方法中还可以传入一个对象(router-link的to属性也可以传入对象),对象中可以包含其它的一些配置,例如:

      router.push({
        path:"/about",
        query:{
          name: "shopkeeper",
          age:18
        }
      });

参数可以在子组件的$route.query中获取:

<h2>About: {{$route.query.name}}-{{$route.query.age}}</h2>

router还有的方法例如replace、go、forward、back等都和history中的用法相同。

router-link的v-slot

router-link实际上可以看作是一个插槽,在这个标签的内部可以写入我们想要展示的元素或者组件:

    <router-link to="/home" replace active-class="home-active">
      <nav-bar title="首页"></nav-bar>
    </router-link>

route-link中定义v-slot属性,可以获取其内部传来的一个对象:

    <!-- props:href 跳转的链接 -->
    <!-- props:route对象 -->
    <!-- props:navigate函数 -->
    <!-- props:isActive 当前是否处于活跃状态 -->
    <!-- props:isExactActive 是否是精确活跃状态 -->
    <router-link to="/home" replace active-class="home-active" v-slot="props" custom>
      <!-- <nav-bar title="首页"></nav-bar> -->
      <button>{{props.href}}</button>
      <p>{{props.route}}</p>
      <button @click="props.navigate">导航</button>
      <span :class="{'active': props.isActive}">{{props.isActive}}</span>
    </router-link>

如果再加上custom属性的话,router-link将不会给插槽中的组件外层嵌套一个a标签,即此时跳转需要我们手动执行(使用props.navigate函数)。

router-view的v-slot

router-view也可以使用插槽,在v-slot中可以获取props.Component属性,即当前要显示的组件,通过这种方式就可以结合transition、component、keep-live等组件使用

动态添加/删除路由

对于一些需要选择性注释的路由,可以动态添加:

// 动态添加路由
const categoryRoute = {
  path: "/category",
  component: ()=>import("../pages/Category.vue") 
}
const momentRoute = {
  path: "moment",
  component: ()=>import("../pages/HomeMoment.vue") 
}
// 添加顶级的路由对象
router.addRoute(categoryRoute);
// 添加二级的路由对象
router.addRoute("home",momentRoute);

动态删除一个路由有三种方式:

  • 添加一个name相同的路由,之前的路由就会被替代
  • 通过removeRoute方法,传入路由的名称
  • 通过addRoute方法的返回值回调(返回的值是一个删除路由的函数)

路由的其它一些方法补充:

  • router.hasRoute() 检查路由是否存在
  • router.getRoutes() 获取一个包含所有路由记录的数组

路由导航守卫

路由的导航守卫有多种函数可以实现,现在以beforeEach为例,beforeEach种传入一个函数:

// to:Route对象,即将跳转到的route对象
// from:Route对象,从哪个路由对象导航过来的
/**
 * 返回值:
 * 1.false:不进行导航
 * 2.undefined或者不写返回值:进行默认的导航
 * 3.字符串:路径,跳转到对应的路径中
 * 4.对象:类似于router.push({path:"/login",component})
 */
router.beforeEach((to, from)=>{
  console.log(to.path,from.path);
  if (to.path.indexOf("/home")!==-1){
    return "/login"
  }
})

使用导航守卫实现一个简单的登录逻辑:假定login组件中,按下登录按钮就会在localStorage中保存一个token,

<template>
  <div>
    <button @click="loginClick">登录</button>
  </div>
</template>

<script>
import {useRouter} from 'vue-router'
  export default {
    setup(props) {
      const router=useRouter()
      const loginClick=()=>{
        window.localStorage.setItem("token","shopkeper")
        router.push({
          path:"/home"
        })
      }
      return {loginClick}
    }
  }
</script>

那么在导航守卫中,如果要跳转到一个非login的页面,都应该判断本地是否有token,如果没有就应该跳转到login组件中:

router.beforeEach((to, from)=>{
  if (to.path!== "/login"){
    const token = window.localStorage.getItem("token");
    if (!token){
      return "/login"
    }
  }
})

其它的导航守卫可以参考官网

发表评论