Vue官方路由管理器Vue-router入门教程

2023-08-25 13:04:37 浏览数 (1)

前几天写了 Vue状态管理模式:Vuex入门教程 ,今天再整理一下 Vue Router 的入门笔记。

什么是 Vue Router ?

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含如下功能:

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

安装

代码语言:javascript复制
npm install vue-router

main.js 中通过 Vue.use() 明确地安装路由功能:

代码语言:javascript复制
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

基础使用

router.js or router/index.js

代码语言:javascript复制
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({
    routes:[{
      path: '/home',
      component: Home
    },
    {
      path: '/about',
      component: About
    }
  ]
})
export default router;

main.js

代码语言:javascript复制
import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

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

以上配置完后

App.vue

代码语言:javascript复制
<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> |
    </div>
    <!-- 路由出口 -->
    <!-- 路由匹配的组件将被渲染到这里 -->
    <router-view/>
  </div>
</template>

运行项目,打开浏览器,切换 Home 和 About 超链接,查看效果。

命名路由

在配置路由的时候,给路由添加 name 属性,可以动态的根据 name 来进行访问。

代码语言:javascript复制
const router = new Router({
    routes:[{
      path: '/home',
      name:"home",
      component: Home
    },
    {
      path: '/about',
      name:'about'
      component: About
    }
  ]
})

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

代码语言:javascript复制
<router-link :to="{name:'home'}">Home</router-link>
<router-link :to="{name:'about'}">About</router-link>

动态路由匹配

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

User.vue

代码语言:javascript复制
<template>
  <div>
    <h3>用户页面</h3>
  </div>
</template>

<script>
export default {

};
</script>

<style scoped>
</style>

路由配置:

代码语言:javascript复制
const router = new Router({
    routes:[
        {
            path: '/user/:id',
            name: 'user',
            component: User,
        },
    ]
})

链接传参:

代码语言:javascript复制
<router-link :to="{name:'user', params:{id:1}}">User</router-link>

访问链接:

http://localhost:8080/user/1

http://localhost:8080/user/2

查看效果。

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

代码语言:javascript复制
<template>
  <div>
    <h3>用户页面{{$route.params.id}}</h3>
  </div>
</template>

响应路由参数的变化:

注意:当使用路由参数时,例如从 /user/1 导航到 /user/2,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。

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

代码语言:javascript复制
/*使用watch(监测变化) $route对象
 watch: {
        $route(to, from) {
            console.log(to.params.id);

        }
    }, */
// 或者使用导航守卫
beforeRouteUpdate(to,from,next){
	//查看路由的变化
    //一定要调用next,不然就会阻塞路由的变化
    next();
}

404路由

代码语言:javascript复制
const router = new Router({
    routes:[
        //....
        // 匹配不到理由时,404页面显示
        {
            path: '*',
            component: () => import('@/views/404')
        }
    ]
})

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

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

代码语言:javascript复制
{
    path: '/user-*',
    component: () => import('@/views/User-admin.vue')
}
this.$route.params.pathMatch // 'admin'

匹配优先级:

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

查询参数

像这种地址:http://localhos:8080/page?id=1&title=w3h5

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

访问 http://localhos:8080/page?id=1&title=w3h5 查看 Page

Page.vue

代码语言:javascript复制
<template>
    <div>
        <h3>Page页面</h3>
        <h3>{{$route.query.userId}}</h3>
    </div>
</template>

<script>
    export default {
        created () {
            //查看路由信息对象
            console.log(this.$route);
        },
    }
</script>

<style scoped>

</style>

路由重定向和别名

例如:从 / 重定向到 /home

代码语言:javascript复制
const router = new Router({
    mode: 'history',
    routes: [
        // 重定向
        {
            path: '/',
            redirect: '/home'
        },
        {
            path: '/home',
            name: 'home',
            component: Home
        },
    ]
})

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

代码语言:javascript复制
const router = new VueRouter({
  routes: [
    { path: '/', redirect: { name: 'name' }}
  ]
})

甚至是一个方法:

代码语言:javascript复制
const router = new VueRouter({
  routes: [
    { path: '/a', redirect: to => {
      // 方法接收 目标路由 作为参数
      // return 重定向的 字符串路径/路径对象
    }}
  ]
})

别名:

代码语言:javascript复制
{
    path: '/home',
    name: 'home',
    component: Home,
    alias: '/alias'
}

起别名,仅仅起起别名。用户访问 http://loacalhost:8080/alias  的时候,显示 Home 组件。

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

路由组件传参

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

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

取代与 $route 的耦合

代码语言:javascript复制
{
      path: '/user/:id',
      name: 'user',
      component: User,
      props:true
},

User.vue

代码语言:javascript复制
<template>
<div>
    <h3>用户页面{{$route.params.id}}</h3>
    <h3>用户页面{{id}}</h3>
    </div>
</template>
<script>
    export default{
        //....
        props: {
            id: {
                type: String,
                default: ''
            },
        },
    }
</script>

props 也可以是函数:

代码语言:javascript复制
{
      path: '/user/:id',
      name: 'user',
      component: User,
      props: (route)=>({
        id: route.params.id,
        title:route.query.title
      })
      
}

User.vue

代码语言:javascript复制
<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.push

声明式:<router-link :to="...">

编程式:router.push(...)

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

代码语言:javascript复制
// 字符串
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' }})

前进后退:

代码语言:javascript复制
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)

// 后退一步记录,等同于 history.back()
router.go(-1)

// 前进 3 步记录
router.go(3)

// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)

嵌套路由

实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件。

代码语言:javascript复制
/user/1/profile                     /user/1/posts
 ------------------                    ----------------- 
| User             |                  | User            |
|  --------------  |                  |  -------------  |
| | Profile      | |   ------------>  | | Posts       | |
| |              | |                  | |             | |
|  --------------  |                  |  -------------  |
 ------------------                    ----------------- 

router.js

代码语言:javascript复制
{
      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>

代码语言:javascript复制
<template>
  <div>
    <h3>用户页面{{$route.params.id}}</h3>
    <h3>用户页面{{id}}</h3>
    <router-view></router-view>
  </div>
</template>

App.vue

代码语言:javascript复制
<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 (主内容) 两个视图,这个时候命名视图就派上用场了。

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

App.vue

代码语言:javascript复制
<router-view/>
<router-view name='main'/>
<router-view name='sidebar'/>

导航守卫

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

完整的导航解析流程

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

全局守卫:

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

代码语言:javascript复制
const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})

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

有两个路由 /notes/login

router.vue

代码语言:javascript复制
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

代码语言:javascript复制
<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.stringify(data));
        // 跳转我的笔记页面
        this.$router.push({ name: "notes" });
      }, 1000);
    },
   
  }
};
</script>

App.vue

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

组件内的守卫:

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

  • beforeRouteEnter
  • beforeRouteUpdate (2.2 新增)
  • beforeRouteLeave
代码语言:javascript复制
<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: [],
      confir: true
    };
  },
  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 字段

代码语言:javascript复制
{
      path: '/blog',
      name: 'blog',
      component: () => import('@/views/Blog'),
      meta: {
        requiresAuth: true
}
},
{
      // 路由独享的守卫
      path: '/notes',
      name: 'notes',
      component: () => import('@/views/Notes'),
      meta: {
        requiresAuth: true
      }
},
代码语言:javascript复制
// 全局守卫
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

代码语言:javascript复制
//登录操作
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 状态。

代码语言:javascript复制
<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>
代码语言:javascript复制
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();
      });
    }
  }
};

未经允许不得转载:w3h5-Web前端开发资源网 » Vue官方路由管理器Vue-router入门教程

0 人点赞