学习使用 Vue 路由

前言:现在 Vue 的路由已经开始大规模应用在单页面应用上了。比较常见的就是路由网址中的 URL 里面的hash(#) ,这个 hash(#)来源于哪里那?没错就来自HTML 的锚点技术。大概如下原理:

HTML锚点

一图胜千言。
源代码(省略好多h6标签):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>#的作用</title>
</head>
<body>
    <h1><a href="#title">跳转到标记一 </a></h1>
    <h6>一行车马向东疾驰,行不数里,便有数骑马迎来,驰到车前,翻身下马,高声向令狐冲致意,言语礼数,甚是恭敬。</h6>
    <h1><a id="title">我是标记一</a></h1>
    <h6>一行车马向东疾驰,行不数里,便有数骑马迎来,驰到车前,翻身下马,高声向令狐冲致意,言语礼数,甚是恭敬。</h6>
</body>
</html>

除了使用锚点技术之外,很明显无论 React 还是 Vue 的路由大量使用了 H5 里面的,window.history,最常用的就包含window.pushState({},"",url)=>添加一条历史记录。window.history.replaceState()=>替换当前URL, window.history.forward() => 浏览器的前进按钮,window.history.back() => 浏览器的后退按钮,window.history.go(number)=>浏览器前进后退几步,还有一个 hash 路由事件onhashchange

一、配置一个路由

Vue 官网:https://router.vuejs.org/zh/
项目安装路由:

npm install --save vue-router

项目目录的结构:这次目录的结构做的真好看,制作方法是使用搜狗输入法的(ctr+shift+z)制表符。

┏ components
┃   ┣ HaHa.vue
┃   ┗ XiXi.vue
┣ router
┃   ┗ router.js
┣ App.vue
┣ main.js
┗ index.html

最终的路由效果:


最终的路由效果

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 学习</title>
</head>
<body>
    <div id="app"></div>
    <script src = "./virtual/bundle.js"></script>
</body>
</html>

main.js 里 vue-router 配置四步走

import Vue from "vue";
import App from "./App.vue";
// ①
import VueRouter from "vue-router";
import routes from "./router/router.js";

// 使用路由②
Vue.use(VueRouter);
// ③
const router = new VueRouter({
   routes
})
new Vue({
    el:"#app",
    render(h){
        return h(App);
    },
    // ④
    router
});

router.js

// 引入组件
import XiXi from "../components/XiXi.vue";
import HaHa from "../components/HaHa.vue";
// 暴露路由需要的数组
export default [
    {path : "/XiXi",component : XiXi},
    {path : "/HaHa",component : HaHa}
];

App.vue

<template>
    <div>
        <h1>路由测试</h1>
        <div class="box">
            <p>我是一个盒子组件将会在这里显示</p>
            <!-- 使用 router-link 组件来导航. -->
            <!-- 通过传入 `to` 属性指定链接. -->
            <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
            <router-link to="/HaHa">Go to HaHa</router-link>
            <router-link to="/XiXi">Go to XiXi</router-link>
            <router-link to="/">Go to home</router-link>
            <!-- 路由出口 -->
            <!-- 路由匹配到的组件将渲染在这里 -->
            <router-view></router-view>
        </div>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style scoped>
.box{
    width: 500px;
    height: 200px;
    border:20px solid #5e5ed9;
}
</style>

components 里面就是单纯的两个组件。

二、子路由

我们在子组件 HaHa 里面再放入孙子组件 Bar 。URL 变成http://127.0.0.1:8080/#/HaHa/bar

演示效果大约如下:


我们需要改动的地方有:
router.js 里面在组件 HaHa 下面加入 childrren 书写子路由。与 react 不同的是子路由 不需要在前面加上 /

// 引入组件
import XiXi from "../components/XiXi.vue";
import HaHa from "../components/HaHa.vue";
import Bar from "../components/Bar.vue";
// 暴露路由需要的数组
export default [
    {path : "/XiXi",component : XiXi},
    {
        path : "/HaHa",
        component : HaHa,
        children:[
            {path : "Bar",component:Bar}
        ]
    }
];

除此之外还需要在 HaHa.vue 组件里面放上标签<router-view></router-view>,用来显示孙子组件 Bar 。

嵌套路由如何指定默认路由:

{
    path:"user",
    name:user,
    component: ()=>import("@/components/user.vue"),
    children:[
        {
            path:"/",//path:"/"就是默认路由,同时path:""也是,自动访问
            name:index,
            component: ()=>import("@/components/index.vue")
        }
    ]
}
三、两种路由跳转方式
  • 第一种 HTMl 方法
    就是我们上面使用的 <router-link to="/HaHa">Go to HaHa</router-link> 标签,这个标签的页面效果就类似HTML里面的a标签。
  • JS 方法
    <a @click="$router.push('/HaHa/bar')">Go to HaHa</a> 我们在 a 标签添加事件,因为配置过路由,$router就像vuex的$store一样可以在任何子组件使用,我们只需要调用push就能指定跳转目标。

路由跳转路径可为分为两种:字符串和对象

  1. 字符串上面我们使用的就是字符串
  2. 对象字面量方法,使用对象的时候我们可以:
    • {path:'/HaHa'},还可以传参分为 params和query的方式,{path:'/HaHa',params:{userId:123}},{path:'/HaHa',query:{name:'hero'}}
    • {name:'haha'} 命名路由,也可以传参也分为 params和query的方式,{path:'/HaHa',params:{userId:123}},{path:'/HaHa',query:{name:'hero'}}
四、使用路由进行页面布局

有时候想同时 (同级) 展示多个视图,而不是嵌套展示,就例如我们的有一个网页页面的头部是一个 Header 组件,页面底部是一个Footer 组件,页面中间左边是一个侧边栏 Slider 组件。右边是页面内容 Content 组件。而我们要求输入的网址必须为:http://127.0.0.1:8080/#/index.html

原型图如下:


首先我们要是用路由的重定向功能,让网址一打开http://127.0.0.1:8080/#/就跳转到http://127.0.0.1:8080/#/index.html。路由文件添加代码:

{ path: '/', redirect: "/index.html"}

插题路由重定向是可以重定向到自己内部的。

{
    path:"/home",
    redertec:"/index",
    children:[
        {
            path:"/index",
            name:"index",
            children:[
                
            ]
        }
    ]
}

下面来讲讲如何实现,默认已经完成基本布局HTML大致布局代码:

<div class="box">
            <!-- 网页头部 -->
            <header>我是Header组件</header>
            <!-- 网页主体 -->
            <main>
                <aside>我是Slider侧边栏组件</aside>
                <section>我是Content组件</section>
            </main>
            <!-- 网页的底部 -->
            <footer>我是Footer组件</footer>
        </div>

我们只需要把每部分单独提出来变成组件然后使用路由进行显示。

  • 命名视图法
    <header><router-view></router-view></header> 组件 router-view 不加 name 属性在路由文件里面组件将会默认填充到这里,如果写了name属性那么相应的组件会映射到相应的name标签。
    我们现在把内容给提成四个组件:App.vue 配上路由就变成了
<!-- 网页头部 -->
<header><router-view></router-view></header>
<!-- 网页主体 -->
<main>
    <aside><router-view name="Slider"></router-view></aside>
    <section><router-view name="Content"></router-view></section>
</main>
<!-- 网页的底部 -->
<footer><router-view name="Footer"></router-view></footer>

然后我们只需要改变路由文件 router.js 文件,让不同的路由视图,显示显示对应的组件,只需要采用组件组的形式:

// 引入组件
import Header from "../components/Header.vue";
import Footer from "../components/Footer.vue";
import Slider from "../components/Slider.vue";
import Content from "../components/Content.vue";
// 暴露路由需要的数组
export default [
    { path: '/', redirect: "/index.html"},
    { path: '/index.html', components:{
        default:Header,
        Footer,
        Slider,
        Content
    }}
];
五、面包屑导航
面包屑导航

左边的 Slider 组件作用使用 HTML 进行路由跳转。代码为:

<template>
    <div>
        <aside>
            <div>
                <router-link class="cur" to="/">首页</router-link>
            </div>
            <div>
                <router-link class="cur" to="/index.html/yule">娱乐</router-link>
            </div>
            <div>
                <router-link class="cur" to="/index.html/news">新闻</router-link>
            </div>
        </aside>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style scoped>
.cur{
    display:inline-block;
    margin:20px 30px;
    text-decoration: none;
    color:#666;
    font-size: 16px;
}
</style>

然后去更改路由文件router.js:

{ 
    path: '/index.html',
    components : {
        default:Header,
        Footer,
        Slider,
        Content
    },
    meta:{chinese:"首页"},
    children : [
        { path : "news" ,component : News,meta:{chinese:"新闻"}},
        { path : "yule" ,component : Yule,meta:{chinese:"娱乐"}}
    ]
}

路由元信息(meta)?
定义路由的时候可以配置 meta 字段:一个路由匹配到的所有路由记录会暴露为 $route 对象的 $route.matched 数组。因此,我们需要遍历 $route.matched 来检查路由记录中的 meta 字段。

我们增加了两个子组件,news 和 yule(娱乐)。
再回到 Content 组件里面。进行配置。

<div>
    当前所在位置:
    <router-link to="/">首页</router-link> 
    {{$router.currentRoute.matched[1] ? "→ "+$router.currentRoute.matched[1].meta.chinese : null}}
    <router-view></router-view>
</div>

猛一看可能比较懵,我们来看看 $router 身上携带了什么参数,我们直接在生命周期beforeCreate 里面打印出来$router


就一个 API 有用:currentRoute,在把currentRoute 打印出来console.log(this.$router.currentRoute);


各个参数用处大约如下:

// 路由模式
console.log(this.$router.mode);
//当前路由的路径
console.log(this.$router.currentRoute.fullPath);
console.log(this.$router.currentRoute.path);
// URL路径的查询结果index.html?name=hero后面的东西{name: "hero"}
console.log(this.$router.currentRoute.query);
// 动态参数/:id,用户输入的id格式{id: "876543"}
console.log(this.$router.currentRoute.params);
// <router-view></router-view> 上的name属性
console.log(this.$router.currentRoute.name);
// meta自定义传入的参数,可以被我们使用
console.log(this.$router.currentRoute.meta.chinese);
// matched是一个数组,里面存放着满足当前路径的所有路由组件的路径
console.log(this.$router.currentRoute.matched);

需要注意的是用到路由参数的时候,可能会失效,例如: /index.html/news 导航到 /index.html/yule,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。重要的是生命周期钩子不会再被调用。在视图中如果使用路由参数(参数是对象或数组)插入,造成结果是视图不更新,所以声明式写法必须没有引用类型。除此之外解决办法就只能使用官方推荐的方法:

使用 watch (监测变化) $router 对象:

  watch: {
    '$router' (to, from) {
      // 对路由变化作出响应...
    }
  }

或者使用生命周期的 updated 来实现。亲测可行,Vue 就是乱,官网上讲不行,结果还是的以实际为准。

六、导航守卫(路由钩子)
1. 组件内的守卫
  • beforeRouteEnter 进入组件触发
  • beforeRouteUpdate (2.2 新增) 组件复用触发
  • beforeRouteLeave 离开组件触发

接下来以 XiXi 组件为例,修改代码加入路由进入和路由离开:

<template>
    <div>
        <h1>我是嘻嘻组件</h1>
    </div>
</template>

<script>
    export default {
        // 组件内的守卫
        // 导航离开该组件的对应路由时调用
        beforeRouteLeave (to, from , next) {
            // 这个离开守卫通常用来禁止用户在还未保存修改前突然离开
            const answer = window.confirm("你确定要离开当前页面!");
            if (answer) {
                next();//确定跳转
            } else {
                next(false);//导航可以通过 next(false) 来取消
            }
        },
        beforeRouteEnter (to, from, next) {
        // 进入该组件触发
        // 不!能!获取组件实例 `this`
        // 因为当守卫执行前,组件实例还没被创建
        const answer = window.confirm("你确定进入该页面!");
            if (answer) {
                next(vm => {
                // 通过 `vm` 访问本组件的实例
                console.log(vm)
                });//确定进入
            } else {
                next(false);//不进入
            }
        }
    }
</script>

<style lang="scss" scoped>

</style>

演示结果:

组件内的守卫

最最重要的就是beforeRouteLeave(将要离开当前路由),这个离开守卫通常用来禁止用户在还未保存修改前突然离开,简书写文章时未保存,或者平时填写一些表单退出时给出提示用到的就是这个。除了进入路由和离开路由还有个特殊的路由。为此特地新建一个组件 Foo,并配置路由为:{path : "/foo/:id",component : Foo}
Foo组件的内容为:

<template>
    <div>
        <h1>被复用的组件</h1>
    </div>
</template>

<script>
    export default {
        beforeRouteUpdate (to, from, next) {
        // 在当前路由改变,但是该组件被复用时调用
        // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
        // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
        // 可以访问组件实例 `this`
        const answer = window.confirm("你确定从/foo/1 => /foo/2 !");
            if (answer) {
                next(vm => {
                // 通过 `vm` 访问本组件的实例
                console.log(vm)
                });//确定进入
            } else {
                next(false);//不进入
            }
        }
    }
</script>

<style lang="scss" scoped>

</style>

在 APP 组件在添加两个跳转按钮:

<router-link to="/foo/1">/foo/1</router-link>
<router-link to="/foo/2">/foo/2</router-link>

查看演示效果:


beforeRouteUpdate

每个守卫方法接收三个参数:

  • to: Route: 即将要进入的目标 路由对象
  • from: Route:当前导航正要离开的路由。
  • next: Function:next(): 进行管道中的下一个钩子。next(false): 中断当前的导航。next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。
2. 路由独享的守卫

可以在路由配置上直接定义 beforeEnter 守卫:此处以 HaHa 组件为例

beforeEnter: (to, from, next) => {
    // 路由独享的守卫
    const answer = window.confirm("路由独享的守卫!");
    if (answer) {
        next();//确定进入
    } else {
        next(false);//不进入
    }
}

查看演示:


路由独享的守卫
3. 全局前置守卫

当一个导航触发时,全局前置守卫按照创建顺序调用。
官网代码案例:

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

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

太简单不演示了。

七、路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。

使用的形式其实就是组件的函数化:
以前:

component:About

现在:

component: () => import('./About.vue')

这是 Vue 的路由懒加载,还有 webpack 的懒加载方式,我同学项目中也有人用。基本没变,看了秒懂:

component: r => require.ensure([], () => r(require('../page/home')), 'home')

详情请去查看这个博客,里面最重要的话就是:

当然其实讲了那么长的 require.ensure并没有什么用,因为这个函数已经被 import() 取代了,但是考虑到之前的版本应该有很多人都是用 require.ensure 方法去加载的,所以还是讲一下,而且其实 import 的执行过程跟 require.ensure 是一样的,只不过用了更友好的语法而已,所以关于 import 的执行流程我也没啥好讲的了,感兴趣的人看一下两者的 API介绍就好了。

八、404 路由设置

常规参数只会匹配被/分隔的 URL 片段中的字符。如果想匹配任意路径,我们可以使用通配符 (*):

{
  // 会匹配所有路径
  path: '*'
}
{
  // 会匹配以 `/user-` 开头的任意路径
  path: '/user-*'
}

当使用通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。路由 { path: '*' } 通常用于客户端 404 错误。当使用一个通配符时,$router.currentRoute.params 内会自动添加一个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分:
例如输入错误的网址为:http://127.0.0.1:8080/#/index
$router.currentRoute.params.pathMatch 匹配输出为:/index

八、滚动行为

滚动行为:使用前端路由,当切换到新路由时,想要页面滚到自己指定位置,或者是保持原先的滚动位置,就像重新加载页面那样。

const router = new VueRouter({
   routes,
   scrollBehavior (to, from, savedPosition) {
    // return 期望滚动到哪个的位置切换滚动到
    return { x: 0, y: 1000 }
  }
});
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容