### 路由

    前端路由:
        根据用户请求不同的url来展示不同的页面或者数据,前端路由是不会涉及到后端请求的,以及页面不会进行刷新。用户体验比较好,一般用来做单页面开发(SPA)。前端路由底层原理:hashchange和H5的history API中的popState和replaceState来实现。

    后端路由:
        根据用户请求的路径来返回不同的数据或页面,后端路由一般情况下都是用来做接口的,通过ajax请求的路径来返回对应的数据。

    使用流程:
        ①安装路由:npm install vue-router -S
        ②创建router/index.js文件(将vue和vue-router引入,组件引入,创建路由实例,将路由表routes【数组】和每个页面配置好):

            import Vue from "vue";
            import VueRouter from "vue-router";
            import Movie from "../pages/movie";
            import Cinema from "../pages/cinema";
            import Mine from "../pages/mine";
            Vue.use(VueRouter);

            let router=new VueRouter({
                mode:"hash",
                routes:[
                    {
                        path:"/movie",
                        component:Movie
                    },
                    {
                        path:"/cinema",
                        component:Cinema
                    },
                    {
                        path:"/mine",
                        component:Mine
                    }
                ]
            });

            export default router;

        ③main.js中将抛出的router引入,并挂载到vue实例中:

            import router from "./router";

            new Vue({
                router,
                render:h=>h(App)
            }).$mount("#app");

        ④路由显示:在根组件App.vue中用<router-view></router-view>开辟一块空间用来显示不同的组件。此时在地址栏地址后面输入地址可以进入到对应的页面
        ⑤路由跳转:新建components/tabBar/index.vue,用<router-link></router-link> to属性将路径赋予到每个按钮

            <router-link v-for="(item,index) in tabs" :key="index" :to="item.path">
                <i class="iconfont" v-html="item.icon"></i>
                <span>{{item.text}}</span>
            </router-link>

### 路由常用的配置项

    mode—-路由的形式,默认是hash,另一种是history
    routes—-路由表(数组)
        path—-匹配路由的路径
        component—-路由匹配成功后显示对应的组件
        redirect—-路由重定向
        name—-命名路由
        props—-路由解耦
        children—-路由嵌套
        meta—-路由元信息–>路由携带的一些独有信息
        immediate:true—-页面首次加载的时候做一次监听(处理动态路由传参时,动态路由参数发生改变而页面没有更新问题时,watch监听$route时用到了一次)

### vue路由的内置组件

    当我们将VueRouter挂载到vm实例身上时会多出2个内置组件
 
    router-view—-渲染路由匹配的组件
    router-link—-路由的跳转(底层原理是a标签,它属于内置组件自身会有一个to属性,这个属性是跳转的地址,tag属性:指定router-link渲染成哪个标签)

### vue中路由跳转的方式有哪些

    1、<a></a>
    2、router-link
    3、编程式导航
 

### 路由传值

    概念:不同的页面之间需要相互传递数据来进行数据的请求或进行数据的渲染,因此需要路由传值。
 
    从movie跳转至detail:
 
    1、动态路由传值
        ①在定义路由的时候在path路由处通过 /:属性 的方式来定义传递参数的属性(router/index.js):

            {
                path:"/detail/:goodsName/:goodsPrice",
                component:Detail
            }

        ②在路由跳转的时候在路径跳转的地方通过 /值 的方式来进行传值,此时在movie页面点击跳到detail页面的时候,地址栏上会有参数(movie/index.vue):

            <li v-for="(item,index) in goods" :key="index">
                <router-link :to="'/detail'+'/'+item.name+'/'+item.price">{{item.name}}</router-link>
            </li>

        ③在需要接收参数的页面通过 this.$route.params 的方式来进行接收(detail/index.vue):

            created() {
                let {goodsName,goodsPrice}=this.$route.params;
                this.goodsName=goodsName;
                this.goodsPrice=goodsPrice;
            }

    
 2、query传值(通过url地址进行拼接数据传递给另一个路由)
        ①路由表中为detail路由设置name属性(router/index.js):

            {
                name:"detail",
                path:"/detail",
                component:Detail
            }

        ②在路由跳转的时候通过 路径?key=value&key=value 的方式将数据进行传值(movie/index.vue):

            <li v-for="(item,index) in goods" :key="index">
                <router-link :to="{name:'detail',query:{goodsName:item.name,goodsPrice:item.price}}">{{item.name}}</router-link>
            </li>

        ③在需要接收参数的页面通过 this.$router.query 的方式进行接收(detail/index.vue):

            created() {
                let {goodsName,goodsPrice}=this.$route.query;
                this.goodsName=goodsName;
                this.goodsPrice=goodsPrice;
            }

  3、路由解耦(在动态路由的基础上,利用props传值)

        ①在定义路由的时候在path路由处通过 /:属性 的方式来定义传递参数的属性,在定义路由的时候添加一个props属性为true(router/index.js):

            {
                name:"detail",
                path:"/detail/:goodsName/:goodsPrice",
                component:Detail,
                props:true
            }

        ②在路由跳转的时候在路劲跳转的地方通过 /值 的方式来进行传值(movie/index.vue):

            <li v-for="(item,index) in goods" :key="index">
                <router-link :to="{name:'detail',params:{goodsName:item.name,goodsPrice:item.price}}">{{item.name}}</router-link>
            </li>

        ③在需要接收参数的页面通过props来进行接收,此时不需要data中的goodsName和goodsPrice(detail/index.vue):

            export default {
                name:"Detail",
                props:["goodsName","goodsPrice"],
            }

 
    4、编程式导航
        ①编程式导航是js的方式进行跳转,所以给li绑定点击事件(movie/index.vue):

            <li v-for="(item,index) in goods" :key="index" @click="push(item.name,item.price)">
                {{item.name}}
            </li>

        ②定义push方法,这里是将name和price直接在地址栏后传递过去(movie/index.vue):

            methods: {
                push(name,price){
                    this.$router.push("/detail/"+name+"/"+price);
                }
            }

        ③在detail/index.vue中用this.$route.params接收参数(detail/index.vue):

            created() {
                let {goodsName,goodsPrice}=this.$route.params;
                this.goodsName=goodsName;
                this.goodsPrice=goodsPrice;
            }

        ④这种直接将参数接在地址栏后面,动态路由也是这样,所以它们的接收方式都可以用props来接收(这种接收方式相当于父传子,只是它多了一步【先在路由表中设置当前路由props:true】,然后在接收的页面通过props接收值):

            props:["goodsName","goodsPrice"]

    动态路由和query路由传值之间的区别?
        动态路由传值的时候,值是必须传递的;
        query传值的时候,值不是必须传递的。
 

### 编程式导航(通过js的方式进行路由的跳转)

    this.$router.push()
    this.$router.go()
    this.$router.back()
    this.$router.forward()
    this.$router.replace()
 
    这里的前进后退方式能否执行取决于浏览器中是否有浏览历史记录。
 

### 嵌套路由 children

    步骤:
        ①路由表中设置movie的二级路由:

            {
                name:"movie",
                path:"/movie",
                component:Movie,
                children:[
                    {path:"hotMovie",component:HotMovie},
                    {path:"comingMovie",component:ComingMovie}
                ]
            }

        ②movie页面页面中点击去到二级路由(detail/index.vue):

            <div id="nav">
                <div>北京</div>
                <div>
                    <router-link to="/movie/hotMovie" tag="div">正在热映</router-link>
                    <router-link to="/movie/comingMovie" tag="div">即将上映</router-link>
                </div>
                <div>搜索</div>
            </div>
            <router-view></router-view>

### 路由元信息 meta

    步骤:
        (1)设置每个页面的title
            ①设置路由元信息meta(注意children中path属性值前面没有 /)

                routes:[
                    {
                        name:"movie",
                        path:"/movie",
                        component:Movie,
                        children:[
                            {path:"hotMovie",component:HotMovie},
                            {path:"comingMovie",component:ComingMovie}
                        ],
                        meta:{title:"电影",flag:true}
                    },
                    {
                        path:"/cinema",
                        component:Cinema,
                        meta:{title:"影院",flag:true}
                    },
                    {
                        path:"/mine",
                        component:Mine,
                        meta:{title:"我的",flag:false}
                    }
                ]

            ②在movie、cinema、mime中分别设置title:

                created() {
                    document.title=this.$route.meta.title;
                }

            ③可以通过beforeEach()设置title,不用在每个页面重复操作(router/index.js):

                router.beforeEach((to,from,next)=>{
                    document.title=to.meta.title;
                })

        (2)通过路由元信息设置,当切换至mine页面时,tabBar隐藏
            ①设置路由元信息meta中flag值默认都为true,在mine中flag值为false(同上)
            ②在根组件App.vue中通过this.$route.meta拿到路由元信息:

                computed: {
                    flag(){
                        return this.$route.meta.flag;
                    }
                }

            ③将flag值设置给TabBar组件:

                <TabBar v-if="flag"></TabBar>

### 路由的生命周期(路由钩子函数、路由守卫)

    路由守卫:路由守卫就是路由生命周期,它是路由跳转前后的一些验证,分为全局路由守卫和局部路由守卫。
 
    全局路由守卫:beforeEach()
        给所有的路由添加的守卫
 
    注意:局部的路由守卫是在组件内部进行使用,全局路由守卫是在路由的配置项中使用的。
 
    router/index.js:(beforeEach()一般用来替代beforeRouterEnter(),做登录验证,不用每个组件都写)

        router.beforeEach((to,from,next)=>{
            document.title=to.meta.title;
            if(to.meta.requireAuth){
                let token=localStorage.getItem("token");
                if(token){
                    // 当Application中localStorage中有token值时走这里,可以手动添加测试
                    next();
                }else{
                    next({name:"login",params:{toPath:to.path}});
                }
            }else{
                next();
            }
        });

    
 局部路由守卫:
        1、beforeRouteEnter()—-路由进入时
            参数:
                to—-去哪里(对象)
                from—-从哪里来(对象)
                next—-next()函数必须调用一下,否则路由所对应的组件不会渲染
            使用场景:
                1、验证用户是否登录
                2、验证用户的vip是否到期/剩余时间
                3、用户权限
                4、消息提示

            Q:在beforeRouteEnter()中能否访问到this指向?

                默认是不能。如果想要访问到this,需要在next()函数中的参数中获取,这个参数可以是一个函数,这个函数的参数是vm的实例。
 
            步骤:(跳转至mine页面前判断是不是登录状态,如果不是先去login页面)
                ①mine页面中添加beforeRouteEnter():

                    beforeRouteEnter (to, from, next) {
                        if(to.meta.requireAuth){
                            let token=localStorage.getItem("token");
                            if(token){
                                next();
                            }else{
                                // 跳转到登录页面
                                // this.$router.push("/login");// 这里的this是拿不到的,因为现在还没有进入到该组件
                                next((vm)=>{
                                    // 参数vm就是this
                                    vm.$router.push({name:"login",params:{toPath:to.path}});
                                });
                            }
                        }else{
                            next();
                        }
                    }

                    注意:
                        1、beforeRouteEnter()中没有this,如果非要拿,可以通过 next((vm)=>{}); 中的vm就是this。
                        2、params传值时,path:”/login” 改为 name:”login”
                ②login页面中登录成功后,跳转至用户之前想要跳转的页面:

                    <button @click="toPathHandler">登录成功</button>
                    created() {
                        this.toPath=this.$route.params.toPath;
                    },
                    methods: {
                        toPathHandler(){
                            // 登录成功后,跳转至用户之前想要跳转的页面
                            this.$router.push(this.toPath);
                        }
                    }

        2、beforeRouteUpdate()—-路由更新时
            当路由更新的时候会触发的一个守卫,一般情况下在组件中没有经历创建和销毁,但是路由发生了改变的时候需要执行的生命周期。
 
            这啥意思呢?举个梨子:当使用动态路由传参时(:/属性),如果不用beforeRouteUpdate()会发生一个情况:路由已经切换过来了,但是页面没有触发更新。
 
            步骤:(cinema跳转至goodsDetail,goodsDetail中做二级路由切换)
                ①路由表中将goodsDetail设置为cinema的二级路由,通过动态路由传参:

                    {
                        path:"/cinema",
                        component:Cinema,
                        meta:{
                            title:"影院",
                            flag:true
                        },
                        children:[
                            {name:"goodsDetail",path:"goodsDetail/:name",component:GoodsDetail}
                        ]
                    }

                ②通过params传值将name传到goodsDetail(cinema/index.vue):

                    <router-link tag="li"
                        :to="{name:'goodsDetail',params:{name:item.name}}"
                        v-for="(item,index) in goods" :key="index"
                    >
                        {{item.name}}
                    </router-link>
                    <router-view></router-view>

                ③如果只写一个created接收name值,那么只有在第一次进来的时候会显示对应的值,后面再进行点击,路由会跟着切换,但是组件没有经历创建与销毁,所以created只执行了第一次,所以页面参数传递不过来,此时需要用到beforeRouteUpdate()(goodsDetail/index.vue):

                    export default {
                        name:"goodsDetail",
                        created() {
                            let {name}=this.$route.params;
                            this.goodsName=name;
                        },
                        beforeRouteUpdate(to,from,next){
                            let {name}=to.params;
                            this.goodsName=name;
                            next();
                        },
                        data() {
                            return {
                                goodsName:""
                            }
                        }
                    }

                ④如果不用beforeRouteUpdate(),用watch监听 $route 也可以达到同样效果:

                    export default {
                        name:"goodsDetail",
                        watch: {
                            "$route":{
                                handler(to,from){
                                    let {name}=to.params;
                                    this.goodsName=name;
                                },
                                immediate:true
                            }
                        },
                        data() {
                            return {
                                goodsName:""
                            }
                        }
                    }

        3、beforeRouteLeave()—-理由离开时
            使用场景:
                1、用户未支付
                2、答题系统
                3、记录历史记录
                4、注销、切换账号

            beforeRouteLeave (to, from, next) {
                let flag=confirm("您确定放弃支付吗?");
                if(flag) next();
            }

### 路由懒加载

    npm run build之后dist文件中会有一个 js/app.js 文件,这里包含了src下所有的内容,app.js的体积比较大。
 
    1、通过ES6的import方式引入(常用)

        import Movie from "../pages/movie";
        component:Movie

        改为

     component:()=>import("../pages/movie")

    2、require进行引入

        import Movie from "../pages/movie";
        component:Movie

        替换为

        component:(resolve)=>require(["../pages/movie"],resolve)

        此时,npm run build后dist/js文件夹中除了app.js还有 1.js、2.js、3.js …然后打开dist的index.html时会按需加载需要的js文件。
 
    3、骨架屏加载
 

### vue/cli

    安装:npm install -g @vue/cli
        完了之后可以使用 vue 命令会打印以下内容:
            Commands:
                create [options] <app-name>                create a new project powered by vue-cli-service
        说明可以使用【vue create 项目名称】创建一个项目,这个项目通过vue/cli创建的。
    创建项目:vue create 项目名称
 
    步骤:
        ①day08文件夹右键在终端打开,vue create myapp
        ②Manually select features
        ③Babel、Router、vuex
        ④提示Use history mode for router?—-n
        ⑤In package.json
        ⑥Save this as a preset for future projects?—-n
        ⑦cd myapp
        ⑧npm run serve

### webpack配置别名alias

    步骤:
        ①base.config.js中配置alias对象(配置完重启):

            alias:{
                "@":path.join(__dirname,"../src"),
                "pages":path.join(__dirname,"../src/pages"),
                "components":path.join(__dirname,"../src/components")
            }

        ②在需要使用的时候,用 @ 替代src文件夹:
            例如:

                import Movie from "../pages/movie"; 可以改为 import Movie from "@/pages/movie";
                import ComingMovie from '../components/comingMovie'; 可以改为 import ComingMovie from 'components/comingMovie';

### vue/cli中配置别名alias

    步骤:
        ①myapp下新建vue.config.js文件:

            const path=require("path");
            module.exports={
                configureWebpack:{
                    resolve:{
                        alias:{
                            "@":path.join(__dirname,"./src"),
                            "views":path.join(__dirname,"./src/views")
                        }
                    }
                }
            }

            其中,

                configureWebpack:{
                    resolve:{
                        alias:{
                            "@":path.join(__dirname,"./src"),
                            "views":path.join(__dirname,"./src/views")
                        }
                    }
                }

            可以写成:

                chainWebpack:(config)=>{
                    config.resolve.alias
                    .set("@",path.join(__dirname,"./src"))
                    .set("views",path.join(__dirname,"./src/views"))
                }

        ②router/index.js中:

            import Home from '../views/Home.vue' 可以改为 import Home from '@/views/Home.vue'
            component: () => import('../views/About.vue') 可以改为 component: () => import('views/About.vue')

### vue-router底层原理

    步骤:
        ①router/index.js中使用自己封装的路由替代vue-router:

            import Router from "vue-router"; 替换为 import Router from "../wql-router";

        ②src下新建wql-router/index.js:

            let Vue;
            class WqlRouter{
                // 接收Vue.use()传过来的Vue
                static install(_Vue){
                    Vue=_Vue;
                }
                constructor(options){
                    this.$options=options;// 接收传递过来的配置项
                    this.routeMap={};// 定义解析routes的对象
                    this.app=new Vue({// 定义路由的地址
                        data:{
                            curr:"/"
                        }
                    });
                    this.init();
                }
                init(){
                    this.bindEvent();// 绑定路由事件
                    this.createRouteMap();// 创建路由对象
                    this.createComponents();// 创建组件
                }
                bindEvent(){
                    window.addEventListener("load",this.handleChange.bind(this));
                    window.addEventListener("hashchange",this.handleChange.bind(this));// hash路由发生改变的时候会触发this.handleChange()
                }
                handleChange(){
                    let hash=this.getHash()||"/";// 更新视图
                    this.app.curr=hash;
                }
                getHash(){
                    return location.hash.slice(1);
                }
                createRouteMap(){
                    this.$options.routes.forEach((item)=>{
                        this.routeMap[item.path]=item;
                    });
                }
                createComponents(){
                    Vue.component("router-view",{
                        render:h=>{
                            let component=this.routeMap[this.app.curr].component;
                            return h(component);
                        }
                    });
                    Vue.component("router-link",{
                        props:{
                            to:{
                                type:String,
                                required:true
                            }
                        },
                        render:function(h){
                            return h("a",{
                                attrs:{
                                    href:`#${this.to}`
                                }
                            },this.$slots.default);
                        }
                    });
                }
            }

            export default WqlRouter;

        注意:
            1、这个只是实现了路由的展示和跳转,很多功能还没有完善,比如<router-link>标签不能设置tag属性。
            2、主要做了三件事:绑定路由事件,创建路由对象,创建组件。通过window.addEventListener()绑定load事件和hashchange事件,首次加载和当路由发生更新的时候,更新视图。构造函数中定义解析routes的对象routeMap,构造函数的options参数就是router/index.js中的mode和routes,通过遍历将接收到的path赋给routeMap,此步骤为创建路由对象。通过Vue.component创建组件,此步骤注意箭头函数会改变this指向,创建<router-link>组件的时候render渲染时用的是ES5的函数。

Question:

    1、单页面开发的缺点:
        1、不利于SEO优化—-解决:服务端渲染 (ssr nuxt)
        2、加载速度慢—-解决:路由懒加载
 
    2、路由报错:
        [Vue warn]: Unknown custom element: <router-link> – did you register the component correctly? For recursive components, make sure to provide the “name” option.
 
        分析:[Vue warn]:未知的自定义元素:<router link>-是否正确注册了组件?对于递归组件,请确保提供“name”选项。
 
        App.vue中使用了<router-link></router-link>组件,而此时router还没有进行注册。
 
        解决:
            ①router/index.js使用路由:

                import Vue from "vue";
                import Router from "vue-router";
                Vue.use(Router);

                import About from "../views/About.vue";
                import Home from "../views/Home.vue";
                const router=new Router({
                    mode:"hash",
                    routes:[
                        {path:"/about",component:About},
                        {path:"/home",component:Home}
                    ]
                });

                export default router;

            ②main.js中引入并注册:

                import router from './router'


                new Vue({
                    router,
                    render: h => h(App)
                }).$mount('#app')