Bootstrap

Vue学习笔记 二

4、Vue基础扩展

4.1 插槽

  • 组件的最大特性就是复用性,而用好插槽能大大提高组件的可复用能力
  • 在Vue中插槽是很重要的存在,通过插槽,我们可以把父组件中指定的DOM作用到子组件的任意位置,后面我们坐项目用到的组件库比如element-ui,vant-ui都频繁用到的插槽,Vue的插槽主要有匿名插槽,具名插槽,作用域插槽三种,下面我们分别来认识一下他们。
4.1.1 匿名插槽★★★★

故名思义就是没有名字的插槽,只需要在子组件中使用<slot></slot>引入即可

我们来看一下案例哈,如何引入插槽的:

<div id="app">
        <!-- 这里的所有组件标签中嵌套的内容会替换掉slot  如果不传值 则使用 slot 中的默认值  -->
        <alert-box>有bug发生</alert-box>
        <alert-box>有一个警告</alert-box>
        <alert-box></alert-box>
</div>
<script type="text/javascript">
        /*
          组件插槽:父组件向子组件传递内容
        */
        Vue.component('alert-box', {
            template: `
            <div>
              <strong>ERROR:</strong>
                        # 当组件渲染的时候,这个 <slot> 元素将会被替换为“组件标签中嵌套的内容”。
                        # 插槽内可以包含任何模板代码,包括 HTML
              <slot>默认内容</slot>
            </div>
          `
        });
        var vm = new Vue({
            el: '#app',
            data: {

            }
        });
</script>
4.1.2 具名插槽★★★★
  • 具有名字的插槽就是具名插槽
  • 使用 <slot> 中的 “name” 属性绑定元素,name就是插槽的名字
<div id="app">
        <base-layout>
            <!-- 2、 通过slot属性来指定, 这个slot的值必须和下面slot组件得name值对应上
                    如果没有匹配到 则放到匿名的插槽中   -->
            <p slot='header'>标题信息</p>
            <p>主要内容1</p>
            <p>主要内容2</p>
            <p slot='footer'>底部信息信息</p>
        </base-layout>

        <base-layout>
            <!-- 注意点:template临时的包裹标签最终不会渲染到页面上     -->
            <template slot='header'>
                <p>标题信息1</p>
                <p>标题信息2</p>
            </template>
            <p>主要内容1</p>
            <p>主要内容2</p>
            <template slot='footer'>
                <p>底部信息信息1</p>
                <p>底部信息信息2</p>
            </template>
        </base-layout>
</div>
<script type="text/javascript">
        /*
          具名插槽
        */
        Vue.component('base-layout', {
            template: `
            <div>
              <header>
                ###	1、 使用 <slot> 中的 "name" 属性绑定元素 指定当前插槽的名字
                <slot name='header'></slot>
              </header>
              <main>
                <slot></slot>
              </main>
              <footer>
                ###  注意点: 
                ###  具名插槽的渲染顺序,完全取决于模板,而不是取决于父组件中元素的顺序
                <slot name='footer'></slot>
              </footer>
            </div>
          `
        });
        var vm = new Vue({
            el: '#app',
            data: {

            }
        });
</script>
4.1.3 作用域插槽★★★★
  • 可以让父组件对子组件的内容进行加工处理
  • 既可以复用子组件的slot,又可以使slot内容不一致
    • 父组件中使用slot-scope绑定一个属性
    • 子组件中给<slot>标签绑定一个自定义属性,可以传递子组件的内容
    • 父组件通过slot-scope接受子组件传递过来的值即可
<div id="app">
        <!-- 
            1、当我们希望li 的样式由外部使用组件的地方定义,因为可能有多种地方要使用						该组件,但样式希望不一样 这个时候我们需要使用作用域插槽   
        -->
        <fruit-list :list='list'>
            <!-- 2、 父组件中使用了<template>元素,而且包含scope="slotProps",
                slotProps在这里只是临时变量   
            --->
            <template slot-scope='slotProps'>
                <strong v-if='slotProps.info.id==3' class="current">
                    {{slotProps.info.name}}
                </strong>
                <span v-else>{{slotProps.info.name}}</span>
            </template>
        </fruit-list>
</div>
<script type="text/javascript">
        /*
          作用域插槽
        */
        Vue.component('fruit-list', {
            props: ['list'],
            template: `
            <div>
              <li :key='item.id' v-for='item in list'>
              //3、 在子组件模板中,<slot>元素上有一个类似props传递数据给组										件的写法 msg="xxx",
              //插槽可以提供一个默认内容,如果如果父组件没有为这个插槽提供了内										容,会显示默认的内容。
                如果父组件为这个插槽提供了内容,则默认的内容会被替换掉
                <slot :info='item'>{{item.name}}</slot>
              </li>
            </div>
          `
        });
        var vm = new Vue({
            el: '#app',
            data: {
                list: [{
                    id: 1,
                    name: 'apple'
                }, {
                    id: 2,
                    name: 'orange'
                }, {
                    id: 3,
                    name: 'banana'
                }]
            }
        });
</script>

4.2 案例讲解-购物车

前面我们学了组件,我们也知道Vue的核心是组件系统和数据驱动,下面我们来做一个购物车的案例,巩固深化一下我们所学的理论知识

4.2.1 组件化布局组件拆分★★★★
  • 把静态页面转换为组件化模式
  • 把组件渲染到页面上显示效果如下所示

代码效果如下:

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
<style>
        #app {
            width: 600px;
            margin-top: 30px;
        }
        .header {
            width: 100%;
            height: 40px;
            line-height: 40px;
            text-align: center;
            background-color: aquamarine;
        }

        .footer {
            width: 100%;
            height: 50px;
            line-height: 50px;
            background-color: azure;
            text-align: right;
        }

        .footer>span {
            padding: 0px 15px;
            font-weight: bold;
            font-size: 15px;
            color: brown;
        }
</style>

上面写了点简单的样式,效果如上图所示

<div id="app" class="container">
    <my-cart></my-cart>
</div>
<script>
        //1、 把静态页面转换成组件化模式
        //1.1  标题组件 
        var CartTitle = {
            template: `
                <div class="header">我的商品</div>
            `
        }
        //1.2  商品列表组件
        var CartList = {
            template: `
            <table class="table table-bordered">
                <tr>
                    <td><img src="media/day06-a.jpg" width="50" /></td>
                    <td>
                        <button>-</button>
                        <input type="text" />
                        <button>+</button>
                    </td>
                    <td><button class="btn btn-sm btn-danger">删除</button></td>
                </tr>
                <tr>
                    <td><img src="media/day06-b.jpg" width="50" /></td>
                    <td>
                        <button>-</button>
                        <input type="text" />
                        <button>+</button>
                    </td>
                    <td><button class="btn btn-sm btn-danger">删除</button></td>
                </tr>
                <tr>
                    <td><img src="media/day06-c.jpg" width="50" /></td>
                    <td>
                        <button>-</button>
                        <input type="text" />
                        <button>+</button>
                    </td>
                    <td><button class="btn btn-sm btn-danger">删除</button></td>
                </tr>
            </table>
            `
        }
        //结算组件
        var CartTotal = {
            template: `
            <div class="footer">
                <span>总价: 100</span> &nbsp;&nbsp;
                <button class="btn btn-danger">结算</button>
            </div>
            `
        }
        //定义一个全局组件 my-cart
        Vue.component("my-cart", {
            // 引入子组件
            template: `
            <div>
                <cart-title></cart-title>
                <cart-list></cart-list>
                <cart-total></cart-total>
            </div>
            `,
            //注册子路由组件
            components: {
                'cart-title': CartTitle,
                'cart-list': CartList,
                'cart-total': CartTotal
            }
        });
        new Vue({
            el: "#app",
            data: {
            },
            methods: {

            }
        })
</script>
4.2.2完成标题和结算组件功能 组件传值★★★★
  • 标题组件事件动态渲染
    • 从父组件把标题数据传递过来 即 父向子组件传值
    • 把传递过来的数据渲染到页面上
  • 结算功能组件
    • 从父组件把商品列表list 数据传递过来 即 父向子组件传值
    • 把传递过来的数据计算最终价格渲染到页面上
<div id="app" class="container">
        <my-cart></my-cart>
</div>
<script>
        //1、 把静态页面转换成组件化模式
        //1.1  标题组件 
        var CartTitle = {
            props: ['title'],
            template: `
                <div class="header">{{title}}</div>
            `
        }
        //结算组件,子组件接受值即可
        var CartTotal = {
            props: ['goods_list'],
            template: `
            <div class="footer">
                <span>总价: {{total}}</span> &nbsp;&nbsp;
                <button class="btn btn-danger">结算</button>
            </div>
            `,
            computed: {
                total:function(){
                    let amounts = 0;
                    this.goods_list.forEach(item=>{
                        amounts +=item.price*item.num;
                    })
                    return amounts.toFixed(2);
                }
            },
        }
        //定义一个全局组件 my-cart
        Vue.component("my-cart", {
            data: function () {
                return {
                    title: "我的商品",
                    goods_list: [
                        {
                            id: 1,
                            name: 'TCL彩电',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-a.jpg'
                        }, {
                            id: 2,
                            name: '机顶盒',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-b.jpg'
                        }, {
                            id: 3,
                            name: '海尔冰箱',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-c.jpg'
                        }, {
                            id: 4,
                            name: '小米手机',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-d.jpg'
                        }, {
                            id: 5,
                            name: 'PPTV电视',
                            price: 1000,
                            num: 2,
                            img: 'media/day06-e.jpg'
                        }
                    ]
                }
            },
            // 引入子组件,父组件向子组件进行传值
            template: `
            <div>
                <cart-title :title="title"></cart-title>
                <cart-list ></cart-list>
                <cart-total :goods_list="goods_list"></cart-total>
            </div>
            `,
            //注册子路由组件
            components: {
                'cart-title': CartTitle,
                'cart-list': CartList,
                'cart-total': CartTotal
            }
        });
 </script>
4.2.3 完成列表组件删除商品功能splice★★★★
  • 从父组件把商品列表list 数据传递过来 即 父向子组件传值
  • 把传递过来的数据渲染到页面上
  • 点击删除按钮的时候删除对应的数据
    • 给按钮添加点击事件把需要删除的id传递过来
      • 子组件中不推荐操作父组件的数据,有可能多个子组件使用父组件的数据,我们需要把数据 传递给父组件让父组件操作数据
      • 父组件删除对应的数据
<div id="app" class="container">
        <my-cart></my-cart>
</div>
<script>
        //1、 把静态页面转换成组件化模式
        //1.1  标题组件 
        var CartTitle = {
            props: ['title'],
            template: `
                <div class="header">{{title}}</div>
            `
        }
        //1.2  商品列表组件
        var CartList = {
            props: ['goods_list'],
            template: `
            <table class="table table-bordered">
                <tr v-for="(item,index) in goods_list" :key="item.id">
                    <td>
                        <img :src="item.img" width="50" />
                        {{item.name}}
                    </td>
                    <td>
                        <button>-</button>
                        <input type="text" v-model="item.num"/>
                        <button>+</button>
                    </td>
                    <td><button class="btn btn-sm btn-danger" @click='del(item.id)'>删除</button></td>
                </tr>
            </table>
            `,
            methods: {
                del(id) {
                    console.log(id);
                    this.$emit("cart-del", id);
                }
            }
        }
        //结算组件,子组件接受值即可
        var CartTotal = {
            props: ['goods_list'],
            template: `
            <div class="footer">
                <span>总价: {{total}}</span> &nbsp;&nbsp;
                <button class="btn btn-danger">结算</button>
            </div>
            `,
            computed: {
                total: function () {
                    let amounts = 0;
                    this.goods_list.forEach(item => {
                        amounts += item.price * item.num;
                    })
                    return amounts.toFixed(2);
                }
            },
        }
        //定义一个全局组件 my-cart
        Vue.component("my-cart", {
            data: function () {
                return {
                    title: "我的商品",
                    goods_list: [
                        {
                            id: 1,
                            name: 'TCL彩电',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-a.jpg'
                        }, {
                            id: 2,
                            name: '机顶盒',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-b.jpg'
                        }, {
                            id: 3,
                            name: '海尔冰箱',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-c.jpg'
                        }, {
                            id: 4,
                            name: '小米手机',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-d.jpg'
                        }, {
                            id: 5,
                            name: 'PPTV电视',
                            price: 1000,
                            num: 2,
                            img: 'media/day06-e.jpg'
                        }
                    ]
                }
            },
            // 引入子组件,父组件向子组件进行传值
            template: `
            <div>
                <cart-title :title="title"></cart-title>
                <cart-list :goods_list="goods_list" @cart-del="removeGoods"></cart-list>
                <cart-total :goods_list="goods_list"></cart-total>
            </div>
            `,
            //注册子路由组件
            components: {
                'cart-title': CartTitle,
                'cart-list': CartList,
                'cart-total': CartTotal
            },
            methods: {
                removeGoods(id) {
                    console.log(id)
                    let index = this.goods_list.findIndex(item => {
                        return item.id == id;
                    });
                    this.goods_list.splice(index, 1);
                }
            }
        });
        new Vue({
            el: "#app",
            data: {
            },
            methods: {

            }
        })
</script>
4.2.4 完成列表组件更新商品数量computed★★★★
  • 将输入框中的默认数据动态渲染出来
  • 输入框失去焦点的时候 更改商品的数量
  • 点击按钮+和按钮-更新商品的数量,同步更新总价格
  • 子组件中不推荐操作数据 把这些数据传递给父组件 让父组件处理这些数据
  • 父组件中接收子组件传递过来的数据并处理
<div id="app" class="container">
        <my-cart></my-cart>
</div>
<script>
        //1、 把静态页面转换成组件化模式
        //1.1  标题组件 
        var CartTitle = {
            props: ['title'],
            template: `
                <div class="header">{{title}}</div>
            `
        }
        //1.2  商品列表组件
        var CartList = {
            props: ['goods_list'],
            template: `
            <table class="table table-bordered">
                <tr v-for="(item,index) in goods_list" :key="item.id">
                    <td>
                        <img :src="item.img" width="50" />
                        {{item.name}}
                    </td>
                    <td>
                        <button @click="decr(item.id)">-</button>
                        <input type="text" :value="item.num" @blur="changeNums(item.id,$event)"/>
                        <button @click="incr(item.id)">+</button>
                    </td>
                    <td><button class="btn btn-sm btn-danger" @click='del(item.id)'>删除</button></td>
                </tr>
            </table>
            `,
            methods: {
                del(id) {
                    console.log(id);
                    this.$emit("cart-del", id);
                },
                changeNums(id, event) {
                    this.$emit("change-num",
                        {
                            id: id,
                            type: 'change',
                            num: event.target.value
                        }
                    )
                },
                incr(id) {
                    this.$emit('change-num',
                        {
                            id: id,
                            type: 'incr',
                        }
                    );
                },
                decr(id) {
                    this.$emit('change-num',
                        {
                            id: id,
                            type: 'decr',
                        }
                    );
                }
            }
        }
        //结算组件,子组件接受值即可
        var CartTotal = {
            props: ['goods_list'],
            template: `
            <div class="footer">
                <span>总价: {{total}}</span> &nbsp;&nbsp;
                <button class="btn btn-danger">结算</button>
            </div>
            `,
            computed: {
                total: function () {
                    let amounts = 0;
                    this.goods_list.forEach(item => {
                        amounts += item.price * item.num;
                    })
                    return amounts.toFixed(2);
                }
            },
        }
        //定义一个全局组件 my-cart
        Vue.component("my-cart", {
            data: function () {
                return {
                    title: "我的商品",
                    goods_list: [
                        {
                            id: 1,
                            name: 'TCL彩电',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-a.jpg'
                        }, {
                            id: 2,
                            name: '机顶盒',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-b.jpg'
                        }, {
                            id: 3,
                            name: '海尔冰箱',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-c.jpg'
                        }, {
                            id: 4,
                            name: '小米手机',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-d.jpg'
                        }, {
                            id: 5,
                            name: 'PPTV电视',
                            price: 1000,
                            num: 2,
                            img: 'media/day06-e.jpg'
                        }
                    ]
                }
            },
            // 引入子组件,父组件向子组件进行传值
            template: `
            <div>
                <cart-title :title="title"></cart-title>
                <cart-list :goods_list="goods_list" 
                    @cart-del="removeGoods"
                    @change-num="changeNums"
                ></cart-list>
                <cart-total :goods_list="goods_list"></cart-total>
            </div>
            `,
            //注册子路由组件
            components: {
                'cart-title': CartTitle,
                'cart-list': CartList,
                'cart-total': CartTotal
            },
            methods: {
                removeGoods(id) {
                    console.log(id)
                    let index = this.goods_list.findIndex(item => {
                        return item.id == id;
                    });
                    this.goods_list.splice(index, 1);
                },
                changeNums(data) {
                    let id = data.id;
                    let index = this.goods_list.findIndex(item => {
                        return item.id == id;
                    });
                    if (data.type == "change") {
                        this.goods_list[index].num = data.num;
                    }

                    if (data.type == "incr") {
                        this.goods_list[index].num++;
                    }
                    if (data.type == "decr") {
                        this.goods_list[index].num--;
                    }
                }
            }
        });
</script>

4.3 接口调用方式

前端要做动态数据渲染,只能通过调用接口的方式来获取服务端的数据,目前常见的方式有ajax,fetch,axios等常见的方式可以获取接口的数据,我们来分别认识一下这些操作方式…

4.3.1 原生ajax★★★★

ajax主要是负责是客户端和服务端异步数据通信的工具,原生的ajax对浏览器支持比较好,所以群众基础还是比较广的,我们先来看一下原生的ajax如何实现对接口数据的请求:

原生的ajax请求数据大致分为如下几个步骤:

<script>
        let xhr = null;
        //实例化xmlhttprequest对象
        xhr = new XMLHttpRequest();
        //配置要请求的接口地址
        xhr.open("post","url地址",true);
        //设置请求的头部信息,如果是post请求的话
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
        //设置请求后状态发生变化的回调函数
        xhr.onreadystatechange = ()=>{

        };
        // 发送请求
        xhr.send();
</script>
4.3.2 jQuery的ajax★★★★

但是我们大部分时候可能用上面原生的ajax的时候是很少的,我们一般都是用jQuery封装好的Api,这个前提是我们需要先引入jQuery文件,否则提示报错,jQuery的ajax使用步骤大致如下所示

<script>
        $.ajax({
            url:"",
            data:{

            },
            type: "post",
            dataType: "json",
            async: true,
            success(res){

            },
            error(){

            }
        })
</script>
4.3.3 fetch★★★★
  • Fetch API是新的ajax解决方案 Fetch会返回Promise
  • fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象
  • 基本结构大致如下 fetch(url, options).then()
<script type="text/javascript">
        /*
          Fetch API 基本用法
              fetch(url).then()
             第一个参数请求的路径   
             Fetch会返回Promise   
             所以我们可以使用then 拿到请求成功的结果 
        */
        fetch('http://localhost:3000/fdata').then(function (data) {
            // text()方法属于fetchAPI的一部分,
            //它返回一个Promise实例对象,用于获取后台返回的数据
            return data.text();
        }).then(function (data) {
            //   在这个then里面我们能拿到最终的数据  
            console.log(data);
        })
</script>
  • fetch支持很多请求的方式如POST,GET,DELETE,UPDATE,PATCH和PUT
    • 默认的是 GET 请求
    • 需要在 options 对象中 指定对应的 method method:请求使用的方法
    • post 和 普通 请求的时候 需要在options 中 设置 请求头 headers 和 body
<script type="text/javascript">
        /*
              Fetch API 调用接口传递参数
        */
        //1.1 GET参数传递 - 传统URL  通过url  ? 的形式传参
        fetch('http://localhost:3000/books?id=123', {
            //get 请求可以省略不写 默认的是GET 
            method: 'get'
        }).then(function (data) {
            //它返回一个Promise实例对象,用于获取后台返回的数据
            return data.text();
        }).then(function (data) {
            //在这个then里面我们能拿到最终的数据
            console.log(data)
        });

        //1.2  GET参数传递  restful形式的URL  通过 / 的形式传递参数  即  id = 456 和id后台的配置有关
        fetch('http://localhost:3000/books/456', {
            //get 请求可以省略不写 默认的是GET 
            method: 'get'
        }).then(function (data) {
            return data.text();
        }).then(function (data) {
            console.log(data)
        });

        //2.1  DELETE请求方式参数传递      删除id  是  id = 789
        fetch('http://localhost:3000/books/789', {
            method: 'delete'
        })
            .then(function (data) {
                return data.text();
            }).then(function (data) {
                console.log(data)
            });

        //3 POST请求传参
        fetch('http://localhost:3000/books', {
            method: 'post',
            //3.1  传递数据 
            body: 'uname=lisi&pwd=123',
            //3.2  设置请求头 
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        }).then(function (data) {
            return data.text();
        }).then(function (data) {
            console.log(data)
        });

        //POST请求传参
        fetch('http://localhost:3000/books', {
            method: 'post',
            body: JSON.stringify({
                uname: '张三',
                pwd: '456'
            }),
            headers: {
                'Content-Type': 'application/json'
            }
        }).then(function (data) {
            return data.text();
        }).then(function (data) {
            console.log(data)
        });

        //PUT请求传参     修改id 是 123 的
        fetch('http://localhost:3000/books/123', {
            method: 'put',
            body: JSON.stringify({
                uname: '张三',
                pwd: '789'
            }),
            headers: {
                'Content-Type': 'application/json'
            }
        }).then(function (data) {
            return data.text();
        }).then(function (data) {
            console.log(data)
        });
</script>
4.3.4 axios★★★★
  • 基于promise用于浏览器和node.js的http客户端
  • 支持浏览器和node.js
  • 支持promise
  • 能拦截请求和响应
  • 自动转换JSON数据
  • 能转换请求和响应数据

axios基础用法

  • get和 delete请求传递参数
    • 通过传统的url 以 ? 的形式传递参数
    • restful 形式传递参数
    • 通过params 形式传递参数
  • post 和 put 请求传递参数
    • 通过选项传递参数
    • 通过 URLSearchParams 传递参数
<script type="text/javascript">
        // 1. 发送get 请求
        axios.get('http://localhost:3000/adata').then(function (ret) {
            //  拿到 ret 是一个对象      所有的对象都存在 ret 的data 属性里面
            // 注意data属性是固定的用法,用于获取后台的实际数据
            // console.log(ret.data)
            console.log(ret)
        })
        // 2.  get 请求传递参数
        // 2.1  通过传统的url  以 ? 的形式传递参数
        axios.get('http://localhost:3000/axios?id=123').then(function (ret) {
            console.log(ret.data)
        })
        // 2.2  restful 形式传递参数
        axios.get('http://localhost:3000/axios/123').then(function (ret) {
            console.log(ret.data)
        })
        // 2.3  通过params  形式传递参数
        axios.get('http://localhost:3000/axios', {
            params: {
                id: 789
            }
        }).then(function (ret) {
            console.log(ret.data)
        })
        //3 axios delete 请求传参     传参的形式和 get 请求一样
        axios.delete('http://localhost:3000/axios', {
            params: {
                id: 111
            }
        }).then(function (ret) {
            console.log(ret.data)
        })

        // 4  axios 的 post 请求
        // 4.1  通过选项传递参数
        axios.post('http://localhost:3000/axios', {
            uname: 'lisi',
            pwd: 123
        }).then(function (ret) {
            console.log(ret.data)
        })
        // 4.2  通过 URLSearchParams  传递参数
        var params = new URLSearchParams();
        params.append('uname', 'zhangsan');
        params.append('pwd', '111');
        axios.post('http://localhost:3000/axios', params).then(function (ret) {
            console.log(ret.data)
        })

        //5  axios put 请求传参   和 post 请求一样
        axios.put('http://localhost:3000/axios/123', {
            uname: 'lisi',
            pwd: 123
        }).then(function (ret) {
            console.log(ret.data)
        })
</script>

axios全局配置

//配置公共的请求头 
axios.defaults.baseURL = 'https://api.example.com';
//配置 超时时间
axios.defaults.timeout = 2500;
//配置公共的请求头
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
//配置公共的 post 的 Content-Type
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

axios拦截器
axios的拦截器分为请求拦截器和响应拦截器:

  • 请求拦截器
    • 请求拦截器的作用是在请求发送前进行一些操作
      • 例如在每个请求体里加上token,统一做了处理如果以后要改也非常容易
  • 响应拦截器
    • 响应拦截器的作用是在接收到响应后进行一些操作
      • 例如在服务器返回登录状态失效,需要重新登录的时候,跳转到登录页
<script>
        // 1. 请求拦截器 
        axios.interceptors.request.use(function (config) {
            console.log(config.url)
            // 1.1  任何请求都会经过这一步   在发送请求之前做些什么   
            config.headers.mytoken = 'nihao';
            // 1.2  这里一定要return   否则配置不成功  
            return config;
        }, function (err) {
            //1.3 对请求错误做点什么    
            console.log(err)
        })
        //2. 响应拦截器 
        axios.interceptors.response.use(function (res) {
            //2.1  在接收响应做些什么  
            var data = res.data;
            return data;
        }, function (err) {
            //2.2 对响应错误做点什么  
            console.log(err)
        })
</script>

4.4 异步编程

4.4.1 异步★★★

异步和同步是相对的,同步我们一般指的是同时进行,比如张三等李四去吃饭,这个时候李四如果没有去吃饭的话,张三就会一直等着,
就比如请求服务器数据,如果是同步的话,比如等到服务器返回数据后在执行后续的代码逻辑,但是如果是异步的话,我只需要把请求发送出去,然后继续执行我的代码,这个时候就加入了一个异步线程,当数据返回后在执行对应的逻辑,就形成了异步操作。
所以同步是阻塞的,异步是非阻塞的。

代码如下

function doSomething(){
  return new Promise(function(resolve){
    setTimeout(function(){
      console.log('执行结束');
      let result = 6;
      resolve(result);
    },100);
    console.log("异步操作")
  });
} 
doSomething().then(result=>{
  console.log('接收到结果为:'+result);
});

执行效果如下:

4.4.2 单线程
  • JavaScript的执行环境是「单线程」
  • 所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个,它会「阻塞」其他任务。这个任务可称为主线程
  • 异步模式可以一起执行多个任务
4.4.3 常见异步调用★★★

JS中常见的异步调用有

  • 定时任务
  • ajax
  • 事件函数

定时任务
常用到的就是超时函数setTimeout,设置后就会把要执行的代码加入异步队列,然后继续执行后面的代码,等到时间后从异步队列中执行相对应的代码,不会阻塞后面代码的执行。

ajax
ajax本身就是为了解决客户端和服务端异步通信而产生的,jQuery中的ajax我们可以通过设置async的属性控制同步或者异步,默认为异步,async为true为异步,否则为同步

事件函数
采用事件驱动模式。
任务的执行不取决代码的顺序,而取决于某一个事件是否发生。
监听函数有:on,bind,listen,addEventListener,observe

也就是说只有当事件触发后才会执行对应的代码,函数才会被调用,这样的话就不会阻塞其它代码的执行,但是用的多了,可以会导致页面流程不太流畅…

5、异步请求数据

5.1promise

5.1.1promise解决了什么问题★★★★★
  • 主要解决异步深层嵌套的问题
  • promise 提供了简洁的API 使得异步操作更加容易
5.1.2基于Promise发送Ajax请求解决回调地狱★★★★★
<script type="text/javascript">
  /*
     1. Promise基本使用
           我们使用new来构建一个Promise  Promise的构造函数接收一个参数,是函数,并且传入两个参数,resolve,reject, 分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数
    */
  var p = new Promise(function(resolve, reject){
    //2. 这里用于实现异步任务  setTimeout
    setTimeout(function(){
      var flag = false;
      if(flag) {
        //3. 正常情况
        resolve('hello');
      }else{
        //4. 异常情况
        reject('出错了');
      }
    }, 100);
  });
//  5 Promise实例生成以后,可以用then方法指定resolved状态和reject状态的回调函数 
//  在then方法中,你也可以直接return数据而不是Promise对象,在后面的then中就可以接收到数据了  
p.then(function(data){
  console.log(data)
},function(info){
  console.log(info)
});
</script>

5.2promise基本API

5.2.1.then()★★★★★

得到异步任务正确的结果

foo()
  .then(function(data){
  # 得到异步任务正确的结果
  console.log(data)
},function(data){
  # 获取异常信息
  console.log(data)
})
# 成功与否都会执行(不是正式标准) 
  .finally(function(){
    console.log('finished')
  });
5.2.2.catch()★★★★★

获取异常信息

foo()
  .then(function(data){
  # 得到异步任务正确的结果
  console.log(data)
},function(data){
  # 获取异常信息
  console.log(data)
})
# 成功与否都会执行(不是正式标准) 
  .finally(function(){
    console.log('finished')
  });
5.2.3.finally()★★★

成功与否都会执行(不是正式标准)

foo()
  .then(function(data){
  # 得到异步任务正确的结果
  console.log(data)
},function(data){
  # 获取异常信息
  console.log(data)
})
# 成功与否都会执行(不是正式标准) 
  .finally(function(){
    console.log('finished')
  });
5.2.4静态方法all()★★★

Promise.all方法接受一个数组作参数,数组中的对象(p1、p2、p3)均为promise实例(如果不是一个promise,该项会被用Promise.resolve`转换为一个promise)。它的状态由这三个promise实例决定

5.2.5静态方法race()★★★

Promise.race方法同样接受一个数组作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilledrejected`),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数

/*
      Promise常用API-对象方法
    */
// console.dir(Promise)
function queryData(url) {
  return new Promise(function(resolve, reject){
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
      if(xhr.readyState != 4) return;
      if(xhr.readyState == 4 && xhr.status == 200) {
        // 处理正常的情况
        resolve(xhr.responseText);
      }else{
        // 处理异常情况
        reject('服务器错误');
      }
    };
    xhr.open('get', url);
    xhr.send(null);
  });
}

var p1 = queryData('http://localhost:3000/a1');
var p2 = queryData('http://localhost:3000/a2');
var p3 = queryData('http://localhost:3000/a3');
Promise.all([p1,p2,p3]).then(function(result){
  //   all 中的参数  [p1,p2,p3]   和 返回的结果一 一对应["HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]
  console.log(result) //["HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]
})
Promise.race([p1,p2,p3]).then(function(result){
  // 由于p1执行较快,Promise的then()将获得结果'P1'。p2,p3仍在继续执行,但执行结果将被丢弃。
  console.log(result) // "HELLO TOM"
})

5.3fetch

5.3.1fetch API 中的HTTP请求★★★★★
  • fetch(url, options).then()
  • HTTP协议,它给我们提供了很多的方法,如POST,GET,DELETE,UPDATE,PATCH和PUT
    • 默认的是 GET 请求
    • 需要在 options 对象中 指定对应的 method method:请求使用的方法
    • post 和 普通 请求的时候 需要在options 中 设置 请求头 headers 和 body
5.3.2参数传递★★★★★

​ Fetch API 调用接口传递参数

// GET参数传递 - 传统URL  通过url  ? 的形式传参 
fetch('http://localhost:3000/books?id=123', {
// GET参数传递  restful形式的URL  通过/ 的形式传递参数  即  id = 456 和id后台的配置有关   
fetch('http://localhost:3000/books/456', {
  // get 请求可以省略不写 默认的是GET 
  method: 'get'
})
  .then(function(data) {
  return data.text();
}).then(function(data) {
  console.log(data)
});

//  DELETE请求方式参数传递      删除id  是  id=789
fetch('http://localhost:3000/books/789', {
  method: 'delete'
})
  .then(function(data) {
  return data.text();
}).then(function(data) {
  console.log(data)
});

// POST请求传参
fetch('http://localhost:3000/books', {
  method: 'post',
  //  传递数据 
  body: 'uname=lisi&pwd=123',
  //  设置请求头 
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
})
  .then(function(data) {
  return data.text();
}).then(function(data) {
  console.log(data)
});

//POST请求传参
fetch('http://localhost:3000/books', {
  method: 'post',
  body: JSON.stringify({
    uname: '张三',
    pwd: '456'
  }),
  headers: {
    'Content-Type': 'application/json'
  }
})
  .then(function(data) {
  return data.text();
}).then(function(data) {
  console.log(data)
});

//PUT请求传参     修改id 是 123 的 
fetch('http://localhost:3000/books/123', {
  method: 'put',
  body: JSON.stringify({
    uname: '张三',
    pwd: '789'
  }),
  headers: {
    'Content-Type': 'application/json'
  }
})
  .then(function(data) {
  return data.text();
}).then(function(data) {
  console.log(data)
});
5.3.3fetchAPI 中响应数据格式★★★★★

用fetch来获取数据,如果响应正常返回,我们首先看到的是一个response对象,其中包括返回的一堆原始字节,这些字节需要在收到后,需要我们通过调用方法将其转换为相应格式的数据,比如JSONBLOB或者TEXT等等

/*
      Fetch响应结果的数据格式
    */
fetch('http://localhost:3000/json').then(function(data){
  // return data.json();   //  将获取到的数据使用 json 转换对象
  return data.text(); //  //  将获取到的数据 转换成字符串 
}).then(function(data){
  // console.log(data.uname)
  // console.log(typeof data)
  var obj = JSON.parse(data);
  console.log(obj.uname,obj.age,obj.gender)
})

5.4Axios

5.4.1axios基础用法★★★★★
  • 基于promise用于浏览器和node.js的http客户端

  • 支持浏览器和node.js

  • 支持promise

  • 能拦截请求和响应

  • 自动转换JSON数据

  • 能转换请求和响应数据

  • get和 delete请求传递参数

    • 通过传统的url 以 ? 的形式传递参数
    • restful 形式传递参数
    • 通过params 形式传递参数
  • post 和 put 请求传递参数

    • 通过选项传递参数
    • 通过 URLSearchParams 传递参数
5.4.2传参★★★★★
  1. 发送get 请求

    axios.get('http://localhost:3000/adata').then(function(ret){ 
      // 拿到 ret 是一个对象      所有的对象都存在 ret 的data 属性里面
      // 注意data属性是固定的用法,用于获取后台的实际数据
      // console.log(ret.data)
      console.log(ret)
    })
    
  2. get 请求传递参数

    // 通过传统的url  以 ? 的形式传递参数
    axios.get('http://localhost:3000/axios?id=123').then(function(ret){
      console.log(ret.data)
    })
    // restful 形式传递参数 
    axios.get('http://localhost:3000/axios/123').then(function(ret){
      console.log(ret.data)
    })
    // 通过params  形式传递参数 
    axios.get('http://localhost:3000/axios', {
      params: {
        id: 789
      }
    }).then(function(ret){
      console.log(ret.data)
    })
    
  3. axios delete 请求传参 传参的形式和 get 请求一样

    axios.delete('http://localhost:3000/axios', {
      params: {
        id: 111
      }
    }).then(function(ret){
      console.log(ret.data)
    })
    
  4. axios 的 post 请求

    // 通过选项传递参数
    axios.post('http://localhost:3000/axios', {
      uname: 'lisi',
      pwd: 123
    }).then(function(ret){
      console.log(ret.data)
    })
    // 通过 URLSearchParams  传递参数 
    var params = new URLSearchParams();
    params.append('uname', 'zhangsan');
    params.append('pwd', '111');
    axios.post('http://localhost:3000/axios', params).then(function(ret){
      console.log(ret.data)
    })
    
  5. axios put 请求传参 和 post 请求一样

    axios.put('http://localhost:3000/axios/123', {
      uname: 'lisi',
      pwd: 123
    }).then(function(ret){
      console.log(ret.data)
    })
    
5.4.3axios 全局配置★★★★★
配置公共的请求头 
axios.defaults.baseURL = 'https://api.example.com';
配置 超时时间
axios.defaults.timeout = 2500;
配置公共的请求头
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
配置公共的 post 的 Content-Type
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
5.4.4axios 拦截器★★★★
  • 请求拦截器

    • 请求拦截器的作用是在请求发送前进行一些操作
      • 例如在每个请求体里加上token,统一做了处理如果以后要改也非常容易
  • 响应拦截器

    • 响应拦截器的作用是在接收到响应后进行一些操作

      • 例如在服务器返回登录状态失效,需要重新登录的时候,跳转到登录页

      • 请求拦截

        axios.interceptors.request.use(function(config) {
          console.log(config.url)
          // 任何请求都会经过这一步   在发送请求之前做些什么   
          config.headers.mytoken = 'nihao';
          // 这里一定要return   否则配置不成功  
          return config;
        }, function(err){
          // 对请求错误做点什么    
          console.log(err)
        })
        
      • 响应拦截

        axios.interceptors.response.use(function(res) {
          // 在接收响应做些什么  
          var data = res.data;
          return data;
        }, function(err){
          // 对响应错误做点什么  
          console.log(err)
        })
        

5.5 async await

5.5.1基本用法★★★★
  • async作为一个关键字放到函数前面
    • 任何一个async函数都会隐式返回一个promise
  • await关键字只能在使用async定义的函数中使用
    • ​ await后面可以直接跟一个 Promise实例对象
    • ​ await函数不能单独使用
5.5.2处理多个异步请求★★★★
async function queryData() {
  //  添加await之后 当前的await 返回结果之后才会执行后面的代码   
  var info = await axios.get('async1');
  // 让异步代码看起来、表现起来更像同步代码
  var ret = await axios.get('async2?info=' + info.data);
  return ret.data;
}
queryData().then(function(data){
  console.log(data)
})

5.6案例讲解-图书列表

5.6.1获取图书列表 axios.get()★★★★
  • 导入axios 用来发送ajax
  • 把获取到的数据渲染到页面上
<div id="app">
  <div class="grid">
    <table>
      <thead>
        <tr>
          <th>编号</th>
          <th>名称</th>
          <th>时间</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        <!-- 5.  把books  中的数据渲染到页面上   -->
        <tr :key='item.id' v-for='item in books'>
          <td>{{item.id}}</td>
          <td>{{item.name}}</td>
          <td>{{item.date }}</td>
          <td>
            <a href="">修改</a>
            <span>|</span>
            <a href="">删除</a>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</div>
<script type="text/javascript" src="js/vue.js"></script>
// 导入axios   
<script type="text/javascript" src="js/axios.js"></script>
<script type="text/javascript">
  /*
             图书管理-添加图书
         */
  //   配置公共的url地址  简化后面的调用方式
  axios.defaults.baseURL = 'http://localhost:3000/';
  axios.interceptors.response.use(function(res) {
    return res.data;
  }, function(error) {
    console.log(error)
  });

  var vm = new Vue({
    el: '#app',
    data: {
      flag: false,
      submitFlag: false,
      id: '',
      name: '',
      books: []
    },
    methods: {
      // 定义一个方法 用来发送 ajax 
      //  使用 async  来 让异步的代码  以同步的形式书写 
      queryData: async function() {
        // 调用后台接口获取图书列表数据
        // var ret = await axios.get('books');
        // this.books = ret.data;
        //  发送ajax请求  把拿到的数据放在books 里面   
        this.books = await axios.get('books');
      }
    },

    mounted: function() {
      // mounted  里面 DOM已经加载完毕  在这里调用函数  
      this.queryData();
    }
  });
</script>
5.6.2添加图书 axios.post()★★★★
  • 获取用户输入的数据 发送到后台
  • 渲染最新的数据到页面上
methods: {
  handle: async function(){
    if(this.flag) {
      // 编辑图书
      // 就是根据当前的ID去更新数组中对应的数据
      this.books.some((item) => {
        if(item.id == this.id) {
          item.name = this.name;
          // 完成更新操作之后,需要终止循环
          return true;
        }
      });
      this.flag = false;
    }else{
      //  在前面封装好的 handle 方法中  发送ajax请求  
      //  使用async  和 await 简化操作 需要在 function 前面添加 async   
      var ret = await axios.post('books', {
        name: this.name
      })
      //  根据后台返回的状态码判断是否加载数据 
      if(ret.status == 200) {
        //  调用 queryData 这个方法  渲染最新的数据 
        this.queryData();
      }
    }
    // 清空表单
    this.id = '';
    this.name = '';
  },        
}         
5.6.3验证图书名字是否存在 axios.post()★★★★
  • 添加图书之前发送请求验证图示是否已经存在
  • 如果不存在 往后台里面添加图书名称
    • 图书存在与否只需要修改submitFlag的值即可

watch: {
  name: async function(val) {
    // 验证图书名称是否已经存在
    // var flag = this.books.some(function(item){
    //   return item.name == val;
    // });
    var ret = await axios.get('/books/book/' + this.name);
    if(ret.status == 1) {
      // 图书名称存在
      this.submitFlag = true;
    }else{
      // 图书名称不存在
      this.submitFlag = false;
    }
  }
},
5.6.4编辑图书 axios.post()★★★★
  • 根据当前书的id 查询需要编辑的书籍
  • 需要根据状态位判断是添加还是编辑
methods: {
  handle: async function(){
    if(this.flag) {
      // 编辑图书   把用户输入的信息提交到后台
      var ret = await axios.put('books/' + this.id, {
        name: this.name
      });
      if(ret.status == 200){
        //  完成添加后 重新加载列表数据
        this.queryData();
      }
      this.flag = false;
    }else{
      // 添加图书
      var ret = await axios.post('books', {
        name: this.name
      })
      if(ret.status == 200) {
        // 重新加载列表数据
        this.queryData();
      }
    }
    // 清空表单
    this.id = '';
    this.name = '';
  },
    toEdit: async function(id){
      //  flag状态位用于区分编辑和添加操作
      this.flag = true;
      //  根据id查询出对应的图书信息  页面中可以加载出来最新的信息
      // 调用接口发送ajax 请求  
      var ret = await axios.get('books/' + id);
      this.id = ret.id;
      this.name = ret.name;
    },
5.6.5删除图书 axios.get()★★★★

把需要删除的id书籍 通过参数的形式传递到后台

deleteBook: async function(id){
  // 删除图书
  var ret = await axios.delete('books/' + id);
  if(ret.status == 200) {
    // 重新加载列表数据
    this.queryData();
  }
}

6、路由

6.1 路由概念

6.1.1 什么是路由★★★★

路由的本质就是一种对应关系,比如说我们在url地址中输入我们要访问的url地址之后,浏览器要去请求这个url地址对应的资源。

那么url地址和真实的资源之间就有一种对应的关系,就是路由。

6.1.2 前端路由和后端路由★★★★
  1. 前端路由和后端路由的实现方式

    • 后端路由是由服务器端进行实现,并完成资源的分发
    • 前端路由是依靠hash值(锚链接)的变化进行实现
  2. 后端路由性能相对前端路由来说较低,所以,我们接下来主要学习的是前端路由

  3. 前端路由基本概念

    • 根据不同的事件来显示不同的页面内容,即事件与事件处理函数之间的对应关系
    • 前端路由主要做的事情就是监听事件并分发执行事件处理函数

6.2 路由初体验

6.2.1 前端路由实现★★★★

前端路由实现原理:

前端路由是基于hash值的变化进行实现的(比如点击页面中的菜单或者按钮改变URL的hash值,根据hash值的变化来控制组件的切换)
核心实现依靠一个事件,即监听hash值变化的事件

核心代码:

window.onhashchange = function(){
    //location.hash可以获取到最新的hash值
    location.hash
}

前端路由实现tab栏切换:

点击每个超链接之后,会进行相应的内容切换,

核心思路:

在页面中有一个vue实例对象,vue实例对象中有四个组件,分别是tab栏切换需要显示的组件内容
在页面中有四个超链接,如下:

<a href="#/zhuye">主页</a> 
<a href="#/keji">科技</a> 
<a href="#/caijing">财经</a>
<a href="#/yule">娱乐</a>

当我们点击这些超链接的时候,就会改变url地址中的hash值,当hash值被改变时,就会触发onhashchange事件
在触发onhashchange事件的时候,我们根据hash值来让不同的组件进行显示:

window.onhashchange = function() {
    // 通过 location.hash 获取到最新的 hash 值
    console.log(location.hash);
    switch(location.hash.slice(1)){
        case '/zhuye':
        //通过更改数据comName来指定显示的组件
        //因为 <component :is="comName"></component> ,组件已经绑定了comName
        vm.comName = 'zhuye'
        break
        case '/keji':
        vm.comName = 'keji'
        break
        case '/caijing':
        vm.comName = 'caijing'
        break
        case '/yule':
        vm.comName = 'yule'
        break
    }
}

代码演示:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>Document</title>
        <!-- 导入 vue 文件 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
    </head>
    <body>
        <!-- 被 vue 实例控制的 div 区域 -->
        <div id="app">
            <!-- 切换组件的超链接 -->
            <a href="#/zhuye">主页</a> 
            <a href="#/keji">科技</a> 
            <a href="#/caijing">财经</a>
            <a href="#/yule">娱乐</a>
            <!-- 根据 :is 属性指定的组件名称,把对应的组件渲染到 component 标签所在的位置 -->
            <!-- 可以把 component 标签当做是【组件的占位符】 -->
            <component :is="comName"></component>
        </div>
        <script>
            // #region 定义需要被切换的 4 个组件
            // 主页组件
            const zhuye = {
                template: '<h1>主页信息</h1>'
            }
            // 科技组件
            const keji = {
                template: '<h1>科技信息</h1>'
            }
            // 财经组件
            const caijing = {
                template: '<h1>财经信息</h1>'
            }
            // 娱乐组件
            const yule = {
                template: '<h1>娱乐信息</h1>'
            }
            // #endregion
            // #region vue 实例对象
            const vm = new Vue({
                el: '#app',
                data: {
                    comName: 'zhuye'
                },
                // 注册私有组件
                components: {
                    zhuye,
                    keji,
                    caijing,
                    yule
                }
            })
            // #endregion
            // 监听 window 的 onhashchange 事件,根据获取到的最新的 hash 值,切换要显示的组件的名称
            window.onhashchange = function() {
                // 通过 location.hash 获取到最新的 hash 值
                console.log(location.hash);
                switch(location.hash.slice(1)){
                    case '/zhuye':
                        vm.comName = 'zhuye'
                        break
                    case '/keji':
                        vm.comName = 'keji'
                        break
                    case '/caijing':
                        vm.comName = 'caijing'
                        break
                    case '/yule':
                        vm.comName = 'yule'
                        break
                }
            }
        </script>
    </body>
</html>

6.3 Vue-Router介绍

它是一个Vue.js官方提供的路由管理器。是一个功能更加强大的前端路由器,推荐使用。
Vue Router和Vue.js非常契合,可以一起方便的实现SPA(single page web application,单页应用程序)应用程序的开发。
Vue Router依赖于Vue,所以需要先引入Vue,再引入Vue Router

6.3.1 有哪些特性★★★★★
  • 支持H5历史模式或者hash模式
  • 支持嵌套路由
  • 支持路由参数
  • 支持编程式路由
  • 支持命名路由
  • 支持路由导航守卫
  • 支持路由过渡动画特效
  • 支持路由懒加载
  • 支持路由滚动行为
6.3.2 使用步骤★★★★★
  1. 导入js文件

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
    
  2. 添加路由链接:<router-link>是路由中提供的标签,默认会被渲染为a标签,to属性默认被渲染为href属性,
    to属性的值会被渲染为#开头的hash地址

    <router-link to="/user">User</router-link>
    <router-link to="/login">Login</router-link>
    
  3. 添加路由填充位(路由占位符)

    <router-view></router-view>
    
  4. 定义路由组件

    var User = { template:"<div>This is User</div>" }
    var Login = { template:"<div>This is Login</div>" }
    
  5. 配置路由规则并创建路由实例

    var myRouter = new VueRouter({
        //routes是路由规则数组
        routes:[
            //每一个路由规则都是一个对象,对象中至少包含path和component两个属性
            //path表示  路由匹配的hash地址,component表示路由规则对应要展示的组件对象
            {path:"/user",component:User},
            {path:"/login",component:Login}
        ]
    })
    
  6. 将路由挂载到Vue实例中

    new Vue({
        el:"#app",
        //通过router属性挂载路由对象
        router:myRouter
    })
    

完整代码演示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <!-- 导入 vue 文件 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
	<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <!-- 被 vm 实例所控制的区域 -->
    <div id="app">
      <router-link to="/user">User</router-link>
      <router-link to="/register">Register</router-link>
      <!-- 路由占位符 -->
      <router-view></router-view>
    </div>
    <script>
      const User = {
        template: '<h1>User 组件</h1>'
      }
      const Register = {
        template: '<h1>Register 组件</h1>'
      }
      // 创建路由实例对象
      const router = new VueRouter({
        // 所有的路由规则
        routes: [
          { path: '/user', component: User },
          { path: '/register', component: Register }
        ]
      })
      // 创建 vm 实例对象
      const vm = new Vue({
        // 指定控制的区域
        el: '#app',
        data: {},
        // 挂载路由实例对象
        // router: router
        router
      })
    </script>
  </body>
</html>
6.3.3 重定向★★★★

路由重定向:可以通过路由重定向为页面设置默认展示的组件

在路由规则中添加一条路由规则即可,如下:

var myRouter = new VueRouter({
    //routes是路由规则数组
    routes: [
        //path设置为/表示页面最初始的地址 / ,redirect表示要被重定向的新地址,设置为一个路由即可
        { path:"/",redirect:"/user"},
        { path: "/user", component: User },
        { path: "/login", component: Login }
    ]
})

完整代码演示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <!-- 导入 vue 文件 -->
    <script src="./lib/vue_2.5.22.js"></script>
    <script src="./lib/vue-router_3.0.2.js"></script>
  </head>
  <body>
    <!-- 被 vm 实例所控制的区域 -->
    <div id="app">
      <router-link to="/user">User</router-link>
      <router-link to="/register">Register</router-link>

      <!-- 路由占位符 -->
      <router-view></router-view>
    </div>
    <script>
      const User = {
        template: '<h1>User 组件</h1>'
      }
      const Register = {
        template: '<h1>Register 组件</h1>'
      }
      // 创建路由实例对象
      const router = new VueRouter({
        // 所有的路由规则
        routes: [
          { path: '/', redirect: '/user'},
          { path: '/user', component: User },
          { path: '/register', component: Register }
        ]
      })
      // 创建 vm 实例对象
      const vm = new Vue({
        // 指定控制的区域
        el: '#app',
        data: {},
        // 挂载路由实例对象
        // router: router
        router
      })
    </script>
  </body>
</html>

效果和6.2.2案例效果图一样,区别在与6.2.2需要点击一下User才可以显示User组件;而重定向是打开浏览器后会直接显示User组件

6.4 嵌套路由

6.4.1 什么是嵌套路由★★★★
  1. 嵌套路由概念

    当我们进行路由的时候显示的组件中还有新的子级路由链接以及内容。

    嵌套路由最关键的代码在于理解子级路由的概念:

    比如我们有一个/login的路由
    那么/login下面还可以添加子级路由,如:
    /login/account
    /login/phone

  2. 嵌套路由案例

    核心代码:

    // 创建路由实例对象
    const router = new VueRouter({
        // 所有的路由规则
        routes: [
            { path: '/', redirect: '/user'},
            { path: '/user', component: User },
            // children 数组表示子路由规则
            { path: '/register', component: Register, children: [
                { path: '/register/tab1', component: Tab1 },
                { path: '/register/tab2', component: Tab2 }
            ] }
        ]
    })
    

    完整代码演示:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>Document</title>
        <!-- 导入 vue 文件 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
    	<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
      </head>
      <body>
        <!-- 被 vm 实例所控制的区域 -->
        <div id="app">
          <router-link to="/user">User</router-link>
          <router-link to="/register">Register</router-link>
          <!-- 路由占位符 -->
          <router-view></router-view>
        </div>
        <script>
          const User = {
            template: '<h1>User 组件</h1>'
          }
          const Register = {
            template: `<div>
              <h1>Register 组件</h1>
              <hr/>
              <!-- 子路由链接 -->
              <router-link to="/register/tab1">tab1</router-link>
              <router-link to="/register/tab2">tab2</router-link>
              <!-- 子路由的占位符 -->
              <router-view />
            <div>`
          }
          const Tab1 = {
            template: '<h3>tab1 子组件</h3>'
          }
          const Tab2 = {
            template: '<h3>tab2 子组件</h3>'
          }
          // 创建路由实例对象
          const router = new VueRouter({
            // 所有的路由规则
            routes: [
              { path: '/', redirect: '/user'},
              { path: '/user', component: User },
              // children 数组表示子路由规则
              { path: '/register', component: Register, children: [
                { path: '/register/tab1', component: Tab1 },
                { path: '/register/tab2', component: Tab2 }
              ] }
            ]
          })
          // 创建 vm 实例对象
          const vm = new Vue({
            // 指定控制的区域
            el: '#app',
            data: {},
            // 挂载路由实例对象
            // router: router
            router
          })
        </script>
      </body>
    </html>
    

6.5 动态路由

6.5.1 什么是动态路由★★★★

你可以在一个路由中设置多段“路径参数”,对应的值都会设置到$route.params中。

核心代码演示:

var User = { template:"<div>用户:{{$route.params.id}}</div>"}
var myRouter = new VueRouter({
    //routes是路由规则数组
    routes: [
        //通过/:参数名  的形式传递参数 
        { path: "/user/:id", component: User },
    ]
})
6.5.2 多种实现方式★★★★
  1. 通过$route.params来获取路径参数

    var User = { template:"<div>用户:{{$route.params.id}}</div>"}
    var myRouter = new VueRouter({
        //routes是路由规则数组
        routes: [
            //通过/:参数名  的形式传递参数 
            { path: "/user/:id", component: User },
        ]
    })
    
  2. 通过props来接收参数

    var User = { 
        props:["id"],
        template:"<div>用户:{{id}}</div>"
    }
    var myRouter = new VueRouter({
        //routes是路由规则数组
        routes: [
            //通过/:参数名  的形式传递参数 
            //如果props设置为true,route.params将会被设置为组件属性
            { path: "/user/:id", component: User,props:true },
        ]
    })
    
  3. 我们可以将props设置为对象,那么就直接将对象的数据传递给组件进行使用

    var User = { 
        props:["username","pwd"],
        template:"<div>用户:{{username}}---{{pwd}}</div>"
    }
    var myRouter = new VueRouter({
        //routes是路由规则数组
        routes: [
            //通过/:参数名  的形式传递参数 
            //如果props设置为对象,则传递的是对象中的数据给组件
            { path: "/user/:id", component: User,props:{username:"jack",pwd:123} },
        ]
    })
    
  4. 如果想要获取传递的参数值还想要获取传递的对象数据,那么props应该设置为函数形式

    var User = { 
        props:["username","pwd","id"],
        template:"<div>用户:{{id}} -> {{username}}---{{pwd}}</div>"
    }
    var myRouter = new VueRouter({
        //routes是路由规则数组
        routes: [
            //通过/:参数名  的形式传递参数 
            //如果props设置为函数,则通过函数的第一个参数获取路由对象
            //并可以通过路由对象的params属性获取传递的参数
            //
            { path: "/user/:id", component: User,props:(route)=>{
                return {username:"jack",pwd:123,id:route.params.id}
            } 
            },
        ]
    })
    

动态路由案例:

代码演示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <!-- 导入 vue 文件 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
	<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <!-- 被 vm 实例所控制的区域 -->
    <div id="app">
      <router-link to="/user/1">User1</router-link>
      <router-link to="/user/2">User2</router-link>
      <router-link to="/user/3">User3</router-link>
      <router-link to="/register">Register</router-link>
      <!-- 路由占位符 -->
      <router-view></router-view>
    </div>
    <script>
      const User = {
        props: ['id', 'uname', 'age'],
        template: '<h1>User 组件 -- 用户id为: {{id}} -- 姓名为:{{uname}} -- 年龄为:{{age}}</h1>'
      }
      const Register = {
        template: '<h1>Register 组件</h1>'
      }
      // 创建路由实例对象
      const router = new VueRouter({
        // 所有的路由规则
        routes: [
          { path: '/', redirect: '/user' },
          {
            path: '/user/:id',
            component: User,
            props: route => ({ uname: 'zs', age: 20, id: route.params.id })
          },
          { path: '/register', component: Register }
        ]
      })
      // 创建 vm 实例对象
      const vm = new Vue({
        // 指定控制的区域
        el: '#app',
        data: {},
        // 挂载路由实例对象
        // router: router
        router
      })
    </script>
  </body>
</html>

6.6 命名路由

6.6.1 什么是命名路由★★★

给路由取别名

命名路由案例:

代码演示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <!-- 导入 vue 文件 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
	<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <!-- 被 vm 实例所控制的区域 -->
    <div id="app">
      <router-link to="/user/1">User1</router-link>
      <router-link to="/user/2">User2</router-link>
	  <!-- 添加了别名之后,可以使用别名进行跳转 -->
      <router-link :to="{ name: 'user', params: {id: 3} }">User3</router-link>
      <router-link to="/register">Register</router-link>
      <!-- 路由占位符 -->
      <router-view></router-view>
    </div>
    <script>
      const User = {
        props: ['id', 'uname', 'age'],
        template: '<h1>User 组件 -- 用户id为: {{id}} -- 姓名为:{{uname}} -- 年龄为:{{age}}</h1>'
      }
      const Register = {
        template: '<h1>Register 组件</h1>'
      }
      // 创建路由实例对象
      const router = new VueRouter({
        // 所有的路由规则
        routes: [
          { path: '/', redirect: '/user' },
          {
            // 命名路由: 通过name属性为路由添加一个别名
            name: 'user',
            path: '/user/:id',
            component: User,
            props: route => ({ uname: 'zs', age: 20, id: route.params.id })
          },
          { path: '/register', component: Register }
        ]
      })
      // 创建 vm 实例对象
      const vm = new Vue({
        // 指定控制的区域
        el: '#app',
        data: {},
        // 挂载路由实例对象
        // router: router
        router
      })
    </script>
  </body>
</html>

6.7 编程式导航

6.7.1 导航的方式有几种★★★★
  1. 声明式导航:通过点击链接的方式实现的导航

    <router-link to="/user">User1</router-link>
    
  2. 编程式导航:调用js的api方法实现导航

    this.$router.push( { name:'user' } )
    
6.7.2 编程式导航的实现★★★★
this.$router.push("hash地址");
this.$router.push("/login");
this.$router.push({ name:'user' , params: {id:123} });
this.$router.push({ path:"/login" });
this.$router.push({ path:"/login",query:{username:"jack"} });
this.$router.go( n ); //n为数字,参考history.go
this.$router.go( -1 );

编程式导航案例:

代码演示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <!-- 导入 vue 文件 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
	<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <!-- 被 vm 实例所控制的区域 -->
    <div id="app">
      <router-link to="/user/1">User1</router-link>
      <router-link to="/user/2">User2</router-link>
      <router-link :to="{ name: 'user', params: {id: 3} }">User3</router-link>
      <router-link to="/register">Register</router-link>
      <!-- 路由占位符 -->
      <router-view></router-view>
    </div>
    <script>
      const User = {
        props: ['id', 'uname', 'age'],
        template: `<div>
          <h1>User 组件 -- 用户id为: {{id}} -- 姓名为:{{uname}} -- 年龄为:{{age}}</h1>
          <button @click="goRegister">跳转到注册页面</button>
        </div>`,
        methods: {
          goRegister() {
            // 编程式导航  
            this.$router.push('/register')
          }
        },
      }
      const Register = {
        template: `<div>
          <h1>Register 组件</h1>
          <button @click="goBack">后退</button>
        </div>`,
        methods: {
          goBack() {
            // 编程式导航  
            this.$router.go(-1)
          }
        }
      }
      // 创建路由实例对象
      const router = new VueRouter({
        // 所有的路由规则
        routes: [
          { path: '/', redirect: '/user' },
          {
            // 命名路由
            name: 'user',
            path: '/user/:id',
            component: User,
            props: route => ({ uname: 'zs', age: 20, id: route.params.id })
          },
          { path: '/register', component: Register }
        ]
      })
      // 创建 vm 实例对象
      const vm = new Vue({
        // 指定控制的区域
        el: '#app',
        data: {},
        // 挂载路由实例对象
        // router: router
        router
      })
    </script>
  </body>
</html>

6.8 案例讲解-后台管理系统开发准备

6.8.1 案例需求★★★
  • 点击左侧的"用户管理",“权限管理”,“商品管理”,“订单管理”,"系统设置"都会出现对应的组件并展示内容

  • 其中"用户管理"组件展示的效果如上图所示,在用户管理区域中的详情链接也是可以点击的,点击之后将会显示用户详情信息。

6.8.2 布局方案★★★

div+css布局实现页面的自适应

6.8.3 技术选型★★★
  • 路由的基础用法
  • 嵌套路由
  • 路由重定向
  • 路由传参
  • 编程式导航
6.8.4 开发流程★★★
  1. 静态布局编写
  2. 引入vue,vue-router文件
  3. 抽离组件
  4. 功能实现
6.8.5 开发思路★★★★
  1. 根据效果图编写布局

  2. 在页面中引入vue,vue-router

    <!-- 导入 vue 文件 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
    
  3. 创建Vue实例对象,准备开始编写代码实现功能

    const vm = new Vue({
        el: '#app',
        router
    })
    
  4. 抽离并渲染app根组件

    const App = {
    template: `<div>
        <!-- 头部区域 -->
        <header class="header">后台管理系统</header>
        <!-- 中间主体区域 -->
        <div class="main">
            <!-- 左侧菜单栏 -->
            <div class="content left">
                <ul>
                    <li><a href="#">用户管理</a></li>
                    <li><a href="#">权限管理</a></li>
                    <li><a href="#">商品管理</a></li>
                    <li><a href="#">订单管理</a></li>
                    <li><a href="#">系统设置</a></li>
                </ul>
            </div>
            <!-- 右侧内容区域 -->
            <div class="content right"><div class="main-content"> </div>
        </div>
        <!-- 尾部区域 -->
        <footer class="footer">版权信息</footer>
    </div>`
    }
    
  5. 将左侧菜单改为路由链接

    <!-- 左侧菜单栏 -->
    <div class="content left">
        <ul>
            <li><router-link to="/users">用户管理</router-link></li>
            <li><router-link to="/rights">权限管理</router-link></li>
            <li><router-link to="/goods">商品管理</router-link></li>
            <li><router-link to="/orders">订单管理</router-link></li>
            <li><router-link to="/settings">系统设置</router-link></li>
        </ul>
    </div>
    
  6. 创建左侧菜单对应的路由组件

    const Users = {
        template: `<div>
        <h3>用户管理区域</h3>
        <table>
            <thead>
            	<tr><th>编号</th><th>姓名</th><th>年龄</th><th>操作</th></tr>
            </thead>
            <tbody>
                <tr v-for="item in userlist" :key="item.id">
                	<td>1</td>
                	<td>张三</td>
                	<td>20</td>
                    <td>
                    	<a href="javascript:;">详情</a>
                    </td>
                </tr>
            </tbody>
        </table>
        </div>`
    }
    const UserInfo = {
        props: ['id'],
        template: `<div>
    	<h5>用户详情页 --- 用户Id为:{{id}}</h5>
    	<button>后退</button>
    	</div>`
    }
    const Rights = {
        template: `<div>
        <h3>权限管理区域</h3>
        </div>`
    }
    const Goods = {
        template: `<div>
        <h3>商品管理区域</h3>
        </div>`
    }
    const Orders = {
        template: `<div>
    	<h3>订单管理区域</h3>
    	</div>`
    }
    const Settings = {
        template: `<div>
    	<h3>系统设置区域</h3>
    	</div>`
    }
    
  7. 在右侧主题区域添加路由占位符

    <!-- 右侧内容区域 -->
    <div class="content right"><div class="main-content">
        <router-view />
    </div>
    
  8. 添加子路由规则

    // 创建路由对象
    const router = new VueRouter({
        routes: [
            {
                path: '/',
                component: App,
                children: [
                    { path: '/users', component: Users },
                    { path: '/userinfo/:id', component: UserInfo, props: true },
                    { path: '/rights', component: Rights },
                    { path: '/goods', component: Goods },
                    { path: '/orders', component: Orders },
                    { path: '/settings', component: Settings }
                ]
            }
        ]
    })
    
  9. 通过路由重定向默认渲染用户组件

    // 创建路由对象
    const router = new VueRouter({
        routes: [
            {
                path: '/',
                component: App,
                redirect: '/users',
                children: [
                    { path: '/users', component: Users },
                    { path: '/userinfo/:id', component: UserInfo, props: true },
                    { path: '/rights', component: Rights },
                    { path: '/goods', component: Goods },
                    { path: '/orders', component: Orders },
                    { path: '/settings', component: Settings }
                ]
            }
        ]
    })
    
  10. 渲染用户列表数据

    const Users = {
        data() {
            return {
                userlist: [
                    { id: 1, name: '张三', age: 10 },
                    { id: 2, name: '李四', age: 20 },
                    { id: 3, name: '王五', age: 30 },
                    { id: 4, name: '赵六', age: 40 }
                ]
            }
        },
        template: `<div>
        <h3>用户管理区域</h3>
        <table>
            <thead>
            	<tr><th>编号</th><th>姓名</th><th>年龄</th><th>操作</th></tr>
            </thead>
            <tbody>
                <tr v-for="item in userlist" :key="item.id">
                	<td>{{item.id}}</td>
                	<td>{{item.name}}</td>
                	<td>{{item.age}}</td>
                    <td>
                    	<a href="javascript:;" @click="goDetail(item.id)">详情</a>
                    </td>
                </tr>
            </tbody>
        </table>
        </div>`
    }
    
  11. 编程式导航实现路由详情

    const Users = {
        data() {
            return {
                userlist: [
                    { id: 1, name: '张三', age: 10 },
                    { id: 2, name: '李四', age: 20 },
                    { id: 3, name: '王五', age: 30 },
                    { id: 4, name: '赵六', age: 40 }
                ]
            }
        },
        methods: {
            goDetail(id) {
                console.log(id)
                this.$router.push('/userinfo/' + id)
            }
        },
        template: `<div>
        <h3>用户管理区域</h3>
        <table>
            <thead>
            	<tr><th>编号</th><th>姓名</th><th>年龄</th><th>操作</th></tr>
            </thead>
            <tbody>
                <tr v-for="item in userlist" :key="item.id">
                	<td>{{item.id}}</td>
                	<td>{{item.name}}</td>
                	<td>{{item.age}}</td>
                    <td>
                    	<a href="javascript:;">详情</a>
                    </td>
                </tr>
            </tbody>
        </table>
        </div>`
    }
    
  12. 实现后退功能

    const UserInfo = {
        props: ['id'],
        template: `<div>
    	<h5>用户详情页 --- 用户Id为:{{id}}</h5>
    	<button @click="goback()">后退</button>
    	</div>`,
        methods: {
            goback() {
                // 实现后退功能
                this.$router.go(-1)
            }
        }
    }
    

6.9 案例讲解-后台管理系统功能实现

6.9.1 根组件创建 template★★★★★
const app = {
template:`<div>
    <!-- 头部区域 -->
    <header class="header">传智后台管理系统</header>
    <!-- 中间主体区域 -->
    <div class="main">
        <!-- 左侧菜单栏 -->
        <div class="content left">
            <ul>
                <li>用户管理</li>
                <li>权限管理</li>
                <li>商品管理</li>
                <li>订单管理</li>
                <li>系统设置</li>
            </ul>
        </div>
        <!-- 右侧内容区域 -->
        <div class="content right">
            <div class="main-content">添加用户表单</div>
        </div>
    </div>
    <!-- 尾部区域 -->
    <footer class="footer">版权信息</footer>
</div>`
}
6.9.2 默认显示根组件 routes★★★★★
const myRouter = new VueRouter({
    routes:[
        {path:"/",component:app}
    ]
})
const vm = new Vue({
    el:"#app",
    data:{},
    methods:{},
    router:myRouter
})

补充:到此为止,基本的js代码都处理完毕了,我们还需要设置一个路由占位符

<body>
  <div id="app">
    <router-view></router-view>
  </div>
</body>
6.9.3 跳转配置 router-link★★★★★
const app = {
    template:`<div>
        ........
        <div class="main">
            <!-- 左侧菜单栏 -->
            <div class="content left">
                <ul>
                    <!-- 注意:我们把所有li都修改为了路由链接 -->
                    <li><router-link to="/users">用户管理</router-link></li>
                    <li><router-link to="/accesses">权限管理</router-link></li>
                    <li><router-link to="/goods">商品管理</router-link></li>
                    <li><router-link to="/orders">订单管理</router-link></li>
                    <li><router-link to="/systems">系统设置</router-link></li>
                </ul>
            </div>
            <!-- 右侧内容区域 -->
            <div class="content right">
                <div class="main-content">
                    <!-- 在 -->
                    <router-view></router-view> 
                </div>
            </div>
        </div>
        .......
    </div>`
}
6.9.4 用户信息列表 data数据模拟★★★★★

代码演示:

userList:[
    {id:1,name:"zs",age:18},
    {id:2,name:"ls",age:19},
    {id:3,name:"wang",age:20},
    {id:4,name:"jack",age:21},
]
6.9.5 用户详情 传参★★★★★

代码演示:

const Users = {
    data(){
        return {
            userList:[
                {id:1,name:"zs",age:18},
                {id:2,name:"ls",age:19},
                {id:3,name:"wang",age:20},
                {id:4,name:"jack",age:21},
            ]
        }
    },
    template:`<div>
        <h3>用户管理</h3>
        <table>
            <thead>
                <tr>
                    <th>编号</th>
                    <th>姓名</th>
                    <th>年龄</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <tr :key="item.id" v-for="item in userList">
                    <td>{{item.id}}</td>
                    <td>{{item.name}}</td>
                    <td>{{item.age}}</td>
                    <td><a href="javascript:;" @click="goDetail(item.id)">详情</a></td>
                </tr>
            </tbody>
        </table>
    </div>`,
    methods:{
        goDetail(id){
            this.$router.push("/userinfo/"+id);
        }
    }
}

完整代码演示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>基于vue-router的案例</title>
    <style type="text/css">
      html,
      body,
      #app {
        margin: 0;
        padding: 0px;
        height: 100%;
      }
      .header {
        height: 50px;
        background-color: #545c64;
        line-height: 50px;
        text-align: center;
        font-size: 24px;
        color: #fff;
      }
      .footer {
        height: 40px;
        line-height: 40px;
        background-color: #888;
        position: absolute;
        bottom: 0;
        width: 100%;
        text-align: center;
        color: #fff;
      }
      .main {
        display: flex;
        position: absolute;
        top: 50px;
        bottom: 40px;
        width: 100%;
      }
      .content {
        flex: 1;
        text-align: center;
        height: 100%;
      }
      .left {
        flex: 0 0 20%;
        background-color: #545c64;
      }
      .left a {
        color: white;
        text-decoration: none;
      }
      .right {
        margin: 5px;
      }
      .btns {
        width: 100%;
        height: 35px;
        line-height: 35px;
        background-color: #f5f5f5;
        text-align: left;
        padding-left: 10px;
        box-sizing: border-box;
      }
      button {
        height: 30px;
        background-color: #ecf5ff;
        border: 1px solid lightskyblue;
        font-size: 12px;
        padding: 0 20px;
      }
      .main-content {
        margin-top: 10px;
      }
      ul {
        margin: 0;
        padding: 0;
        list-style: none;
      }
      ul li {
        height: 45px;
        line-height: 45px;
        background-color: #a0a0a0;
        color: #fff;
        cursor: pointer;
        border-bottom: 1px solid #fff;
      }
      table {
        width: 100%;
        border-collapse: collapse;
      }
      td,
      th {
        border: 1px solid #eee;
        line-height: 35px;
        font-size: 12px;
      }
      th {
        background-color: #ddd;
      }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
	<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <!-- 要被 vue 实例所控制的区域 -->
    <div id="app">
      <!-- 路由占位符 -->
      <router-view></router-view>
    </div>
    <script>
      // 定义 APP 根组件
      const App = {
        template: `<div>
          <!-- 头部区域 -->
          <header class="header">后台管理系统</header>
          <!-- 中间主体区域 -->
          <div class="main">
            <!-- 左侧菜单栏 -->
            <div class="content left">
              <ul>
                <li><router-link to="/users">用户管理</router-link></li>
                <li><router-link to="/rights">权限管理</router-link></li>
                <li><router-link to="/goods">商品管理</router-link></li>
                <li><router-link to="/orders">订单管理</router-link></li>
                <li><router-link to="/settings">系统设置</router-link></li>
              </ul>
            </div>
            <!-- 右侧内容区域 -->
            <div class="content right"><div class="main-content">
              <router-view />
            </div></div>
          </div>
          <!-- 尾部区域 -->
          <footer class="footer">版权信息</footer>
        </div>`
      }
      const Users = {
        data() {
          return {
            userlist: [
              { id: 1, name: '张三', age: 10 },
              { id: 2, name: '李四', age: 20 },
              { id: 3, name: '王五', age: 30 },
              { id: 4, name: '赵六', age: 40 }
            ]
          }
        },
        methods: {
          goDetail(id) {
            console.log(id)
            this.$router.push('/userinfo/' + id)
          }
        },
        template: `<div>
        <h3>用户管理区域</h3>
        <table>
          <thead>
            <tr><th>编号</th><th>姓名</th><th>年龄</th><th>操作</th></tr>
          </thead>
          <tbody>
            <tr v-for="item in userlist" :key="item.id">
              <td>{{item.id}}</td>
              <td>{{item.name}}</td>
              <td>{{item.age}}</td>
              <td>
                <a href="javascript:;" @click="goDetail(item.id)">详情</a>
              </td>
            </tr>
          </tbody>
        </table>
      </div>`
      }
      const UserInfo = {
        props: ['id'],
        template: `<div>
          <h5>用户详情页 --- 用户Id为:{{id}}</h5>
          <button @click="goback()">后退</button>
        </div>`,
        methods: {
          goback() {
            // 实现后退功能
            this.$router.go(-1)
          }
        }
      }
      const Rights = {
        template: `<div>
        <h3>权限管理区域</h3>
      </div>`
      }
      const Goods = {
        template: `<div>
        <h3>商品管理区域</h3>
      </div>`
      }
      const Orders = {
        template: `<div>
        <h3>订单管理区域</h3>
      </div>`
      }
      const Settings = {
        template: `<div>
        <h3>系统设置区域</h3>
      </div>`
      }
      // 创建路由对象
      const router = new VueRouter({
        routes: [
          {
            path: '/',
            component: App,
            redirect: '/users',
            children: [
              { path: '/users', component: Users },
              { path: '/userinfo/:id', component: UserInfo, props: true },
              { path: '/rights', component: Rights },
              { path: '/goods', component: Goods },
              { path: '/orders', component: Orders },
              { path: '/settings', component: Settings }
            ]
          }
        ]
      })
      const vm = new Vue({
        el: '#app',
        router
      })
    </script>
  </body>
</html>

7、前端工程化

7.1 模块化分类

传统开发模式主要有命名冲突和文件依赖的问题。

模块化是一个语言膨胀的必经之路,它能够帮助开发者拆分和组织代码。

模块化就是把单独的一个功能封装到一个模块(文件)中,模块之间相互隔离,但是可以通过特定的接口公开内部成员,也可以依赖别的模块。

模块化开发的好处:方便代码的重用,从而提升开发效率,并且方便后期的维护

7.1.1 浏览器端的模块化★★★

浏览器端的模块话我们主要探讨AMD和CMD两种规范

AMD

Asynchronous Module Definition,异步模块定义,

// 定义AMD规范的模块
define([function() {
  return 模块
})

AMD规范的被依赖模块是异步加载的,而定义的模块是被当作回调函数来执行的,依赖于require.js模块管理工具库,当然,AMD规范不是采用匿名函数自调用的方式来封装,我们依然可以利用闭包的原理来实现模块的私有成员和公有成员:

define(['module1', 'module2'], function(m1, m2) {
  let x = 1;
  function add() {
    x += 1;
    return x;
  }
  return { add };
})

CMD

CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。AMD 推崇依赖前置,CMD 推崇依赖就近。

define(function(require, exports, module) {
  //  同步加载模块
  var a = require('./a');
  a.doSomething();
  // 异步加载一个模块,在加载完成时,执行回调
  require.async(['./b'], function(b) {
    b.doSomething();
  });
  // 对外暴露成员
  exports.doSomething = function() {};
});
// 使用模块
seajs.use('path');

CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的。

因此,在CMD中require函数同步加载模块时没有HTTP请求过程。

7.1.2 服务器端的模块化★★★
  • 服务器端的模块化规范是使用CommonJS规范:
    • 使用require引入其他模块或者包
    • 使用exports或者module.exports导出模块成员
    • 一个文件就是一个模块,都拥有独立的作用域
// 文件名:x.js
let x = 1;
function add() {
  x += 1;
  return x;
}
module.exports.x = x;
module.exports.add = add;

CommonJS通过require()引入模块依赖,require函数可以引入Node的内置模块、自定义模块和npm等第三方模块。

// 文件名:main.js
let xm = require('./x.js');
console.log(xm.x);  // 1
console.log(xm.add());  // 2
console.log(xm.x);   // 1

require函数同步加载了x.js,并且返回了module.exports输出字面量的拷贝值

7.1.3 ES6模块化★★★
  • ES6模块化规范中定义:
    • 每一个js文件都是独立的模块
    • 导入模块成员使用import关键字
    • 暴露模块成员使用export关键字
//index.js
export default {
  username: "liguanh",
  age;18
}

//导入对象
import object from "./index"
console.log(object);

ES6的模块化已经不是规范了,而是JS语言的特性。随着ES6的推出,AMD和CMD也随之成为了历史。ES6模块与模块化规范相比,有两大特点:

  • 模块化规范输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • 模块化规范是运行时加载,ES6 模块是编译时输出接口。

小结: 推荐使用ES6模块化,因为AMD,CMD局限使用与浏览器端,而CommonJS在服务器端使用。
ES6模块化是浏览器端和服务器端通用的规范.

7.2 nodeJS中安装babel体验ES6模块化

7.2.1 安装babel★★★★
  • 打开终端或者gitbash窗口,在终端中输入如下命令:

    npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/node
    
  • 安装完毕之后,再次输入命令安装:

    npm install --save @babel/polyfill
    
7.2.2 创建babel.config.js★★★★
  • 在项目目录中创建babel.config.js文件。编辑js的代码如下:

    const presets = [
                ["@babel/env",{
                    targets:{
                        edge:"17",
                        firefox:"60",
                        chrome:"67",
                        safari:"11.1"
                    }
                }]
            ]
    //暴露模块化的数据
    module.exports = { presets }
    
7.2.3 创建index.js文件

在项目目录中创建index.js文件作为入口文件
在index.js中输入需要执行的js代码,例如:

for(var i=0;i<10;i++){
  console.log("hello world");
}
7.2.4 使用npx执行★★★★
  • 打开终端或者gitbash执行如下的命令:

    npx babel-node ./index.js
    

7.3 设置默认的导入导出

在实际代码中,我们通过export关键字是能够对外暴露本模块中的变量对象,函数,类的,尽管使用如下方式export向外暴露多个变量对象,后面跟一大括号,变量名与变量名之间用逗号隔开:

exprot {identifier1,identifier2,...}

但是问题来了,如果我不想写这些变量对象呢?那么可以使用default关键字指定单个变量,函数或者类,但是要格外注意一点就是每个模块只能设置一个默认的导出值。那下面我们来看一下默认导入和导出具体怎么使用。

7.3.1 默认导出★★★★
export default {
        成员A,
        成员B,
        .......
    },
//导出信息如下:
let num = 100;
export default{
        num
  }
7.3.2 默认导入★★★★

导入的话我们需要使用import关键字:

import 接收名称对象 from "模块标识符",如下:
import test from "./test.js"

**注意:**在一个模块中,只允许使用export default向外默认暴露一次成员,千万不要写多个export default。
如果在一个模块中没有向外暴露成员,其他模块引入该模块时将会得到一个空对象

这里还要注意一点就是:若是使用export default的方式默认导出,此处的sub就不要加{}双大括号,否则就会报错。

7.4 设置按需导入导出

7.4.1 按需导出★★★★★
var name = "随笔川迹";
var age = 18;
var weChatPublic = "itclanCoder";

function sub(num1,num2){
  return num1-num2;
}

export default {
   name,
   age,
   weChatPublic,
   sub
}
7.4.2 按需导入★★★★★
import { name,age,weChatPublic,sub} from "./test.js"
//同时导入默认导出的成员以及按需导入的成员
import { name,age as uname ,uage } from "./test.js"

注意:一个模块中既可以按需导入也可以默认导入,一个模块中既可以按需导出也可以默认导出

7.5 直接导入并执行代码

7.5.1 import★★★★★

有时候,我们只想单纯执行某个模块中的代码,并不需要得到模块中向外暴露的成员,此时,可以直接导入并执行模块代码。

// 直接导入并执行模块代码
import './m2.js'
// 当前文件模块为 m2.js
// 在当前模块中执行一个 for 循环操作
for(let i = 0; i < 3; i++) {
console.log(i)
}

7.6 webpack

7.6.1 概念★★★★★

webpack是一个流行的前端项目构建工具,可以解决目前web开发的困境。
webpack提供了模块化支持,代码压缩混淆,解决js兼容问题,性能优化等特性,提高了开发效率和项目的可维护性

7.6.2 webpack基本使用★★★★★

创建项目目录并初始化

创建项目,并打开项目所在目录的终端,输入命令:

npm init -y

创建首页及js文件

在项目目录中创建index.html页面,并初始化页面结构:在页面中摆放一个ul,ul里面放置几个li
在项目目录中创建js文件夹,并在文件夹中创建index.js文件

安装jQuery

打开项目目录终端,输入命令:

npm install jQuery -S

导入jQuery

打开index.js文件,编写代码导入jQuery并实现功能:

import $ from "jquery";
$(function() {
  $("li:odd").css("background", "cyan");
  $("li:odd").css("background", "pink");
});

**注意:**此时项目运行会有错误,因为import $ from “jquery”;这句代码属于ES6的新语法代码,在浏览器中可能会存在兼容性问题
所以我们需要webpack来帮助我们解决这个问题。

安装webpack

  • 打开项目目录终端,输入命令:

    npm install webpack webpack-cli -D
    
  • 然后在项目根目录中,创建一个 webpack.config.js 的配置文件用来配置webpack
    在 webpack.config.js 文件中编写代码进行webpack配置,如下:

    module.exports = {
            mode:"development"//可以设置为development(开发模式),production(发布模式)
        }
    

    补充:mode设置的是项目的编译模式。
    如果设置为development则表示项目处于开发阶段,不会进行压缩和混淆,打包速度会快一些
    如果设置为production则表示项目处于上线发布阶段,会进行压缩和混淆,打包速度会慢一些

  • 修改项目中的package.json文件添加运行脚本dev,如下:

    "scripts":{
            "dev":"webpack"
    }
    

    注意:scripts节点下的脚本,可以通过 npm run 运行,如:
    运行终端命令:npm run dev
    将会启动webpack进行项目打包

  • 运行dev命令进行项目打包,并在页面中引入项目打包生成的js文件

    打开项目目录终端,输入命令:
    npm run dev
    等待webpack打包完毕之后,找到默认的dist路径中生成的main.js文件,将其引入到html页面中。
    浏览页面查看效果。

  • 设置webpack的打包入口/出口

    在webpack 4.x中,默认会将src/index.js 作为默认的打包入口js文件
    默认会将dist/main.js 作为默认的打包输出js文件
    如果不想使用默认的入口/出口js文件,我们可以通过改变 webpack.config.js 来设置入口/出口的js文件,如下:

    module.exports = {
            mode:"development",
            //设置入口文件路径
            entry: path.join(__dirname,"./src/xx.js"),
            //设置出口文件
            output:{
                //设置路径
                path:path.join(__dirname,"./dist"),
                //设置文件名
                filename:"res.js"
            }
    }
    
  • 设置webpack的自动打包

    默认情况下,我们更改入口js文件的代码,需要重新运行命令打包webpack,才能生成出口的js文件
    那么每次都要重新执行命令打包,这是一个非常繁琐的事情,那么,自动打包可以解决这样繁琐的操作。
    实现自动打包功能的步骤如下:

    • 安装自动打包功能的包:webpack-dev-server
      npm install webpack-dev-server -D

    • 修改package.json中的dev指令如下:

      "scripts":{
          "dev":"webpack-dev-server"
       }
      
    • 将引入的js文件路径更改为:

      <script src="/bundle.js"></script>
      
    • 运行npm run dev,进行打包

    • 打开网址查看效果:http://localhost:8080

      **注意:**webpack-dev-server自动打包的输出文件,默认放到了服务器的根目录中.

      补充:
      在自动打包完毕之后,默认打开服务器网页,实现方式就是打开package.json文件,修改dev命令:
      "dev": "webpack-dev-server --open --host 127.0.0.1 --port 9999"

  • 配置html-webpack-plugin

    使用html-webpack-plugin 可以生成一个预览页面。
    因为当我们访问默认的 http://localhost:8080/的时候,看到的是一些文件和文件夹,想要查看我们的页面
    还需要点击文件夹点击文件才能查看,那么我们希望默认就能看到一个页面,而不是看到文件夹或者目录。
    实现默认预览页面功能的步骤如下:

    • 安装默认预览功能的包:html-webpack-plugin

      npm install html-webpack-plugin -D

    • 修改webpack.config.js文件,如下:

       //导入包
       const HtmlWebpackPlugin = require("html-webpack-plugin");
       //创建对象
       const htmlPlugin = new HtmlWebpackPlugin({
       			//设置生成预览页面的模板文件
           template:"./src/index.html",
           //设置生成的预览页面名称
           filename:"index.html"
       })
      
    • 继续修改webpack.config.js文件,添加plugins信息:

      module.exports = {
                      ......
                      plugins:[ htmlPlugin ]
                  }
      
  • webpack中的加载器

    通过loader打包非js模块:默认情况下,webpack只能打包js文件,如果想要打包非js文件,需要调用loader加载器才能打包

    • loader加载器包含:
      • less-loader
      • sass-loader
      • url-loader:打包处理css中与url路径有关的文件
      • babel-loader:处理高级js语法的加载器
      • postcss-loader
      • css-loader,style-loader

    **注意:**指定多个loader时的顺序是固定的,而调用loader的顺序是从后向前进行调用

    • 安装style-loader,css-loader来处理样式文件

      • 安装包
        npm install style-loader css-loader -D

      • 配置规则:更改webpack.config.js的module中的rules数组

        module.exports = {
                ......
                plugins:[ htmlPlugin ],
                module : {
                    rules:[
                        {
                            //test设置需要匹配的文件类型,支持正则
                            test:/\.css$/,
                            //use表示该文件类型需要调用的loader
                            use:['style-loader','css-loader']
                        }
                    ]
                }
            }
        
    • 安装less,less-loader处理less文件

      • 安装包
        npm install less-loader less -D

      • 配置规则:更改webpack.config.js的module中的rules数组

        module.exports = {
                ......
                plugins:[ htmlPlugin ],
                module : {
                    rules:[
                        {
                            //test设置需要匹配的文件类型,支持正则
                            test:/\.css$/,
                            //use表示该文件类型需要调用的loader
                            use:['style-loader','css-loader']
                        },
                        {
                            test:/\.less$/,
                            use:['style-loader','css-loader','less-loader']
                        }
                    ]
                }
            }
        
    • 安装sass-loader,node-sass处理less文件

      • 安装包
        npm install sass-loader node-sass -D

      • 配置规则:更改webpack.config.js的module中的rules数组

        module.exports = {
                ......
                plugins:[ htmlPlugin ],
                module : {
                    rules:[
                        {
                            //test设置需要匹配的文件类型,支持正则
                            test:/\.css$/,
                            //use表示该文件类型需要调用的loader
                            use:['style-loader','css-loader']
                        },
                        {
                            test:/\.less$/,
                            use:['style-loader','css-loader','less-loader']
                        },
                        {
                            test:/\.scss$/,
                            use:['style-loader','css-loader','sass-loader']
                        }
                    ]
                }
            }
        

        补充:安装sass-loader失败时,大部分情况是因为网络原因,详情参考:
        https://segmentfault.com/a/1190000010984731?utm_source=tag-newest

    • 安装post-css自动添加css的兼容性前缀(-ie-,-webkit-)

      • 安装包
        npm install postcss-loader autoprefixer -D

      • 在项目根目录创建并配置postcss.config.js文件

        const autoprefixer = require("autoprefixer");
        module.exports = {
            plugins:[ autoprefixer ]
        }
        
      • 配置规则:更改webpack.config.js的module中的rules数组

        module.exports = {
            ......
            plugins:[ htmlPlugin ],
            module : {
                rules:[
                    {
                        //test设置需要匹配的文件类型,支持正则
                        test:/\.css$/,
                        //use表示该文件类型需要调用的loader
                        use:['style-loader','css-loader','postcss-loader']
                    },
                    {
                        test:/\.less$/,
                        use:['style-loader','css-loader','less-loader']
                    },
                    {
                        test:/\.scss$/,
                        use:['style-loader','css-loader','sass-loader']
                    }
                ]
            }
        }
        
    • 打包样式表中的图片以及字体文件
      在样式表css中有时候会设置背景图片和设置字体文件,一样需要loader进行处理
      使用url-loader和file-loader来处理打包图片文件以及字体文件

      • 安装包
        npm install url-loader file-loader -D
      • 配置规则:更改webpack.config.js的module中的rules数组
      module.exports = {
          ......
          plugins:[ htmlPlugin ],
          module : {
              rules:[
                  {
                      //test设置需要匹配的文件类型,支持正则
                      test:/\.css$/,
                      //use表示该文件类型需要调用的loader
                      use:['style-loader','css-loader']
                  },
                  {
                      test:/\.less$/,
                      use:['style-loader','css-loader','less-loader']
                  },
                  {
                      test:/\.scss$/,
                      use:['style-loader','css-loader','sass-loader']
                  },{
                      test:/\.jpg|png|gif|bmp|ttf|eot|svg|woff|woff2$/,
                      //limit用来设置字节数,只有小于limit值的图片,才会转换
                      //为base64图片
                      use:"url-loader?limit=16940"
                  }
              ]
          }
      }
      
    • 打包js文件中的高级语法:在编写js的时候,有时候我们会使用高版本的js语法
      有可能这些高版本的语法不被兼容,我们需要将之打包为兼容性的js代码
      我们需要安装babel系列的包

      • 安装babel转换器
        npm install babel-loader @babel/core @babel/runtime -D

      • 安装babel语法插件包

        ```shell
        

        npm install @babel/preset-env @babel/plugin-transform-runtime @babel/plugin-proposal-class-properties -D
        ```

      • 在项目根目录创建并配置babel.config.js文件

        module.exports = {
                presets:["@babel/preset-env"],
                plugins:[ "@babel/plugin-transform-runtime", "@babel/plugin-proposal-class-properties" ]
            }
        
      • 配置规则:更改webpack.config.js的module中的rules数组

        module.exports = {
            ......
            plugins:[ htmlPlugin ],
            module : {
                rules:[
                    {
                        //test设置需要匹配的文件类型,支持正则
                        test:/\.css$/,
                        //use表示该文件类型需要调用的loader
                        use:['style-loader','css-loader']
                    },
                    {
                        test:/\.less$/,
                        use:['style-loader','css-loader','less-loader']
                    },
                    {
                        test:/\.scss$/,
                        use:['style-loader','css-loader','sass-loader']
                    },{
                        test:/\.jpg|png|gif|bmp|ttf|eot|svg|woff|woff2$/,
                        //limit用来设置字节数,只有小于limit值的图片,才会转换
                        //为base64图片
                        use:"url-loader?limit=16940"
                    },{
                        test:/\.js$/,
                        use:"babel-loader",
                        //exclude为排除项,意思是不要处理node_modules中的js文件
                        exclude:/node_modules/
                    }
                ]
            }
        }
        
7.6.3 webpack中使用Vue★★★★

上一节我们安装处理了vue单文件组件的加载器,想要让vue单文件组件能够使用,我们必须要安装vue
并使用vue来引用vue单文件组件。

  • 安装Vue
    npm install vue -S

  • 在index.js中引入vue:import Vue from "vue"

  • 创建Vue实例对象并指定el,最后使用render函数渲染单文件组件

    const vm = new Vue({
            el:"#first",
            render:h=>h(app)
        })
    
7.6.4 webpack打包发布★★★★

在项目上线之前,我们需要将整个项目打包并发布。

  • 配置package.json

    "scripts":{
            "dev":"webpack-dev-server",
            "build":"webpack -p"
    }
    
  • 在项目打包之前,可以将dist目录删除,生成全新的dist目录

7.7 Vue脚手架

7.7.1 什么是脚手架★★★★★

Vue脚手架可以快速生成Vue项目基础的架构。

7.7.2 脚手架的基本使用★★★★★
  • 安装3.x版本的Vue脚手架:
    npm install -g @vue/cli
  • 基于3.x版本的脚手架创建Vue项目:
    • 使用命令创建Vue项目
      • 命令:vue create my-project
      • 选择Manually select features(选择特性以创建项目)
      • 勾选特性可以用空格进行勾选。
      • 是否选用历史模式的路由:n
      • ESLint选择:ESLint + Standard config
      • 何时进行ESLint语法校验:Lint on save
      • babel,postcss等配置文件如何放置:In dedicated config files(单独使用文件进行配置)
      • 是否保存为模板:n
      • 使用哪个工具安装包:npm
7.7.3 图形化方式创建Vue项目★★★★
  • 命令:vue ui
    在自动打开的创建项目网页中配置项目信息。
7.7.4 2X旧创建创建旧版Vue项目★★★★
npm install -g @vue/cli-init
vue init webpack my-project
7.7.5 脚手架生成的项目结构分析★★★★
  • node_modules:依赖包目录
  • public:静态资源目录
  • src:源码目录
  • src/assets:资源目录
  • src/components:组件目录
  • src/views:视图组件目录
  • src/App.vue:根组件
  • src/main.js:入口js
  • src/router.js:路由js
  • babel.config.js:babel配置文件
7.7.6 脚手架的自定义配置★★★★
  • 通过 package.json 进行配置 [不推荐使用]

    "vue":{
                "devServer":{
                    "port":"9990",
                    "open":true
                }
       }
    
  • 通过单独的配置文件进行配置,创建vue.config.js

    module.exports = {
                devServer:{
                    port:8888,
                    open:true
                }
     }
    

7.8 ElementUI的基本使用

Element-UI:一套基于2.0的桌面端组件库

7.8.1 安装★★★★
npm install element-ui -S
7.8.2 全局引入★★★★
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";
Vue.use(ElementUI)
7.8.3 局部引入★★★★

借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。

首先,安装 babel-plugin-component:

npm install babel-plugin-component -D

然后,将 .babelrc 修改为:

{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}
7.8.4 组件使用方法演示★★★★★

接下来,如果你只希望引入部分组件,比如 Button 和 Select,那么需要在 main.js 中写入以下内容:

import Vue from 'vue';
import { Button, Select } from 'element-ui';
import App from './App.vue';

Vue.component(Button.name, Button);
Vue.component(Select.name, Select);
/* 或写为
 * Vue.use(Button)
 * Vue.use(Select)
 */

new Vue({
  el: '#app',
  render: h => h(App)
});
<el-row>
  <el-col :span="24"><div class="grid-content bg-purple-dark"></div></el-col>
</el-row>
<el-row>
  <el-col :span="12"><div class="grid-content bg-purple"></div></el-col>
  <el-col :span="12"><div class="grid-content bg-purple-light"></div></el-col>
</el-row>
<el-row>
  <el-col :span="8"><div class="grid-content bg-purple"></div></el-col>
  <el-col :span="8"><div class="grid-content bg-purple-light"></div></el-col>
  <el-col :span="8"><div class="grid-content bg-purple"></div></el-col>
</el-row>

8、Vuex

8.1Vuex概述

8.1.1Vuex概念★★★★

Vuex是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间的数据共享

使用Vuex管理数据的好处:

  • 能够在vuex中集中管理共享的数据,便于开发和后期进行维护
  • 能够高效的实现组件之间的数据共享,提高开发效率
  • 存储在vuex中的数据是响应式的,当数据发生改变时,页面中的数据也会同步更新
8.1.2Vuex和组件通信对比★★★★

使用Vuex来管理数据共享,各组件无需关注组件间的数据通信传输,一切数据的读取和更新都是各组件与Vuex数据仓库间的操作,避免了复杂项目中数据管理混乱的情况发生

8.1.3Vuex使用场景★★★★★

涉及到非父子关系的组件,例如兄弟关系、祖孙关系,甚至更远的关系组件之间的联系

中大型单页应用,考虑如何更好地在组件外部管理状态

8.2Vuex的基本使用

8.2.1安装★★★★★
npm i vuex -s
8.2.2语法★★★★★
import Vue from 'vue'
import Vuex from 'vuex'

//挂载Vuex
Vue.use(Vuex)

//创建VueX对象
const store = new Vuex.Store({
    state:{
        //存放的键值对就是所要管理的状态
        name:'helloVueX'
    }
})

export default store
8.2.3Vuex完成计数器案例★★★★★

打开刚刚创建的vuex项目,找到src目录中的App.vue组件,将代码重新编写如下:

<template>
  <div>
    <my-addition></my-addition>

    <p>----------------------------------------</p>

    <my-subtraction></my-subtraction>
  </div>
</template>

<script>
import Addition from './components/Addition.vue'
import Subtraction from './components/Subtraction.vue'

export default {
  data() {
    return {}
  },
  components: {
    'my-subtraction': Subtraction,
    'my-addition': Addition
  }
}
</script>

<style>
</style>

在components文件夹中创建Addition.vue组件,代码如下:

<template>
    <div>
        <h3>当前最新的count值为:</h3>
        <button>+1</button>
    </div>
</template>

<script>
export default {
  data() {
    return {}
  }
}
</script>

<style>
</style>

在components文件夹中创建Subtraction.vue组件,代码如下:

<template>
    <div>
        <h3>当前最新的count值为:</h3>
        <button>-1</button>
    </div>
</template>

<script>
export default {
  data() {
    return {}
  }
}
</script>

<style>
</style>

最后在项目根目录(与src平级)中创建 .prettierrc 文件,编写代码如下:

{
    "semi":false,
    "singleQuote":true
}

8.3Vuex中的核心特性

8.3.1State★★★★★

vuex中的数据源,我们需要保存的数据就保存在这里,可以在页面通过 this.$store.state来获取我们定义的数据;

8.3.2Mutation★★★★★

mutations是操作state数据的方法的集合,比如对该数据的修改、增加、删除等等。

8.3.3Action★★★★★

由于直接在mutation方法中进行异步操作,将会引起数据失效。所以提供了Actions来专门进行异步操作,最终提交mutation方法。

Actions中的方法有两个默认参数

  • context 上下文(相当于箭头函数中的this)对象
  • payload 挂载参数。
8.3.4Getter★★★★★

可以对state中的成员加工后传递给外界

Getters中的方法有两个默认参数

  • state 当前VueX对象中的状态对象
  • getters 当前getters对象,用于将getters下的其他getter拿来用
8.3.5Module★★★★

当项目庞大,状态非常多时,可以采用模块化管理模式。Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。

8.4Vuex案例

8.4.1初始化案例State★★★★★

首先使用vue ui初始化一个使用vuex的案例
然后打开public文件夹,创建一个list.json文件,文件代码如下:

[
    {
        "id": 0,
        "info": "Racing car sprays burning fuel into crowd.",
        "done": false
    },
    {
        "id": 1,
        "info": "Japanese princess to wed commoner.",
        "done": false
    },
    {
        "id": 2,
        "info": "Australian walks 100km after outback crash.",
        "done": false
    },
    {
        "id": 3,
        "info": "Man charged over missing wedding girl.",
        "done": false
    },
    {
        "id": 4,
        "info": "Los Angeles battles huge wildfires.",
        "done": false
    }
]

再接着,打开main.js,添加store.js的引入,如下:

import Vue from 'vue'
import App from './App.vue'
import store from './store.js'

// 1. 导入 ant-design-vue 组件库
import Antd from 'ant-design-vue'
// 2. 导入组件库的样式表
import 'ant-design-vue/dist/antd.css'

Vue.config.productionTip = false
// 3. 安装组件库
Vue.use(Antd)

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

再接着打开store.js,添加axios请求json文件获取数据的代码,如下:

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    //所有任务列表
    list: [],
    //文本输入框中的值
    inputValue: 'AAA'
  },
  mutations: {
    initList(state, list) {
      state.list = list
    },
    setInputValue(state,value){
      state.inputValue = value
    }
  },
  actions: {
    getList(context) {
      axios.get('/list.json').then(({ data }) => {
        console.log(data);
        context.commit('initList', data)
      })
    }
  }
})

最后,代开App.vue文件,将store中的数据获取并展示:

<template>
  <div id="app">
    <a-input placeholder="请输入任务" class="my_ipt" :value="inputValue" @change="handleInputChange" />
    <a-button type="primary">添加事项</a-button>

    <a-list bordered :dataSource="list" class="dt_list">
      <a-list-item slot="renderItem" slot-scope="item">
        <!-- 复选框 -->
        <a-checkbox :checked="item.done">{{item.info}}</a-checkbox>
        <!-- 删除链接 -->
        <a slot="actions">删除</a>
      </a-list-item>

      <!-- footer区域 -->
      <div slot="footer" class="footer">
        <!-- 未完成的任务个数 -->
        <span>0条剩余</span>
        <!-- 操作按钮 -->
        <a-button-group>
          <a-button type="primary">全部</a-button>
          <a-button>未完成</a-button>
          <a-button>已完成</a-button>
        </a-button-group>
        <!-- 把已经完成的任务清空 -->
        <a>清除已完成</a>
      </div>
    </a-list>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'app',
  data() {
    return {
      // list:[]
    }
  },
  created(){
    // console.log(this.$store);
    this.$store.dispatch('getList')
  },
  methods:{
    handleInputChange(e){
      // console.log(e.target.value)
      this.$store.commit('setInputValue',e.target.value)
    }
  },
  computed:{
    ...mapState(['list','inputValue'])
  }
}
</script>

<style scoped>
#app {
  padding: 10px;
}

.my_ipt {
  width: 500px;
  margin-right: 10px;
}

.dt_list {
  width: 500px;
  margin-top: 10px;
}

.footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</style>
8.4.2完成添加事项Mutation★★★★★

首先,打开App.vue文件,给“添加事项”按钮绑定点击事件,编写处理函数

//绑定事件
<a-button type="primary" @click="addItemToList">添加事项</a-button>

//编写事件处理函数
methods:{
    ......
    addItemToList(){
      //向列表中新增事项
      if(this.inputValue.trim().length <= 0){
        return this.$message.warning('文本框内容不能为空')
      }

      this.$store.commit('addItem')
    }
  }

然后打开store.js编写addItem

export default new Vuex.Store({
  state: {
    //所有任务列表
    list: [],
    //文本输入框中的值
    inputValue: 'AAA',
    //下一个id
    nextId:5
  },
  mutations: {
    ........
    //添加列表项
    addItem(state){
      const obj = {
        id :state.nextId,
        info: state.inputValue.trim(),
        done:false
      }
      //将创建好的事项添加到数组list中
      state.list.push(obj)
      //将nextId值自增
      state.nextId++
      state.inputValue = ''
    }
  }
  ......
})
8.4.3完成删除事项Mutation★★★★★

首先,打开App.vue文件,给“删除”按钮绑定点击事件,编写处理函数

//绑定事件
<a slot="actions" @click="removeItemById(item.id)">删除</a>

//编写事件处理函数
methods:{
    ......
    removeItemById(id){
      //根据id删除事项
      this.$store.commit('removeItem',id)
    }
  }

然后打开store.js编写addItem

export default new Vuex.Store({
  ......
  mutations: {
    ........
    removeItem(state,id){
      //根据id删除事项数据
      const index = state.list.findIndex( x => x.id === id )
      // console.log(index);
      if(index != -1) state.list.splice(index,1);
    }
  }
  ......
})
8.4.4完成选中状态的改变Action★★★★★

首先,打开App.vue文件,给“复选”按钮绑定点击事件,编写处理函数

//绑定事件
<a-checkbox :checked="item.done" @change="cbStateChanged(item.id,$event)">{{item.info}}</a-checkbox>

//编写事件处理函数
methods:{
    ......
    cbStateChanged(id,e){
      //复选框状态改变时触发
      const param = {
        id:id,
        status:e.target.checked
      }

      //根据id更改事项状态
      this.$store.commit('changeStatus',param)
    }
  }

然后打开store.js编写addItem

export default new Vuex.Store({
  ......
  mutations: {
    ........
    changeStatus(state,param){
      //根据id改变对应事项的状态
      const index = state.list.findIndex( x => x.id === param.id )
      if(index != -1) state.list[index].done = param.status
    }
  }
  ......
})
8.4.5剩余项统计Getter★★★★★

打开store.js,添加getters完成剩余项统计

getters:{
  unDoneLength(state){
    const temp = state.list.filter( x => x.done === false )
    console.log(temp)
    return temp.length
  }
}

打开App.vue,使用getters展示剩余项

//使用映射好的计算属性展示剩余项

{{unDoneLength}}条剩余</span>

//导入getters
import { mapState,mapGetters } from 'vuex'
//映射
computed:{
  ...mapState(['list','inputValue']),
  ...mapGetters(['unDoneLength'])
}
8.4.6清除完成事项Mutation★★★★★

首先,打开App.vue文件,给“清除已完成”按钮绑定点击事件,编写处理函数

<!-- 把已经完成的任务清空 -->
<a @click="clean">清除已完成</a>

//编写事件处理函数
methods:{
  ......
  clean(){
    //清除已经完成的事项
    this.$store.commit('cleanDone')
  }
}

然后打开store.js编写addItem

export default new Vuex.Store({
  ......
  mutations: {
    ........
    cleanDone(state){
      state.list = state.list.filter( x => x.done === false )
    }
  }
  ......
})
8.4.7点击选项卡切换事项Getter★★★★★

打开App.vue,给“全部”,“未完成”,“已完成”三个选项卡绑定点击事件,编写处理函数
并将列表数据来源更改为一个getters。

<a-list bordered :dataSource="infoList" class="dt_list">
  ......
  <!-- 操作按钮 -->
  <a-button-group>
    <a-button :type="viewKey ==='all'?'primary':'default'" @click="changeList('all')">全部</a-button>
    <a-button :type="viewKey ==='undone'?'primary':'default'" @click="changeList('undone')">未完成</a-button>
    <a-button :type="viewKey ==='done'?'primary':'default'" @click="changeList('done')">已完成</a-button>
  </a-button-group>
  ......
</a-list>

//编写事件处理函数以及映射计算属性
methods:{
  ......
  changeList( key ){
    //点击“全部”,“已完成”,“未完成”时触发
    this.$store.commit('changeKey',key)
  }
},
computed:{
  ...mapState(['list','inputValue','viewKey']),
  ...mapGetters(['unDoneLength','infoList'])
}

打开store.js,添加getters,mutations,state

export default new Vuex.Store({
  state: {
    ......
    //保存默认的选项卡值
    viewKey:'all'
  },
  mutations: {
    ......
    changeKey(state,key){
      //当用户点击“全部”,“已完成”,“未完成”选项卡时触发
      state.viewKey = key
    }
  },
  ......
  getters:{
    .......
    infoList(state){
      if(state.viewKey === 'all'){
        return state.list
      }
      if(state.viewKey === 'undone'){
        return state.list.filter( x => x.done === false )
      }
      if(state.viewKey === 'done'){
        return state.list.filter( x => x.done === true )
      }
    }
  }
})
;