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

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

什么是 Vue Router ?

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

  • 嵌套的路由/视图表

  • 模块化的、基于组件的路由配置

  • 路由参数、查询、通配符

  • 基于 Vue.js 过渡系统的视图过渡效果

  • 细粒度的导航控制

  • 带有自动激活的 CSS class 的链接

  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级

  • 自定义的滚动条行为

安装

npm install vue-router

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

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

基础使用

router.js or router/index.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({
    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({
  router, // 4.挂载根实例
  render: h => h(App)
}).$mount('#app')

以上配置完后

App.vue 

<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 来进行访问。

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

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

<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 scoped>
</style>

路由配置:

const router = new Router({
    routes:[
        {
            path: '/user/:id',
            name: 'user',
            component: User,
        },
    ]
})

链接传参:

<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:

<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'

匹配优先级:

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

查询参数

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

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

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

Page.vue

<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

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' }}
  ]
})

甚至是一个方法:

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

别名:

{
    path: '/home',
    name: 'home',
    component: Home,
    alias: '/alias'
}

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

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

路由组件传参

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

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

取代与 $route 的耦合

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

User.vue

<template>
<div>
    <h3>用户页面{{$route.params.id}}</h3>
    <h3>用户页面{{id}}</h3>
    </div>
</template>
<script>
    export default{
        //....
        props: {
            id: {
                type: String,
                default: ''
            },
        },
    }
</script>

props 也可以是函数:

{
      path: '/user/:id',
      name: 'user',
      component: User,
      props: (route)=>({
        id: route.params.id,
        title:route.query.title
      })
      
}

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.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)

嵌套路由

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

/user/1/profile                     /user/1/posts
+------------------+                  +-----------------+
| User             |                  | User            |
| +--------------+ |                  | +-------------+ |
| | Profile      | |  +------------>  | | Posts       | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+

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. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。

  5. 在路由配置里调用 beforeEnter

  6. 解析异步路由组件。

  7. 在被激活的组件里调用 beforeRouteEnter

  8. 调用全局的 beforeResolve 守卫 (2.5+)。

  9. 导航被确认。

  10. 调用全局的 afterEach 钩子。

  11. 触发 DOM 更新。

  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

全局守卫:

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

const router = new VueRouter({ ... })

router.beforeEach((to, from, 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.stringify(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>
export default {
  methods: {
     handleLogout() {
      //删除登录状态信息
      localStorage.removeItem("user");
      //跳转到首页
      this.$router.push('/')
    }
  },
}

组件内的守卫:

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

  • 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: [],
      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 字段

{
      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>
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 » Vue官方路由管理器Vue-router入门教程

赞 (0)
分享到: +

评论 沙发

Avatar

换个身份

  • 昵称 (必填)
  • 邮箱 (选填)