likes
comments
collection
share

Vue3-VueRouter

作者站长头像
站长
· 阅读数 20

Vue3-VueRouter

Vue Router是Vue.js官方的路由管理器。它和 Vue.js 的核⼼深度集成,让构建单⻚⾯应⽤变得易如反掌。包含的功能有:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有⾃动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中⾃动降级
  • ⾃定义的滚动条⾏为

起步

⽤ Vue.js + Vue Router 创建单⻚应⽤,是⾮常简单的。使⽤ Vue.js,我们已经可以通过组合组件来组成应⽤程序,当你要把 Vue Router添加进来,我们需要做的是,将组件 (components) 映射到路由(routes),然后告诉 Vue Router 在哪⾥渲染它们

安装

npm i vue-router -S

在main.js中

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

推荐使用:vue add router 添加插件(记得提前提交)

基本使用

router.js

import Vue from 'vue'
//1.导⼊
import Router from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'
//2.模块化机制 使⽤Router
Vue.use(Router)
//3.创建路由器对象
const router = new Router({
  //mode: 'history', //history模式 干净的网页地址 没有#/之类的符号存在
  routes:[{
    path: '/home',
    component: Home
  },
  {
    path: '/about',
    component: About
 }]
})
export default router;

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
    // 4.挂载根实例
    router,
    render: h => h(App)
}).$mount('#app')

做好以上配置后

<template>
  <div id="app">
    <div id="nav">
	<!-- 使⽤router-link组件来导航 -->
	<!-- 通过传⼊to属性指定连接 -->
	<!-- router-link默认会被渲染成⼀个a标签 -->
	<router-link to="/">Home</router-link> |
        <router-link to="/about">About</router-link> |
	<!-- 路由出⼝ -->
        <!-- 路由匹配的组件将被渲染到这⾥ -->
	<router-view/>
    </div>
  </div>
</template> 

打开浏览器,切换Home和About超链接,查看效果

命名路由

在配置路由的时候,给路由添加名字,访问时就可以动态的根据名字来进行访问

const router = new Router({
        routes:[{
            path: '/home',
            name: 'home',
            component: Home
 		},
 		{
            path: '/about',
            name:'about'
            component: About
 		}]
})

要链接到一个命名路由,可以给router-linkto属性传一个对象:

<router-link :to="{name:'home'}">Home</router-link> |
<router-link :to="{name:'about'}">About</router-link> |

动态路由匹配

我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有⼀个 User 组件,对于所有 ID 各不相同的⽤户,都要使⽤这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使⽤“动态路径参数”(dynamic segment) 来达到这个效果

User.vue

<template> 
  <div> 
  	<h3>⽤户⻚⾯</h3>
  </div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped></style>

路由配置

const router = new Router({
    routes:[{
        path: '/user/:id',
        name: 'user',
		component: User,
    }]
})
javascript
复制代码<router-link :to="{name:'user',params{id:1}}">User</router-link> |

查看效果

当匹配到路由时,参数值会被设置到this.$route.params,可以在每个组件中使用,于是,我们可以更新User的模板,输出当前用户的ID:

<template>
  <div> 
  	<h3>⽤户⻚⾯{{$route.params.id}}</h3>
  </div>
</template>

响应路由参数的变化

提醒⼀下,当使⽤路由参数时,例如从 /user/1 导航到 /user/2原来的组件实例会被复⽤。因为两个路由都渲染同个组件,⽐起销毁再创建,复⽤则显得更加⾼效。不过,这也意味着组件的⽣命周期钩⼦不会再被调⽤。

复⽤组件时,想对路由参数的变化作出响应的话,你可以简单地watch (监测变化) $route 对象:

/*使⽤watch(监测变化) $route对象
 watch: {
     $route(to, from) {
         console.log(to.params.id);
     }
 }, */
// 或者使⽤导航守卫
beforeRouteUpdate(to,from,next){
    //查看路由的变化
    //⼀定要调⽤next,不然就会阻塞路由的变化
    next();
}

404路由

const router = new Router({
    routes:[
     //....
	// 匹配不到路由时,404⻚⾯显示
 	{
        path: '*',
        component: () => import('@/views/404')
 	}]
})

当使⽤通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。路由 { path: '*' } 通常⽤于客户端 404错误

当使⽤⼀个通配符时, $route.params 内会⾃动添加⼀个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分:

{
  path: '/user-*',
  component: () => import('@/views/User-admin.vue')
}
this.$route.params.pathMatch // 'admin'

匹配优先级

有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。

查询参数

const router = new Router({
    routes:[
        //...
        {
            path: '/page',
            name: 'name',
            component:() => ('@/views/Page.vue')
        }]
})
javascript
复制代码<router-link :to="{name: 'page', query:{id: 1, title: 'foo'}}">User</router-link>

Page.vue

<template>
	<div>
		<h3>Page页面</h3>
		<h3>{{$route.query.userId}}</h3>
	</div>
</template>
<script>
export default {
    created () {
	//查看路由信息对象
	console.log(this.$route);
        },
    }
</script>
<style>
</style>

路由重定向和别名

重定向

Example是从/重定向到/home

const router = new Router({
    mode: 'history',
    routes: [
     // 重定向
     {
        path: '/',
        redirect: '/home'
     },
     {
        path: '/home',
		name: 'home',
		component: Home
     },
   ]
})

重定向的目标也可以是一个命名的路由:

const router = new VueRouter({
    routes: [
    {
      path:'/',
      redirect: {name: 'name'}
    }
  ]
})

别名

{
    path: 'user/:id',
    name: 'user',
    component: User,
    alias: '/alias'
}

"别名"的功能让你可以自由地将UI结构映射到任意的URL,而不是受限于配置的嵌套路由结构。

路由组件传参

在组件中使⽤ $route 会使之与其对应路由形成⾼度耦合,从⽽使组件只能在某些特定的 URL 上使⽤,限制了其灵活性。

使⽤ props 将组件和路由解耦:

取代与$route的耦合

Index.js

{
    path: '/user/:id',
    name: 'user',
    component: User,
    //props: true
    //props也可以是一个函数
    props: (route) => {
        id: route.params.id,
        title: route.query.title
    }
}

User.vue

<template>
  <div>
  	<h3>⽤户⻚⾯{{$route.params.id}}</h3>
		<h3>⽤户⻚⾯{{id}}</h3>
	</div>
</template>
<script>
export default{
    //....
    props: {
        id: {
            type: String,
            default: ''
 		},
    },
 }
</script>

props也可以是个函数

User.vue

<template>
  <div>
  	<h3>⽤户⻚⾯{{id}}-{{title}}</h3>
  </div>
</template>
<script>
export default {
    // ...
    props: {
		id: {
            type: String,
            default: ''
 		},
		title:{
            type: String
		}
     },
};
</script>

编程式导航

除了使用<router-link>创建a标签来定义导航链接,我们还可以借助router的实例方法。通过编写代码来实现。

注意:在Vue实例内部,你可以通过router访问路由实例。因此你可以调用this.router访问路由实例。因此你可以调用this.router访问路由实例。因此你可以调用this.router.push。

声明式编程式
<router-link :to="...">router.push(...)

该⽅法的参数可以是⼀个字符串路径,或者⼀个描述地址的对象。例如:

//字符串
this.$router.push('home')
//对象
this.$router.push({path: 'home'})
//命名的路由
this.$router.push({name: 'user', params: {userId}: '123'})
//带查询参数,变成 /register?plan=private
this.$push({path: 'register', query:{plan: 'private'}})

前进后退

//在浏览器记录中前进一步,等同于history.forward()
router.go(1)
//后退一步记录,等同于history.back()
router.go(-1)
//前进3步记录
router.go(3)
//如果history记录不够用,那就默认失败
router.go(-100)
router.go(100)

嵌套路由

router.js

{
    path: '/user/:id',
    name: 'user',
    component: User,
    props: ({params,query})=>({
        id: params.id,
        title:query.title
    }),
    children:[
        // 当 /user/:id/profile 匹配成功,
        // Profile 会被渲染在 User 的 <router-view> 中
        {	
            path:"profile",
            component: Profile
        },
        // 当 /user/:id/posts 匹配成功,
        // Posts 会被渲染在 User 的 <router-view> 中
 		{
            path: "posts",
            component: Posts
 		}
   ]
}

User组件的模板添加一个<router-view>:

<template>
    <div>
        <h3>用户页面{{$route.params.id}}</h3>
        <h3>用户页面{{id}}</h3>
        <router-view></router-view>
    </div>
</template>

App.vue

<template>
    <div id='app'>
        <!-- 嵌套路由 -->
        <router-link to="/user/1/profile">User/profile</router-link> |
        <router-link to="/user/1/posts">User/posts</router-link> |
    </div>
</template>

命名视图

有时候想同时 (同级) 展示多个视图,⽽不是嵌套展示,例如创建⼀个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上⽤场了。

{
  path: '/home',
  name: 'home',
  //注意这个key是components
  components: {
    default: Home, //默认的名字
    main: ()=>import('@/views/Main.vue'),
    sidebar: ()=>import('@/views/Sidebar.vue')
  }
}

App.vue

<router-view/>
<router-view name='main'/>
<router-view name='sidebar'/>

导航守卫

“导航”表示路由正在发生改变。

完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的beforeEach守卫
  4. 在重用的组件里调用beforeRouterUpdate守卫(2.2+)。
  5. 在路由配置里调用beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用beforeRouterEnter
  8. 调用全局的beforeResolve守卫(2.5+)。
  9. 导航被确认。
  10. 调用全局的afterEach钩子。
  11. 触发DOM更新。
  12. 用创建好的实例调用beforeRouterEnter守卫中传给next的回调函数。

全局守卫

你可以使用router.beforeEach注册一个全局前置守卫

const router = new VueRouter({...})
router.beforeEach((to, form, next) => {
	//...
})

有个需求,⽤户访问在浏览⽹站时,会访问很多组件,当⽤户跳转到 /notes ,发现⽤户没有登录,此时应该让⽤户登录才能查看,应该让⽤户跳转到登录⻚⾯,登录完成之后才可以查看我的笔记的内容,这个时候全局守卫起到了关键的作⽤。

有两个路由 /notes/login

router.vue

const router = new VueRouter({
    routes: [
        {
            path: '/notes',
            name: 'notes',
            component: () => import('@/views/Notes')
		},
		{
            path: '/login',
            name: 'login',
            component: () => import('@/views/Login')
		},
    ]
})

//全局守卫
router.beforeEach((to, from, next) => {
  //用户访问的是'/notes'
  if(to.path === '/notes') {
    //查看一下用户是否保存了登录状态信息
    let user = 
    JSON.parse(localStorage.getItem('user'))
    if(user) {
      //如果有,直接放行
      next();
    }else {
      //如果没有,用户跳转登录页面登录
      next('/login')
    }
  }else {
    next();
  }
})

Login.vue

<template>
  <div>
     <input type="text" v-model="username">
     <input type="password" v-model="pwd">
     <button @click="handleLogin">提交</button>
  </div>
</template>
<script>
export default {
	data() {
    return {
      username: "",
      pwd: ""
    };
  },
  methods: {
    handleLogin() {
      //1.获取用户名和密码
      //2.与后端发生交互
      setTimeout(() => {
        let data = {
          username: this.username
        };
        //保存用户登录信息
        localStorage.setItem("user", JSON.stringfy(data));
        //跳转我的笔记页面
        this.$router.push({name: "notes"});
      }, 1000);
    },
  }
}
</script>

App.vue

<!--全局守卫演示-->
<router-link to="/notes">我的笔记</router-link> |
<router-link to="/login">登录</router-link> |
<button @click="handleLogout">退出</button> 
<script>
export default {
    methods: {
        handleLogout() {
            //删除登录状态信息
            localStorage.removeItem("user");
            //跳转到⾸⻚
            this.$router.push('/')
        }
    },
}
</script>

组件内的守卫

你可以在路由组件内直接定义以下路由导航守卫:

  • beforeRouteEnter
  • beforeRouteUpdate(2.2新增)
  • beforeRouteLeave
<template>
  <div>
      <h3>用户编辑页面</h3>
      <textarea name id cols="30" rows="10" v-model="content"</textarea>
      <button @click="saveData">保存</button>
      <div class="wrap" v-for="(item, index) in list" :key="index">
          <p>{{item.title}}</p>
      </div>
  </div>
</template>
<script>
export default {
    data() {
        return {
          content: "",
          list: [],
        }
    },
    methods: {
        saveData() {
          this.list.push({
          title: this.content
        });
        this.content = "";
     }
  },
  beforeRouteLeave(to, from, next) {
    //导航离开该组件的对应路由时调用
    //可以访问组件实例`this`
    if(this.content) {
      alert("请确保信息已保存再离开");
      next(false);
    }else {
      next();
    }
  }
};
</script>

路由元信息实现权限控制

给需要添加权限的路由设置meta字段

{
    path: '/blog',
    name: 'blog',
    component:() => import('@/views/Blog'),
    meta: {
        requiresAuth: true
    },
}
{
    //路由独享的守卫
    path: 'notes',
    name: 'notes',
    component: () => import('@/views/Notes'),
    meta: {
        requiresAuth: true
    }
},
//全局守卫
router.beforeEach((to, from, next) => {
  if(to.matched.some(record => record.meta.requiresAuth)) {
    //需要权限
    if(!localStorage.getItem('user')) {
      next({
        path: '/login',
        query: {
          redirect: to.fullPath
        }
      })
    }else {
      next();
    }
  }else {
    next();
  }
})

Login.vue

//登录操作
handleLogin() {
    // 1.获取⽤户名和密码
    // 2.与后端发⽣交互
    setTimeout(() => {
        let data = {
            username: this.username
         };
	localStorage.setItem("user", JSON.stringify(data));
	// 跳转到之前的⻚⾯
	this.$router.push({path:this.$route.query.redirect });
    },1000};
}

数据获取

有时候,进⼊某个路由后,需要从服务器获取数据。例如,在渲染⽤户信息时,你需要从服务器获取⽤户的数据。我们可以通过两种⽅式来实现:

  • 导航完成之后获取:先完成导航,然后在接下来的组件⽣命周期钩⼦中获取数据。在数据获取期间显示“加载中”之类的指示。
  • 导航完成之前获取:导航完成前,在路由进⼊的守卫中获取数据,在数据获取成功后执⾏导航。

导航完成后获取数据

当你使⽤这种⽅式时,我们会⻢上导航和渲染组件,然后在组件的 created 钩⼦中获取数据。这让我们有机会在数据获取期间展示⼀个 loading 状态,还可以在不同视图间展示不同的 loading 状态。

<template>
  <div class="post"> 
    <div v-if="loading" class="loading">Loading...</div>
	<div v-if="error" class="error">{{ error }}</div> 
	<div v-if="post" class="content">
            <h2>{{ post.title }}</h2> 
            <p>{{ post.body }}</p>
	</div>
    </div>
</template>
<script>
export default {
    name: "Post",
        data() {
            return {
		loading: false,
		post: null,
		error: null
            };
 	},
	// 组件创建完后获取数据,
	// 此时data已经被监视了
	created() {
	// 如果路由有变化,会再次执⾏该⽅法
	this.fetchData();
 	},
	watch: {
    $route: "fetchData"
  },
  methods: {
    fetchData() {
      this.error = this.post = null;
      this.loading = true;
      this.$http.get('/api/post').then((result) => {
        this.loading = false;
        this.post = result.data;
      }).catch((err) => {
        this.error = err.toString();
      });
   }
}
</script>

路由在项目中的封装使用

Vue3+Vue-Router封装,具体文件如下:

router/ ----afterGuard.js #后置路由守卫 ----frontGuard.js #前置路由守卫 ----index.js #路由管理 ----routes.js #路由文件

afterGuard.js

export default function afterGuard(router) {
  router.afterEach((to, from, failure) => {
    // do something
    return true
  });
};

frontGuard.js

export default function frontGuard(router) {
  router.beforeEach((to, from) => {
    // ...
    // 返回 false 以取消导航

    return true;
  });
};

routes.js

import Index from "../layout/index.vue"

const routes = [
  {
    path: "/",
    component: Index,
    children: []
  },
];

export default routes;

index.js

import { createRouter, createWebHashHistory } from "vue-router";
import routes from "./routes";
import frontGuard from './frontGuard'
import afterGuard from './afterGuard'

const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

// 应用前置路由守卫
frontGuard(router)

// 应用后置路由守卫
afterGuard(router)

const useRouter = function(app){
    app.use(router)
}

export default useRouter;

注册路由

main.js

import { createApp } from "vue";
import App from "./App.vue";
import useRouter from './router/index'

const app = createApp(App);

// 注册路由
useRouter(app)

app.mount("#app");