单页应用:一个页面即一个简单应用,组件化开发

传统的页面调转是浏览器请求新页面,单页是把原本的多个页面以组件的形式集成在一个页面中,页面跳转时由vue路由到目标页面,因为目标页面是当前页面的一部分,切换到指定部分即可,浏览器不用发起新请求。
 

单页、路由的基本使用

#下载vue的路由插件
npm install vue-router
<div id="app"></div>

<!-- 引入vue.js -->
<script src="js/vue.js"></script> 

<!-- 引入vue的路由插件。上线时均要换为min版 -->
<script src="js/vue-router.js"></script>

<script>
    // 首页
    var Index={
        template:`
            <div>
                <p>this is the index page</p>
                <p><a href="#/login">login</a></p>   <!-- 注意url写法,#/开头 -->
                <p><a href="#/register">register</a></p>
            </div>
        `,
    }
    

    // 登录页面
    var Login={
        template:`
            <div>
                <p>this is the login page</p>
                <p><a href="#/index">index</a></p>
                <p><a href="#/register">register</a></p>
            </div>
        `
    }
    

    // 注册页面
    var Register={
        template:`
            <div>
                <p>this is the register page</p>
                <p><a href="#/index">index</a></p>
                <p><a href="#/login">login</a></p>
            </div>
        `
    }


    // 启用vue路由
    Vue.use(VueRouter);
    

    // 创建路由对象
    var router=new VueRouter({  //参数是对象
        routes:[  //配置路由规则,对象数组形式
            {path:'',name:'index',component:Index},  //path指定映射地址,注意不带#,component指定组件。path=""表示该页面组件是默认显示的页面
            {path:'/login',name:'login',component:Login},
            {path:'/register',name:'register',component:Register},
        ]
    });

    
    new Vue({
        el: '#app',
        router: router,  //设置路由对象。因为我们使用的路由对象和key名称相同,也可以像html属性一样简写为一个:router
        template:`
            <div>
                <!--路由页面只是当前页面的一部分,当前页面可以写一些其他内容,写的内容是所有路由页面-->
                <p>this is common area</p>  <!-- 公共部分,页面都有的部分 -->
                <router-view></router-view>  <!-- 目标页面。路由到哪个页面,就用对应的组件替换此元素 -->
            </div>
        `,
    })
</script>   

运行,假设这个html页面的地址是:xxx.html#/
则首页的地址是:xxx.html#/
登录页面的地址是:xxx.html#/login
注册页面的地址是:xxx.html#/register

表面上是3个页面,但实际上只有xxx.html一个页面,这3个页面都在xxx.html中。<router-view>是一个容器,用来容纳目标页面。
 

一个组件即一个页面,组件往往很大,通常把每个组件写在单独的js文件中,在xxx.html中使用<script src="">引入

 

路由模式

有2种路由模式

1、哈希模式(默认)
监听地址栏url的hashchange事件,url改变时获取新url的哈希值,与VueRouter对象中配置的路由规则比较,找到新url对应的组件,用对应的组件替换<router-view>。(hash模式的原理)
 

2、history模式
需要后台配合,把接口都打到前端打包好的单页上(xxx.html)

 

路由跳转

实现路由跳转有3种方式

  • <a>链接
  • <router-link>标签
  • $router对象
<a href="#/login">login</a>  <!-- 要带# -->

<router-link to="/login">login</router-link>  <!-- 不带# -->

<script>
    new Vue({
        el: "#app",
        router,
        template: `
            <div>
                <button @click="goLogin">login</button>
                <router-view></router-view> 
            </div>
        `,
        methods: {
            goLogin() {
                this.$router.push({ path: "/login" });  //不带#
            },
        },
    });
</script>

<router-link>、$router都是vue路由中的,都用路由了,url中会自动加#号,我们写路径的时候不需要加#号。

<a>是html的标签,url中不会自动加#号,需要我们自己加#号。
 

$router常用的方法

  • push()  跳转到指定页面,会往history中插入记录
  • replace()  和push()差不多,只是不会往history中插入记录
  • go(-1)  跳转到history中的上一条记录,相当于点击浏览器的后退箭头
  • forward(1)  跳转到history中的下一条记录,相当于点击浏览器的前进箭头

$router有个兄弟对象$route,$route常用的属性:path、query、params
 

在html中,<router-link>用得多;在js代码中,用$router

 

路由传参

<a>链接方式

<a href="#/xxx?username=chy&age=20">index</a>  <!-- 传参 -->

<script>
    // 页面组件
    var Xxx={
        template:`
            <div>
                <p>{{this.$route.query.username}}  {{this.$route.query.age}}</p>
                <p>{{username}} {{age}}</p>
            </div>
        `,
        data(){
            return{
                username:'',
                age:''
            }
        },
        created(){  //路由到此组件|页面时,会新建此组件的实例,只能在created()中获取传递的数据
            this.username=this.$route.query.username;  //query是查询字符串对象,<a>链接传递的参数只能用query来取
            this.age=this.$route.query.age;
        }
    }
</script>

如果只在创建组件时使用一次,直接用组件的$route.query取值即可;如果后续还要使用,则需要用变量来保存参数

 

<router-link>、$router对象方式

都可以用query或者params传递参数

<!-- query,get请求 -->
<router-link :to="{name:'xxx',query:{username:'chy',age:20}}">xxx</router-link>  

<!-- params,post请求 -->
<router-link :to="{name:'xxx',params:{username:'chy',age:20}}">xxx</router-link> 
// $router对象方式,query、params
this.$router.push({name:'xxx',query:{username:'chy',age:20}});
this.$router.push({name:'xxx',params:{username:'chy',age:20}});

都可以用路由配置中的name或者path指定目标组件。

取值方式和<a>差不多,$route.query、$route.params,什么方式传就用什么方式取
 

如果使用params方式,路由配置的url可以写成restful风格

var router = new VueRouter({
    routes: [
        {path: "/xxx/:username", name: "xxx", component: Xxx},  //:参数名  可以获取指定参数的值
    ],
});

如果url写成restful风格,path就不是固定的,传递参数时只能用name指定目标组件,不能用path
 

常使用ES6的解构赋值处理参数

let {username,password}=req.query;

 

路由传参 参数不刷新的问题

以上传参方式都存在一个问题:多次传参,如果直接使用参数,可以获取到本次的参数;如果先把参数赋给变量,使用变量,则变量的值永远是第一次传递的参数值,即参数不刷新。
 
原因:把变量赋值写在created()中,这个钩子函数只在组件创建时执行1次,vue路由会复用组件,后续再向该组件传递参数时,不会调用重新创建该组件,也就不会执行created()重新赋值。
 
如果参数只在页面加载时读取一次,可以在create()中直接取,后续还要使用这个值的话,可以用变量来保存;如果要多次获取不同的路由参数,后续的路由参数不会刷新,如何解决参数不刷新问题?2种方案
 

1、使用 :key 标识路由跳转

<router-view :key="$route.fullPath"></router-view> 

fullPath是完整的url,如果是query方式传参,fullPath中包含了参数;如果是params方式传参,参数写成restful风格(用一个唯一标识参数表的字段,比如username、uid),fullPath中也包含了参数。
 
参数不同时 => $route.fullPath不同 => key不同,key不同时会重新创建组件,执行created()重新获取参数给变量赋值。
 
核心思路是:参数不同时key也要不同,key不同就会重新创建组件。key不一定要用$route.fullPath,常用的还有

$route.query|params.username|uid  //能唯一标识参数表的参数  
new Date().getTime()  //时间戳(毫秒级)

此种方式,参数不同时会重新创建组件,往往会造成组件的频繁创建、销毁,开销很大,不建议使用。

 

2、使用数据监听(推荐)

在目标组件中监听$route.query或者$route.params,在数据监听中进行赋值,不需要使用created()

watch:{  //监听$route.query|params,当然直接监听$route也行
    '$route.params'(){ //有.号,要引起来,不然识别不了
        this.username=this.$route.params.username;  //参数变化时就重新获取参数
        this.age=this.$route.params.age;
    }
},    

路由到同一个页面时会复用组件,不重新创建组件,开销小,推荐。

最好是抽象出一个处理参数的方法,初次路由到该页面时在create()中调用1次,后续再次路由到该页面时watch中调用。需要注意监听的是$route.query|params,只要其中某个参数有变化,就会触发事件。

 

嵌套路由

路由配置中嵌套其它的路由配置

<div id="app"></div>

<script>
    // 导航
    var Nav={
        template:`
            <div>
                <router-link :to="{name:'blog.index'}">首页</router-link>
                <router-link :to="{name:'blog.article'}">文章</router-link>
                <router-link :to="{name:'blog.idea'}">想法</router-link>
                <router-link :to="{name:'blog.message'}">留言</router-link>

                <router-view></router-view>  <!-- 留坑,点击上面4个路由链接,会在此处显示对应的页面 -->
            </div>
        `,
    }
    

    // 首页
    var Index={
        template:`
            <div>
                <p>这是首页部分</p>
            </div>
        `,
    }
    

    // 文章
    var Article={
        template:`
            <div>
                <p>这是文章部分</p>
            </div>
        `,
    }
    

    // 想法
    var Idea={
        template:`
            <div>
                <p>这是想法部分</p>
            </div>
        `,
    }


    // 留言
    var Message={
        template:`
            <div>
                <p>这是留言部分</p>
            </div>
        `,
    }


    // 启用路由
    Vue.use(VueRouter);


    // 创建路由对象
    var router=new VueRouter({
        routes:[ 
            {path:'/',name:'blog',component:Nav,children:[   //path直接映射到根路径(当前html页面下)下,children设置要嵌套的路由
                {path:'',name:'blog.index',component:Index},  //默认页面
                {path:'/article',name:'blog.article',component:Article},  //嵌套路由的name一般要带上外部的路由容器,一看就知道关系
                {path:'/idea',name:'blog.idea',component:Idea},
                {path:'/message',name:'blog.message',component:Message},
            ]},
        ]
    });

        
    new Vue({
        el:'#app',
        router,  //使用路由
        template:`
            <div>
                <router-view></router-view>  <!-- 留给nav的坑位 -->
            </div>
        `,
    })

</script> 

 
嵌套路由的path可以映射到根路径下,也可以使用相对路径,相对于外部路由

{path:'/blog',name:'blog',component:Nav,children:[   //xxx.html#/blog
    {path:'',name:'blog.index',component:Index},  //默认页面
    {path:'/article',name:'blog.article',component:Article},  // /开头是映射到根路径下,xxx.html#/article
    {path:'idea',name:'blog.idea',component:Idea},  // 相对于外部路由,xxx.html#/blog/idea
    {path:'message',name:'blog.message',component:Message},
]},

 

路由守卫

一个页面路由到另一个页面,路由到目标页面之前可以用路由守卫做一些处理,比如登录检测、权限检查,不满足就给出提示信息,不路由到目标页面。

现在的项目基本都是mvc模式,登录检测、权限检查等请求预处理在后台已经做了,前端没必要用路由守卫,此处简单提一下

//写在html(单页容器)的Vue对象的mounted()中
mounted(){
    router.beforeEach( (to,from)=>{   //路由守卫,校验每一个路由跳转。此处使用es6的箭头函数,参数:目标页面、来源页面
        //......
    });
},