Vue.js初体验

认识Vue.js

Vue是一个渐进式的框架:渐进式意味着Vue可以作为一部分嵌入到原来的应用中

Vue中存在很多web开发中的常见功能:

  • 解耦合和数据
  • 可复用组件
  • 状态管理
  • 虚拟DOM

Vue.js安装

安装Vue的方式有很多:

1、CDN引入

<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>

2、下载和引入

https://cn.vuejs.org/js/vue.js
https://cn.vuejs.org/js/vue.min.js

3、npm安装

npm install vue

Vue.js初体验

1、Vue的初体验

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- Vue引入Vue -->
    <script src="../js/vue.js"></script>
</head>
<body>

<!-- 使用ID绑定使用{{xx}}绑定data中的数据并显示 -->
<div id="app">{{message}}</div>


<script>
    // let定义局部变量、var定义全局变量、const定义常量
    const app = new Vue({
        // 用于挂载要管理的元素
        el: '#app',
        // 定义数据
        data: {
            message: 'Hello'
        }

    })

</script>


</body>
</html>

2、Vue的列表演示

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../js/vue.js"></script>

  <div id="app">

    <ul>
      <li v-for="item in movies">{{item}}</li>
    </ul>


  </div>
</head>
<body>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      movies: ['海王', '星际穿越', '大话西游', '盗梦空间']
    }
  })
</script>
</body>
</html>

Vue.js基础语法

插值操作

1、Mustache

我们之前的双大括号其实有一个名字,叫做Mustache语法,也叫做双大括号语法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- Mustache语法中,不仅可以直接写变量,也可以写一些简单的表达式 -->
        <h1>{{firstName + ' ' + lastName}}</h1>
        <h1>{{couter*2}}</h1>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            firstName: 'bobe',
            lastName: 'bryant',
            couter: 2
        }
    })

</script>
</body>
</html>

2、一些基本指令

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>


    <style>
        /*
            v-cloak,cloak斗篷。
            有的时候卡住,没有来得及渲染,用户会看到{{message}}而不是立刻渲染出来的内容
            加入这个属性之后,在Vue解析之前会起作用,解析之后就不会起作用
            所以使用这个属性我们可以在Vue解析之前隐藏界面,让界面渲染之后才显示内容
        */
        [v-cloak]{
            display: none;
        }
    </style>
    <div id="app">
        <!-- v-once不会随着数据的改变而改变,也就是说只是加载一次 -->
        <h1 v-once>{{message}}</h1>

        <!-- v-html可以直接插入标签,就像是在HTML页面直接写标签一样 -->
        <h1 v-html="url"></h1>

        <!-- v-text和{{message}}一样,但是我们一般不使用,因为这种方式会直接覆盖标签内的内容,直接覆盖Hello -->
        <h1 v-text="message">Hello</h1>

        <!-- v-pre会直接将{{message}}显示出来,而不是解析{{message}}的内容 -->
        <h1 v-pre>{{message}}</h1>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: '你好',
            url: '<a href="https://www.baidu.com">百度一下</a>'
        }
    })

</script>
</body>
</html>

动态绑定属性和样式

刚才我们的插值属性是动态绑定的文本,但是假如我们想要动态绑定我们的属性(比如src),那么我们就需要v-bind

我们的双括号只能绑定到标签的内容方面,假如是属性则不能使用

v-bind的基本使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- 给某一个属性赋值 -->
        <img v-bind:src="imgURL" alt="图片">
        <a v-bind:href="baidu"></a>

        <!-- v-bind的语法糖,使用一个冒号就可以绑定属性 -->
        <a :href="baidu"></a>


    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            imgURL: 'https://gitee.com/howling/picgo/raw/master/img/background.png',
            baidu: 'https://www.baidu.com'
        }
    })

</script>
</body>
</html>

动态绑定属性

1、对象语法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <style>
        .active{
            color: red;
        }
    </style>

    <div id="app">
        <!--
            v-bind中可以加一个对象,这个对象使用一个花括号表示
            花括号中可以定义键值对,值确认键,然后用来控制属性的值

            从这个标签中,最后得到的结果我们可以看到,渲染出来的结果是:<h2 class="active">Hello</h2>
            也就是说只保留了active
        -->
        <h2 :class="{active: isActive,line: isLine}">{{message}}</h2>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            isActive: true,
            isLine: false
        }
    })

</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <style>
        .active {
            color: red;
        }
    </style>

    <div id="app">
        <!--
            v-bind还可以直接绑定一个函数或者计算属性,这里演示一下绑定函数的内容
        -->
        <h2 :class="getClass()">{{message}}</h2>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            isActive: true,
            isLine: false
        },
        methods: {
            /* 注意这里的返回值是this.不要忘记加上this. */
            getClass: function () {
                return {active: this.isActive, line: this.isLine}
            }
        }
    })

</script>
</body>
</html>

2、数组语法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <style>
        .active {
            color: red;
        }
    </style>

    <div id="app">
        <!--
            除了对象语法之外,还可以使用数组语法,但是这种方式不能动态改变,所以这种方式一般不会使用

            使用引号的使用会当成字符串解析,不使用引号会当成变量解析data中的内容
        -->
        <h2 :class="[a,'line']">{{message}}</h2>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            a: 'active'
        }
    })

</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <style>
        .active {
            color: red;
        }
    </style>

    <div id="app">
        <!--
            除了对象语法之外,还可以使用数组语法,但是这种方式不能动态改变,所以这种方式一般不会使用

            使用引号的使用会当成字符串解析,不使用引号会当成变量解析data中的内容
        -->
        <h2 :class="getClass()">{{message}}</h2>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            active: 'active'
        },
        methods: {
            getClass: function () {
                return [this.active];
            }
        }
    })

</script>
</body>
</html>

动态绑定样式

1、动态绑定style

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!--
            这是我们的基础的写法,只能固定在50px

            这里注意一件事情,假如key不加单引号不报错,那么可以不加单引号
            但是value必须要加上单引号才可以解析为原本的50px,否则会认为它是一个变量
         -->
        <h2 :style="{'font-size': '50px'}">{{message}}</h2>

        <!-- 动态决定字体大小和颜色 -->
        <h2 :style="{'font-size': fontSize+'px','color': fontColor}">{{message}}</h2>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            fontSize: '50',
            fontColor: 'red'
        }
    })

</script>
</body>
</html>

2、使用对象语法来代替颜色返回值

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- 动态决定字体大小和颜色 -->
        <h2 :style="getStyles()">{{message}}</h2>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            fontSize: '50',
            fontColor: 'red'
        },
        methods: {
            getStyles: function (){
                return {'font-size': this.fontSize+'px','color': this.fontColor}
            }
        }
    })

</script>
</body>
</html>

3、使用数组语法来代替样式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- 动态决定字体大小和颜色 -->
        <h2 :style="getStyles()">{{message}}</h2>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            baseStyle: {backgroundColor: 'red'},
            otherStyle: {color: 'blue'}
        },
        methods: {
            getStyles: function (){
                return [this.baseStyle,this.otherStyle]
            }
        }
    })

</script>
</body>
</html>

计算属性

在DOM中我们其实可以直接通过插值语法渲染出内容吗,但是假如我们想要对数据进行一些转换之后进行显示,或者要将多个数据结合起来进行显示,那么插值语法就有些缺陷了

1、计算属性基本使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- 使用这个方式有点长,很难受 -->
        <h2>{{firstName + lastName}}</h2>

        <!-- 将两个内容进行结合,但是使用方法的方式还是不太好,因为看起来很low -->
        <h2>{{getFullName()}}</h2>

        <!-- 使用计算属性进行两个内容的结合,可以十分方便,而且计算属性不需要加上括号 -->
        <h2>{{fullName}}</h2>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            firstName: 'Lebron',
            lastName: 'James'
        },
        methods: {
            getFullName: function () {
                return this.firstName + this.lastName
            }
        },
        // 计算属性,所以我们一般使用属性的方式起名字,比如getFullName就不如fullName
        computed: {
            fullName: function (){
                return this.firstName + this.lastName
            }
        }
    })

</script>
</body>
</html>

2、计算属性的复杂用法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        {{totalPrice}}
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            books: [
                {id: 110,name: 'Linux', price: 119},
                {id: 110,name: 'Java', price: 79},
                {id: 110,name: 'MySQL', price: 89},
                {id: 110,name: 'Vue', price: 100}
            ]
        },
        computed: {
            totalPrice: function (){
                let result = 0
                for (let i = 0; i < this.books.length;i++){
                    result+=this.books[i].price
                }
                return result
            }
        }
    })

</script>
</body>
</html>

计算属性还有一点比较高级,就是它会将得到的结果缓存起来,然后使用缓存的结果 但是method就会每次都会执行

3、计算属性的本质其实就是getter和setter

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        {{totalPrice}}
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            books: [
                {id: 110,name: 'Linux', price: 119},
                {id: 110,name: 'Java', price: 79},
                {id: 110,name: 'MySQL', price: 89},
                {id: 110,name: 'Vue', price: 100}
            ]
        },
        computed: {
            totalPrice: {
                get: function (){
                    return this.firstName + this.lastName
                },
                set: function (){
                }
            }
        }
    })

</script>
</body>
</html>

这才是计算属性的全写,刚才我们的其实是简写 但是计算属性一般没有set方法,set方法我们一般是删除掉的 所以我们直接就删掉了,就变为了我们上面的方法,也就是说set方法没有了


事件监听

事件监听,就是用户的点击、拖拽、键盘事件等,我们需要使用v-on来使用

1、基本语法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <h2>{{counter}}</h2>

        <!-- v-on绑定点击事件 -->
        <button v-on:click="increment">+</button>
        <button v-on:click="decrement">-</button>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            counter: 0
        },
        methods: {
            increment(){
                this.counter++
            },
            decrement(){
                this.counter--
            }
        }
    })

</script>
</body>
</html>

我们注意到,上面的方法的小括号省略了,省略小括号是有条件的: 1、必须在事件监听的时候,也就是使用v-on绑定事件的时候 2、方法不需要传递参数

2、语法糖

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <h2>{{counter}}</h2>

        <!-- v-on绑定点击事件,@可以代替v-on,也是语法糖 -->
        <button @click="increment">+</button>
        <button @click="decrement">-</button>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            counter: 0
        },
        methods: {
            increment(){
                this.counter++
            },
            decrement(){
                this.counter--
            }
        }
    })

</script>
</body>
</html>

3、v-on传递参数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- 事件定义时省略了小括号,但是函数需要一个参数,那么会自动传入事件本身event -->
        <button @click="btnClick">按钮1</button>
        <!-- 默认情况下,假如有参数那么event对象就不好使了,那么假如我们需要event对象就使用这种方式 -->
        <button @click="btnClick2('abc',$event)">按钮2</button>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        methods: {
            btnClick(obj){
                console.log(obj);
            },
            btnClick2(obj,event){
                console.log(obj+' -- '+event);
            }
        }
    })
</script>
</body>
</html>

假如在methods中定义方法,并且提供给事件调用的时候,那么方法的小括号可以不加 假如方法本身需要一个参数,那么不加入小括号会将原生事件event参数传递过去 假如需要同时传递多个参数,并且同时需要event时,可以通过$event传入

4、v-on的修饰符

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">

        <!-- 1、.stop 阻止外层事件 -->
        <div @click="divClick">
            DIVCLICK
            <!-- 假如我们点击了BUTTON,那么其实DIV上的方法也会被触发,我们显然不想这样 -->
            <button @click="btnClick">BUTTONCLICK</button>
        </div>

        <div @click="divClick">
            DIVCLICK
            <!-- 这个.stop就是v-on的修饰符,这个意思是说当我点击BUTTON时,只会执行Button的方法,而不会顺便执行DIV上的方法 -->
            <button @click.stop="btnClick">BUTTONCLICK</button>
        </div>

        <!-- 2、.prevent 阻止默认事件 -->
        <form action="baidu">
            <!-- 这个很明显会提交,但是假如我们不想让他提交,那么应该使用修饰符 -->
            <input type="submit" value="提交">
        </form>

        <br>

        <form action="baidu">
            <!-- .prevent会组织默认的事件,那么在这里它阻止了提交的事件 -->
            <input type="submit" value="提交" @click.prevent="submitClick">
        </form>


        <!-- 3、 .按键 监听键盘某个案件,比如.enter,比如.esc -->
        <input type="text" @keyup.enter="keyUp">

        <!-- 4、.once 执行一次 -->
        <button @click.once="once">Once</button>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        methods: {
            divClick(){
                console.log('DIVCLICK')
            },
            btnClick() {
                console.log('BUTTONCLICK')
            },
            submitClick(){
                console.log('阻止了默认提交')
            },
            keyUp(){
                console.log('Enter')
            },
            once(){
                console.log('once')
            }
        }
    })

</script>
</body>
</html>

条件判断

1、v-if

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <h2 v-if="isTrue">TRUE</h2>
        <h2 v-if="!isTrue">FALSE</h2>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            isTrue: false
        }
    })

</script>
</body>
</html>

2、v-else

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- v-if中的值为true,则会显示 -->
        <h2 v-if="isTrue">TRUE</h2>
        <!-- 当v-if不显示,则显示v-else -->
        <h2 v-else>FALSE</h2>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            isTrue: false
        }
    })

</script>
</body>
</html>

3、v-else-if

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <h2 v-if="score >=90">优秀</h2>
        <h2 v-else-if="score >=75">良好</h2>
        <h2 v-else-if="score >=60">及格</h2>
        <h2 v-else>不及格</h2>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            score: 60
        }
    })

</script>
</body>
</html>

虽然可以这样使用,但是不推荐在HTML中写复杂的逻辑判断

4、v-show

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!--
            除了v-if作为条件判断,v-show也可以判断是否进行显示
            但是v-show和v-if有点不同

            v-if:当我们的条件为false时,我们的元素根本不会存在DOM中,所以当true和false切换的时候,本质上是进行创建元素和删除元素
            v-show:当我们的条件为false时,元素存在DOM中但是多了一个display:none的属性,也就是说这个true和false切换本质上是显示和隐藏
         -->
        <h2 v-show="isShow">{{message}}</h2>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            isShow: true
        }
    })

</script>
</body>
</html>

v-if:当我们的条件为false时,我们的元素根本不会存在DOM中,所以当true和false切换的时候,本质上是进行创建元素和删除元素 v-show:当我们的条件为false时,元素存在DOM中但是多了一个display:none的属性,也就是说这个true和false切换本质上是显示和隐藏

当切换频率很低时,我们使用v-if,否则使用v-show


循环遍历

1、遍历数组

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- 1、在遍历的时候不使用下标 -->
        <ul>
            <li v-for="item in names">{{item}}</li>
        </ul>

        <!-- 2、在遍历的时候获取数组下标 -->
        <ul>
            <!-- 因为索引是从0开始的,所以要+1 -->
            <li v-for="(item,index) in names">{{index+1 + '-->' + item}}</li>
        </ul>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            names: ['why','Jack','Tom']
        }
    })

</script>
</body>
</html>

2、遍历对象

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- 1、遍历对象的value -->
        <ul>
            <!-- 因为对象是key-value形式,假如只获取一个值,那么就获取value -->
            <li v-for="item in info">{{item}}</li>
        </ul>

        <!-- 2、获取对象key-value -->
        <ul>
            <!-- 获取的时候是获取 (value,key) -->
            <li v-for="(item,key) in info">{{item + ' ' + key}}</li>
        </ul>

        <!-- 3、获取对象key-value-index -->
        <ul>
            <!-- 获取的时候是获取 (value,key) -->
            <li v-for="(item,key,index) in info">{{item + ' ' + key + ' '}}{{index+1}}</li>
        </ul>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            info: {
                message: 'Hello',
                name: 'Jack',
                age: 18,
                height: 188
            }
        }
    })

</script>
</body>
</html>

3、v-for的:key属性

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <ul>
            <!-- 
                加上这个:key属性就会增加性能,但是一定要写一个一一对应的东西
                有人喜欢绑定index,但是绑定index不太好
                假如数组中间要插入什么元素,index和item就不会一一对应了

                但是value作为key的话很有可能会有重复值,那就不能保证唯一性,会报错
                所以这里建议绑定一个唯一的、确定的、一一对应的值,id最好
             -->
            <li v-for="item in letters" :key="item">{{item}}</li>
        </ul>
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            letters: ['A','B','C','D','E']
        }
    })

</script>
</body>
</html>

官方建议加上这个:key,会提高性能

4、哪些数组方法是响应式的

1、push():在数组最后添加元素,可以一次性添加多个元素
2、pop():删除数组中最后一个元素
3、shift():删除数组中第一个元素
4、unshift():在数组最前面添加元素,可以一次性添加多个元素
5、splice(v1,v2,...v3):v1是从哪一个开始,v2是删除几个元素,v3是插入几个元素(可变参数)
6、sort():排序
7、reverse():反转

并不是所有方法能够做到响应式的,比如通过索引直接改变数组不是响应式的,但是可以使用Vue.set()来代替 Vue.set(v1,v2,v3):v1是要改变的对象,v2是要改变的下标,v3是要改变为什么东西


表单绑定(双向绑定)

表单在实际开发中十分重要

1、基本使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- 使用 v-model 实现双向绑定 -->
        <input type="text" v-model="message">

        {{message}}
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        }
    })

</script>
</body>
</html>

v-model其实是一个语法糖,他的背后本质上包含v-bind和v-on

2、radio结合双向绑定

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- radio 其实v-model可以替代name属性,下面的例子中就不写name了-->
        <label for="male">
            <input type="radio" id="male" name="sex" v-model="sex" value="男">
        </label>

        <label for="female">
            <input type="radio" id="female" name="sex" v-model="sex" value="女">
        </label>
        {{sex}}

        <br>
        ----
        <br>

        <!-- checkbox -->
        <label for="lesson">
            我同意以上协议 <input type="checkbox" id="lesson" v-model="isAgree">
            <br>
            <button :disabled="!isAgree">下一步</button>
        </label>


        <br>
        ----
        <br>

        <select name="Fruits" id="" v-model="fruit">
            <option value="苹果">苹果</option>
            <option value="香蕉">香蕉</option>
            <option value="菠萝">菠萝</option>
            <option value="榴莲">榴莲</option>
        </select>
        {{fruit}}
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            sex: '男',
            isAgree: false,
            fruit: '香蕉'
        }
    })

</script>
</body>
</html>

3、修饰符

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- 1、修饰符:lazy,会让绑定不会这么频繁,会在输入框失去焦点或者敲回车时绑定过来 -->
        <input type="text" v-model.lazy="message">{{message}}
        <br>

        <!-- 2、修饰符:number,输入框只能输入数字 -->
        <input type="number" v-model.number="age">

        <!-- 3、trim,剪切两边的空格 -->
        <input type="text" v-model.trim="message">
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello',
            age: 0
        }
    })

</script>
</body>
</html>

4、v-model的本质

之前我们说过,v-model其实就是一个语法糖,它是v-bind:valuev-on:input的结合使用,其实我们也可以这样做 并且我们还可以实现一些更复杂的操作,但是我们首先还是看一下如何使用v-bind:valuev-on:input来替代吧

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- 使用 v-bind:value 和 v-on:input 来代替 -->
        <input type="text" :value="message" @input="message = $event.target.value">
        {{message}}
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        }
    })

</script>
</body>
</html>

5、v-model进行更多操作

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- 使用 v-bind:value 和 v-on:input 来代替 -->
        <input type="text" :value="message" @input="inputmessage">
        {{message}}
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        methods: {
            inputmessage(event){
                this.message = event.target.value
                console.log('进行更多操作')
            }
        }
    })

</script>
</body>
</html>

属性监听

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- 使用 v-bind:value 和 v-on:input 来代替 -->
        <input type="text" v-model="message">
        {{message}}
    </div>
</head>
<body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        // watch用于监听某一个属性的改变,只要这个属性改变了,那么就跳转到这个函数中,这个方法名字和属性名字是一样的
        watch: {
            message(newValue, oldValue) {
                console.log('newValue:' + newValue + '   oldValue:' + oldValue)
            }
        }
    })

</script>
</body>
</html>

组件化

组件化概述

一个人面对复杂问题的时候,可以将这些问题拆分成为多个小问题,然后一个一个解决

组建化也是这样的思想,假如我们将一个页面中所有的处理逻辑全部都放在一起,那么处理起来就会非常复杂

我们将一个页面拆分称为一个个的小的功能块,每一个功能块完成属于自己的这部分功能,那么整个页面的维护和管理就十分容易

Vue中的组件化相当于一棵组件树,我们可以拆分唱一个个的独立的可复用的小组建来构建我们的应用

组件基本使用

构成组件的基本步骤

1、创建组件构造器:调用Vue.extend()方法创建组件构造器

2、注册组件:调用Vue.component()方法注册组件

3、使用组件:在Vue实例范围内使用组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- 3、使用组件 -->
        <cpn></cpn>
    </div>
</head>
<body>
<script>

    // 1、创建组件构造器对象
    const cpnConstruct = Vue.extend({
        template: `
            <div>
                <div>组件初始化</div>
                <p>内容</p>
            </div>`
    })

    // 2、注册组件
    Vue.component('cpn',cpnConstruct)

    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        }
    })

</script>
</body>
</html>

事实上,这种方法在Vue 2.x的文档中就已经看不到了,但是这个是我们最基础的创建方式,要明白

全局组件和局部组件

1、刚才我们使用Vue.component()的方式叫做全局组件,可以在多个Vue实例下面进行使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <cpn></cpn>
    </div>
    <div id="app1">
        <cpn></cpn>
    </div>
</head>
<body>
<script>

    const cpnConstruct = Vue.extend({
        template: `
            <div>
                <div>组件初始化</div>
                <p>内容</p>
            </div>`
    })

    Vue.component('cpn',cpnConstruct)

    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        }
    })

    const app1 = new Vue({
        el: '#app1'
    })

</script>
</body>
</html>

2、局部组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <cpn></cpn>
    </div>
</head>
<body>
<script>

    const cpnConstruct = Vue.extend({
        template: `
            <div>
                <div>组件初始化</div>
                <p>内容</p>
            </div>`
    })

    const app = new Vue({
        el: '#app',
        // 在这里进行注册,那么就是局部组件,只能在这个Vue下面进行使用
        components: {
            cpn: cpnConstruct
        }
    })

</script>
</body>
</html>

父组件和子组件

1、在父组件使用子组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <cpn2></cpn2>
    </div>
</head>
<body>
<script>

    // 1、创建第一个组件构造器
    const cpnC1 = Vue.extend({
        template: `
            <div>
                <h2>标题1</h2>
                <p>内容1</p>
            </div>
        `
    })
    // 2、创建第二个组件构造器,注意在这里注册cpnC1,然后在这里使用
    const cpnC2 = Vue.extend({
        template: `
            <div>
                <h2>标题2</h2>
                <p>内容2</p>

                <cpn1></cpn1>
            </div>
        `,
        components: {
            cpn1: cpnC1
        }
    })

    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        // 我们只需要注册cpnC2,因为cpnC1已经在cpnC2中注册过了
        components: {
            cpn2: cpnC2
        }
    })

</script>
</body>
</html>

注意,在这里中,cpn2是父组件,cpn1是子组件 并且子组件只在父组件中注册,则必须在父组件中使用,出了父组件的范围是不能使用的,它是一个局部的作用域 如果想在外面使用,就在对应的作用域中进行注册

2、注册组件语法糖

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <cpn1></cpn1>
        <cpn2></cpn2>
    </div>
</head>
<body>
<script>

    // 1、全局组件的注册使用组件注册语法糖,它底层调用的其实就是 Vue.extend(),只不过使用语法糖给简化了
    Vue.component('cpn1',{
        template: `
            <div>全局组件语法糖</div>
        `
    })



    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        // 2、局部组件语法糖,它底层调用的其实就是 Vue.extend(),只不过使用语法糖给简化了
        components: {
            'cpn2': {
                template: `<div>局部组件语法糖</div>`
            }
        }
    })

</script>
</body>
</html>

模板分离

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

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


    <!-- 编写模板写法一,使用 text/x-template,起一个ID -->
    <script type="text/x-template" id="cpn1">
        <div>模板分离写法一</div>
    </script>


    <!-- 编写模板写法二,不需要script标签,只需要template标签和一个ID即可 -->
    <template id="cpn2">
        <div>模板分离写法二</div>
    </template>
</head>
<body>
<script>

    // 1、注册全局组件,注意template绑定的数据为模板的ID
    const cpn1 = Vue.component('cpn1',{
        template: '#cpn1'
    })

    // 2、注册全局组件,注意template绑定的数据为模板的ID
    const cpn2 = Vue.component('cpn2',{
        template: '#cpn2'
    })

    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        }
    })

</script>
</body>
</html>

建议使用template的写法,简单,局部组件也可以使用这种方式

组件data

组件不可以访问Vue实例数据

VUE - 图1

组件不能访问到Vue实例中的数据,也就是说上图的内容访问是不允许的,组件应该有自己保存数据、函数的地方

退一步说,即使组件中能够访问Vue实例的数据,但是我们想一下,一个系统中应该会有多少个组件?这些组件的内容全部都放到Vue实例,那么Vue实例中的数据将会鱼龙混杂,杂乱不堪

所以不管从什么角度来分析,Vue中的组件实例应该有它自己的data

组件的data域

组件中也有data类型,但是和Vue实例中的data域不同,它不能是一个对象类型,而是一个方法类型

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

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

    <template id="temp">
        <div>{{title}}</div>
    </template>
</head>
<body>
<script>

    const temp = Vue.component('temp',{
        template: '#temp',
        components: {},
        // 注意,我们可以看到在template中的data是一个方法而不是一个对象
        data(){
            return{
                title: 'ABC'
            }
        }
    })

    const app = new Vue({
        el: '#app',
        // 我们看到,Vue实例中的data域是一个对象类型
        data: {
            message: 'Hello'
        }
    })

</script>
</body>
</html>

注意观察Vue实例和模板实例中data的区别,一个是对象一个是方法

这个组件其实很像Vue实例,它也有data、methods等,Vue实例有的它基本都有

为什么组件中的data必须是一个函数

那么我们说,为什么组件中的data必须是一个函数?

因为我们说,假如不设计成为函数,那么data中的数据就变为了公共的对象了,设计成为函数,那么data中的数据就完全属于这一个模板对象,而不是这个模板的全体对象

看下面这个例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

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

    <template id="temp">
        <div>
            {{counter}}
            <button @click="increment">+</button>
            <button @click="decrement">-</button>
        </div>
    </template>
</head>
<body>
<script>


    const temp = Vue.component('temp',{
        template: '#temp',
        data(){
            return{
                counter: 0
            }
        },
        methods: {
            increment() {
                this.counter++
            },
            decrement() {
                this.counter--
            }
        }

    })


    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        }
    })

</script>
</body>
</html>

注意看,上面的三个<temp></temp>是三个temp实例,每一个都有自己的data函数,而函数中的data是只属于temp的

所以会出现这种情况:数字可以不一致,也就是说data内容不会被共享 VUE - 图2

组件是需要复用的,但是复用的意思是在每一个地方需要有一个自己的逻辑,而不是在所有地方复用一个数据

父子组件通信

刚才说子组件不能引用父组件或者Vue实例中的数据,所以我们有的时候需要使用父组件传递给子组件

Vue有两种方式进行父子组件中的通信:

  • 通过props从父组件向子组件通信
  • 通过事件从子组件向父组件通信

也就是说父组件和子组件之间,都可以进行相互通讯,都有不同的方式

父组件向子组件通信

我们首先将Vue实例作为父组件,另开一个新的组件作为子组件,让父组件向子组件通信数据

1、使用数组的方式进行通信

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!--
            4、使用子组件
            从这里可以看到,使用v-bind绑定了cmovies这个变量,然后值是movies
            这就接受到了父组件传递过来的movies内容
        -->
        <cpn :cmovies="movies" :cmessage="message"></cpn>
    </div>


    <template id="cpn">
        <div>
            <!-- 3、使用子组件中负责接受的变量 -->
            {{cmessage}}
            <br>
            <ul>
                <li v-for="item in cmovies">{{item}}</li>
            </ul>
        </div>
    </template>
</head>
<body>
<script>

    /* 2、子组件 */
    const cpn = {
        template: '#cpn',
        /*
            通过这个props接收来自父组件的数据
            我们可以看到有一个cmovies的字符串,其实可以把这个字符串看成一个变量,用来接受数据
            可以看到还有一个cmessage,那么这个cmessage就是接受的父组件的message
        */
        props: ['cmovies','cmessage']
    }

    /* 1、父组件 */
    const app = new Vue({
        el: '#app',
        data: {
            /* 将message传递到子组件中 */
            message: 'Hello',
            /* 将movies传递到子组件中 */
            movies: ['海王','海贼王','海尔兄弟']
        },
        components: {
            // 注册子组件,这个写法在之前讲过
            cpn
        }
    })

</script>
</body>
</html>

注意一个巨大的问题,子组件props中不可以进行大写,否则可能会出现一些奇怪的问题,因为v-bind不支持驼峰

但是假如我就是要在props使用驼峰,那么在v-bind中也可以用 比如在props中我定义一个myMessageCode,在v-bind中应该是my-message-code

2、使用对象方式传递

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!--
            接受到了父组件传递过来的内容
        -->
        <cpn :cmovies="movies" :cmessage="message"></cpn>
    </div>


    <template id="cpn">
        <div>
            <!-- 使用这个变量 -->
            {{cmessage}}
            <br>
            <ul>
                <li v-for="item in cmovies">{{item}}</li>
            </ul>
        </div>
    </template>
</head>
<body>
<script>

    /* 子组件 */
    const cpn = {
        template: '#cpn',
        /*
            注意,这次我们使用的类型是对象类型
            使用对象比使用数组有好处,它不仅可以传递值,还可以指定值的类型,可以对数据做验证
            它支持:Array、String、Number、Boolean、Object、Date、Function、Symbol类型
            甚至还可以提供一些默认值,还可以设置这个变量是不是必须传递
        */
        props: {
            /* 指定了cmovies为Array类型 */
            cmovies: Array,
            /* 
                指定了cmessage的类型可以为String或者Number,默认是"ABC",传递值的时候必须传递这个值
            */
            cmessage: {
                type: [String,Number],
                default: "ABC",
                required: true
            }
        }
    }

    /* 父组件 */
    const app = new Vue({
        el: '#app',
        data: {
            /* 将message传递到子组件中 */
            message: 'Hello',
            /* 将movies传递到子组件中 */
            movies: ['海王','海贼王','海尔兄弟']
        },
        components: {
            // 注册子组件,这个写法在之前讲过
            cpn
        }
    })

</script>
</body>
</html>

注意了,我们建议使用对象的方式来进行使用,因为对象的功能十分多

我们的参数的默认值为一个对象或者是一个数组的时候,他的默认值必须为一个函数而不能是一个空的数组或者Object

<script>

 /* 子组件 */
 const cpn = {
     template: '#cpn',
     props: {
         cmovies: {
             type: Array,
             /* 注意看这里,假如type为Array或者Object,那么default必须为一个函数 */
             default(){ return[] }
         },
         cmessage: String
     }
 }

 /* 父组件 */
 const app = new Vue({
     el: '#app',
     data: {
         message: 'Hello',
         movies: ['海王','海贼王','海尔兄弟']
     },
     components: {
         cpn
     }
 })

</script>

子组件传递给父组件

一般来说,子组件传递数据是通过某一个事件传递給的父组件

VUE - 图3

在这个页面中,我点击热门推荐,这是在子组件中的事件,然后我通过这个点击事件带给父组件,让父组件去请求数据,然后再赋值给子组件

1、子组件传递给父组件基本使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <!-- 3、通过 v-on 监听事件,只不过这个是监听我们自己定义的事件,而且注意这里的cpnclick和第二步的名称相同 -->
        <cpn @itemClick="cpnclick"></cpn>
    </div>


    <template id="cpn">
        <div>
            <!-- 1、我们像正常那样绑定一个事件 -->
            <button v-for="item in categories" @click="btnclick(item)">{{item.name}}</button>
        </div>
    </template>
</head>
<body>
<script>


    const cpn = {
        template: '#cpn',
        data() {
            return {
                categories: [
                    {id: 'A', name: '热门推荐'},
                    {id: 'B', name: '手机数码'},
                    {id: 'C', name: '家用家电'},
                ]
            }
        },
        methods: {
            btnclick(item) {
                /* 2、通过这个 this.$emit 传递给父组件,两个参数分别为:发送给父组件事件的名字、事件的参数*/
                this.$emit('itemclick', item)
            }
        }
    }


    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        components: {
            cpn
        },
        /* 4、调用我们的函数,同时注意我们也不要使用驼峰来进行方法的定义 */
        methods: {
            cpnclick(item) {
                console.log(item.name);
            }
        }
    })

</script>
</body>
</html>

注意我们这里的第三步仍然没有填写参数,这是因为我们默认将item参数传递过去了

父组件和子组件的访问方式

我们现在想让父组件直接拿到子组件,然后调用子组件的方法,或者让子组件拿到父组件,然后对父组件进行操作

这种方式我们可以使用父子组件的访问方式

1、父访问子:$children或者$refs(reference)

2、子访问父:$parent

3、子组件直接访问根组件:Vue实例

父访问子

父组件中可能有很多子组件,那么它拿到的可能是一个数组

1、使用$children基本使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>


</head>
<body>

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

    <button @click="btnclick">按钮点击</button>
</div>

<template id="cpn">
    <div>子组件</div>
</template>


<script>


    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        methods: {
            btnclick() {
                console.log(this.$children)
                this.$children[0].showmessage()
            }
        },
        components: {
            cpn: {
                template: '#cpn',
                methods: {
                    showmessage() {
                        console.log('showMessage')
                    }
                }
            }
        }
    })

</script>
</body>
</html>

组件对象就是VueComponnet的类型,他应该是一个VueComponnet数组 然后我们就可以获取任意的子组件来调用子组件的内容

2、使用$refs使用子组件

在开发中,我们使用下标来拿取内容是十分不友好的,因为它可能会出现变动,所以我们应该使用另外的方法拿到子组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>


</head>
<body>

<div id="app">
    <cpn ref="A"></cpn>
    <cpn ref="B"></cpn>
    <cpn ref="C"></cpn>

    <button @click="btnclick">按钮点击</button>
</div>

<template id="cpn">
    <div>子组件</div>
</template>


<script>


    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        methods: {
            btnclick() {
                /* 使用我们的key-value中的key拿到准确的一个值 */
                console.log(this.$refs.A.showmessage())
            }
        },
        components: {
            cpn: {
                template: '#cpn',
                methods: {
                    showmessage() {
                        console.log('showMessage')
                    }
                }
            }
        }
    })

</script>
</body>
</html>

这种情况适合我们拿到精准的某一个子组件才可以 ref相同的时候,后面的覆盖前面的

子组件访问父组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <cpn></cpn>
    </div>
</head>
<body>

<template id="cpn">
    <div>
        子组件
        <button @click="btnclick">访问父组件</button>
    </div>
</template>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        methods: {
            parentclick(){
                console.log('父组件')
            }
        },
        components: {
            cpn: {
                template: '#cpn',
                methods: {
                    btnclick() {
                        // 访问父组件
                        console.log(this.$parent.parentclick())
                    }
                }
            }
        }
    })

</script>
</body>
</html>

访问根组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <cpn></cpn>
    </div>
</head>
<body>

<template id="cpn">
    <div>
        子组件
        <button @click="btnclick">访问根组件</button>
    </div>
</template>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        methods: {
            vueclick(){
                console.log('根组件')
            }
        },
        components: {
            cpn: {
                template: '#cpn',
                methods: {
                    btnclick() {
                        // 访问父组件
                        console.log(this.$root.vueclick())
                    }
                }
            }
        }
    })

</script>
</body>
</html>

插槽

为什么要使用插槽

插槽:slot,插槽的作用就是让我们的程序具备更多的扩展性

比如我们的电脑上有一个usb,这就是一个插槽,可以插入U盘、硬盘、手机…

所以插槽在Vue中,插槽中的代码并不是写死的,而是根据外界的内容进改变

插槽就可以让我们的程序拥有更多的扩展性,抽取共性,保留不同,这就是插槽的作用

插槽基本使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">

        <cpn>
            <!-- 2、注意,这个按钮就是我们想要向插槽里面放置的内容 -->
            <button>按钮</button>
        </cpn>

        <cpn>
            <!-- 3、这个插槽我忽然不想使用按钮,想搞一个文本 -->
            <h2>呵呵</h2>
        </cpn>
    </div>
</head>
<body>


<template id="cpn">
    <div>
        <!-- 1、一般的组件是永远存在的 -->
        <h2>一般的组件</h2>

        <!--
            插槽,可以随着外部的改变动态改变
            比如今天想要一个按钮,明天想要一个文本,就可以使用这个来实现
        -->
        <slot></slot>
    </div>
</template>
<script>

    const cpn = {
        template: '#cpn'
    }

    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        components: {
            cpn
        }
    })

</script>
</body>
</html>

假如我只有一个插槽,但是给了多个内容:比如给了一个button又给了一个文本又给了一个链接…. 这多个内容会当做是一个插槽中的内容,全部显示

2、插槽中的默认值

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">

        <!-- 2、不指定内容就使用默认值 -->
        <cpn></cpn>

        <cpn>
            <!-- 3、指定内容那么就替换 -->
            <h2>呵呵</h2>
        </cpn>

        <cpn></cpn>
    </div>
</head>
<body>


<template id="cpn">
    <div>
        <h2>一般的组件</h2>
        <slot>
            <!-- 1、插槽里面还可以自定义按钮,假如传过来的有内容,那么使用传过来的内容,假如没有就使用默认值 -->
            <button>按钮</button>
        </slot>
    </div>
</template>
<script>

    const cpn = {
        template: '#cpn'
    }

    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        components: {
            cpn
        }
    })

</script>
</body>
</html>

具名插槽

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <cpn>
            <!-- 2、使用slot属性替换内容,而且是替换指定的插槽内容 -->
            <h2 slot="center">CENTER</h2>
        </cpn>
    </div>
</head>
<body>


<template id="cpn">
    <div>
        <h2>一般的组件</h2>

        <!-- 1、使用name属性绑定唯一的插槽 -->
        <slot name="left">
            <button>按钮</button>
        </slot>
        <slot name="center"><p>P标签</p></slot>
        <slot name="right"><span>Span标签</span></slot>
    </div>
</template>
<script>

    const cpn = {
        template: '#cpn'
    }

    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        components: {
            cpn
        }
    })

</script>
</body>
</html>

注意了,我们的插槽假如不指定名字,会全部替换成为一样的内容

编译作用域

作用域的意思就是说这个东西可以作用的范围,我们看一下下面这个代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <cpn></cpn>
        <cpn><h2 v-show="isShow">不显示</h2></cpn>
    </div>
</head>
<body>

<template id="cpn">
    <div>
        <h2 v-show="isShow">显示</h2>
        <slot></slot>
    </div>
</template>
<script>

    const cpn = {
        template: '#cpn',
        data() {
            return {
                isShow: true
            }
        }
    }

    const app = new Vue({
        el: '#app',
        data: {
            isShow: false
        },
        components: {
            cpn
        }
    })

</script>
</body>
</html>

其实结果很清晰了,模板中定义的属性作用域范围就是模板中的,Vue实例中定义的属性作用域就是Vue实例的

官方给了一条准则:父模板中使用父模板的,子模板中使用子模板的

作用域插槽

作用域他的目的就是就是父组件替换插槽的标签,但是内容由子组件提供

也就是说样式由父组件提供,内容由子组件提供

我们先看一个需求:

子组件中包含一组数据:[JavaScript,Python,Go,C++],这个数据需要在多个作用域中展示

但是展示的方式不同:有的页面要求水平展示、有的页面要求列表展示、有的页面要求直接展示数组

那么我直接通过作用域插槽即可使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../js/vue.js"></script>

    <div id="app">
        <cpn>
            <!--
                3、在vue2.5.x以下的时候,这里必须使用template,但是到了之后就可以使用别的组件了,比如div
                而且 slot-scope="slot" 这个属性十分重要,就是声明作用域范围是slot,这样就可以在这个作用域中拿到数据了
                在slot作用域中定义样式用于数据展示,slot.data中的data就是我们v-bind:data="languages"中的data
            -->
            <template slot-scope="slot">
                <span v-for="item in slot.data">{{item}}  -  </span>
            </template>
        </cpn>

        <cpn>
            <template slot-scope="slot">
                <span v-for="item in slot.data">{{item}}  *  </span>
            </template>
        </cpn>
    </div>
</head>
<body>

<template id="cpn">
    <div>
        <!--
            2、使用 v-bind绑定数据
            这个data是随便写的,languages数据是从当前作用域(子组件作用域)获取的
        -->
        <slot :data="languages"></slot>
    </div>
</template>

<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        components: {
            cpn: {
                template: '#cpn',
                data(){
                    return{
                        // 1、在子组件中定义一些数据,要求展示这些数据
                        languages: ['JavaScript','Java','Go','C++']
                    }
                }
            }
        }
    })

</script>
</body>
</html>

为了避免复杂,在上面我没有在slot中定义默认样式,但是我们知道这个可以用


模块化开发

模块化基础

前端发展到现在,已经有了很多的模块化规范,比如CommonJS、AMD、CMD、ES6 的Modules等

无论是哪种规范,都有导入、导出操作,只不过语法层面可能不太一样

在前端,模块化其实就相当于Java中的一个类,导入就是代表导入了一个对应的包名,只不过多了一个导出的过程,导出别人才能使用

CommonJS

NodeJS实现了CommonJS的规范,Node.JS的实现方式是比较出名的

在CommonJS中的导入和导出方式:

1、CommonJS的导出

module.exports = {
    flag: true,
    test(a,b){
        return a + b;
    },
    demo(a,b){
        return a * b;
    }
}

module.exports后面跟上一个对象,这个对象的所有内容都会导出

2、CommonJS的导入

let {flag,test,demo} = require('./aaa.js')

test(1,2)
demo(2,3)

导入需要require(文件路径),然后通过变量接受 文中的{flag,test,demo}是直接进行的解析,我们也可以定义一个变量然后通过变量获得 比如:let A = require('./aaa.js')A.test(1,2)

Modules

ES6的模块化会自动开启文件检测,ES6的模块化新增了两个内容

1、Modules的导出:export

导出方式一

let flag = true

function sum(a, b) {
    return a + b;
}

export {
    flag,sum
}

导出方式二

export let num1 = 1000
export let height1 = 1.88

export function sum(a, b) {
    return a + b;
}

导出方式三,经常使用,注意export default在一个模块中只能存在一个

export let flag = true

let name = 'Name'

export {
    name
}

const address = 'Address'
/* 这个default每一个export的JS只能有一个,等到后面引入的时候可以任意起一个名字来接受 */
export default {
    address
}

2、Modules的导入:import

导入方式一,这个flag和sum必须和export的名字相同

import {flag,sum} from './01-Module模块化开发export.js'

console.log(sum(1,2))

导入方式二:通常情况下我们都不想让别人把这个名字起名,导入方式一对应的flag和sum必须叫做这个名字 但是我们并不想要,所以我们这个导入方式二对应的是export default,可以随意使用名字来接受

import addr from './01-Module模块化开发export.js'

console.log(addr.address);

导入方式三

/* 统一全部导出 */
import * as a from './01-Module模块化开发export.js'

/* 获得export default中的内容 */
console.log(a.default.address);

/* 获得其他的内容 */
console.log(a.flag);

3、使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--
    这个type="module"代表的是模块化开发,他的作用是让他的变量只能是这个文件中的作用域
    比如在这个JS中使用var定义变量,其他的地方也获取不到
-->
<script src="01-Module模块化开发import.js" type="module"></script>

</body>
</html>

这个Module的语法是浏览器支持的,支持ES6的浏览器可以直接解析


Webpack

什么是webpack

它是一个JavaScript静态模块化打包工具,也就是模块化和打包

比如我们开发中有很多内容,比如.js.sass.less.jpg等文件,有一些文件浏览器并不能识别,所以我们需要一些工具来将文件内容打包成为浏览器可以识别的内容

前端模块化我们刚才知道很多的内容,比如AMD、CMD、CommonJS、ES6,但是这些模块化中除了ES6的Module都不被浏览器支持,但是使用webpack之后,会将所有的规范都转换为浏览器可以支持的内容

webpack安装

1、需要node.js环境

2、全局安装webpack(先指定 3.6.0看一下脚手架2.x):npm install webpack@3.6.0 -g

注意这里安装3.6.0,因为后续的有很多命令更改了

3、局部安装webpack(后续需要):--save-dev是开发时依赖,项目打包后不需要继续使用

webpack起步

首先我们介绍一下目录结构:srcdist

  • src:我们在程序编写的,开发的目录
    • main.js/index.js:入口,使用webpack之后,JS文件可以使用任何模块化规范了
    • 其他文件
  • dist:我们在打包之后放到dist中,打包的目录
  • index.html:引用JS文件

    注意了,我们引用的不是main.js文件,因为我们不知道在开发过程中使用的什么模块化规范开发的 所以我们需要使用webpack将所有src下的内容打包,webpack会在dist生成最终的一个js文件,我们需要引用那个文件

我们来走一遍流程

1、在src/js/mathUtils.js有以下内容

module.exports = {
    sum(a,b){
        return a + b;
    }
}

注意这个是CommonJS的语法,所以这样也可以证明webpack帮助我们解析语法了

2、在src/main.js中有以下内容

let {sum} = require('./js/mathUtil.js')

console.log(sum(1, 2))

我们看到main.js没有放到js文件夹下面,这是因为我们通常都不把入口放到文件夹下面

3、在终端中输入:webpack ./src/main.js ./dist/bundle.js

在这里注意,假如webpack版本不是3.6.0,那么命令会有所区别,甚至还要别的依赖

4、在index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="./dist/bundle.js"></script>
</body>
</html>

webpack配置

有时候我们其实并不想使用一长串的命令webpack ./src/main.js ./dist/bundle.js

有的时候我只想要简简单单地写一个webpack,让他自动打包到dist/bundles.js

那么这种方式看起来没有定义要打包的内容和输出的路径,所以我们需要使用webpack的配置文件,来告诉他内容

首先我们定义一个文件:webpack.config.js,位置和index.html同级

然后在里面写如下内容

/*
    这个path直接去node里面去找了,不需要我们写
    但是我们需要首先进行项目的 npm init来初始化
    是否初始化成功,就看项目中有没有 package.json文件
    只要有这个文件,那么就代表我们已经进行了初始化
*/
const path = require('path')

/* 一个CommomJS的导出写法 */
module.exports = {
    /* 需要打包的文件 */
    entry: './src/main.js',
    /* 输出的路径和文件,我们需要利用一个对象 */
    output: {
        /*
            path必须是一个绝对路径,不能是相对路径,Node语法动态获取路径
            path.resolve用来将两个字符串拼接
            __dirname(双下划线)是node用来获取当前上下文路径的内容,然后拼接上dist
        */
        path: path.resolve(__dirname,'dist'),
        /* 输出的文件名字 */
        filename: 'bundle.js'
    }
}

VUE - 图4

因为需要使用到node中的语法,所以进行一次初始化

等到进行npm init初始化之后,会出现一个package.json文件,这个文件是用来告诉我们这个项目的一些信息的 比如项目名称、版本、描述、作者、依赖等

经过了上面的内容,我们只需要敲一个webpack命令就直接可以打包了

真实项目中的打包

其实在真实项目中,我们其实并不需要使用webpack,而是npm run build命令打包

目前看起来好像webpack更加简单,但其实到后面它很麻烦,反而npm run build命令更加简单

那么我们应该如何使用npm run build这个命令呢?那么就需要将这两个命令进行映射了

1、在上面我们进行初始化的时候有一个package.json文件,打开它

2、里面有一个scripts

这个scripts其实就是npm的执行命令的地方,假如我执行命令npm run build 那么npm会到这个scripts里面找到key为build的命令然后去执行 那么我们进行如下改造

{
  "name": "maple",
  "version": "1.0.0",
  "description": "webpack学习",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
  "author": "",
  "license": "ISC"
}

但是这种方式和直接使用有些区别 我们有一个全局的webpack,通常项目中有一个项目的webpack 那么假如我们使用npm run install这个命令的时候,它会优先寻找项目的webpack去打包,而直接使用webpack用到的是全局的node

所以我们安装项目中的命令是:npm install webpack@3.6.0 --save-dev -dev的意思就是开发时依赖,当然我们还有一个运行时依赖,以后再说


webpack中的loader

webpack中css的使用

loader

loader是webpack中一个非常核心的概念,对于webpack来说,它本身是不具备处理css、图片、typescript等东西的能力

必须要使用loader对webpack进行扩展

1、src/css/normal.css,创建这个css文件,编写一些内容,将css当成模块

2、在main.js中引入css依赖:require('./css/normal.css')

3、安装loader

loader有很多,我们要根据不同的需求选择不同的loader,附上链接:[https://www.webpackjs.com/loaders/](https://www.webpackjs.com/loaders/) 我们在里面寻找样式的loader—>css loader,但是使用的时候我们要注意安装版本,版本不一致有可能会报错 这里我们要安装2.0.2npm install --save-dev css-loader@2.0.2

但是只有这一个loader还没有用,这个loader只是加载用到的,解析css我们还要再装一个loader:style-loader npm install --save-dev style-loader@0.23.1

4、更改webpack.config.js,按照官网上的内容更改,其实就是加入module模块中的rules

const path = require('path')

module.exports = {
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname,'dist'),
        filename: 'bundle.js'
    },
    module: {
        /* 增加一些规则 */
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader','css-loader' ]
            }
        ]
    }

}

webpack读取rules是从右向左读取,所以它会先读取css-loader,加载css,然后读取style-loader,添加样式 所以这个顺序没错

5、再次尝试打包npm run build,结果应该是成功了


webpack中less的处理

假如我们想在项目中使用less、scss、stylus等,那么我们也可以使用webpack对这些进行处理

less算是css的延伸,比css简单

1、less文件

@fontSize: 50px;
@fontColor: orange;

body{
  font-size: @fontSize;
  color: @fontColor;
}

2、在main.js中依赖less

require('./css/special.less')

3、因为没有less解析器,所以要加载less-loader:npm --save-dev install less-loader@4.1.0 less@3.9.0

4、使用规则

// webpack.config.js
module.exports = {
    ...
    module: {
        rules: [{
            test: /\.less$/,
            use: [{
                loader: "style-loader" // creates style nodes from JS strings
            }, {
                loader: "css-loader" // translates CSS into CommonJS
            }, {
                loader: "less-loader" // compiles Less to CSS
            }]
        }]
    }
};

5、使用npm run build进行打包


webpack图片处理

对于我们webpack中的资源,比如说图片资源,那么应该使用一些

1、需要一张图片资源

2、使用loader:npm --save-dev install url-loader@1.1.2 file-loader@3.0.1

3、使用配置

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192
            }
          }
        ]
      }
    ]
  }
}

注意,当加载的图片小于Limit的时候,会将图片编译为base64图片,否则使用路径 注意一下,file-loader和url-loader的配置只需要配置一个,否则会出现两张图片,导致图片加载错误 这里我们使用的是url-loader

4、配置之后,我们知道图片最终也是需要放到dist文件夹中的,所以我们需要引用的其实是dist文件夹中的图片内容

所以我们需要修改一下配置,只需要修改webpack.config.js中的内容

const path = require('path')
module.exports = {
    entry: './src/main.js',
    output: {
        // ...省略
        /* 只要涉及到任何的url的东西,都会自动加上这个dist */
        publicPath: 'dist/'
    },
    module: {
        // ...省略
    }

}

但是注意了,这个是我们的方法一,也就是将index读取dist文件夹中的内容,自动加上dist的路径前缀 但是之后我们需要将index.html一起打包到dist文件夹中,那个时候就需要删除这个配置了

图片配置详解url-loader

{
    // 使用这些图片格式,还可以自己加上jpeg等
    test: /\.(png|jpg|gif|jpeg)$/,
    use: [
        {
            loader: 'url-loader',
            options: {
                // 超过这个数字就会使用路径,否则会转为base64显示
                limit: 8192,
                /*
                    我们放到dist中,肯定希望图片分文件夹存放,那么img/其实就是在dist下建立一个img文件夹,放到里面
                    [name]是图片的本身名字,[hash.8]是对图片生成的32哈希值取8位,[ext]是扩展名
                 */
                name: 'img/[name].[hash:8].[ext]'
            }
        }
    ]
}

webpack对ES6语法处理

ES6在有些浏览器中是没有办法识别的,但是ES5语法是所有的浏览器支持的,所以我们需要将ES6打包为ES5

前面我们说过,如果要将ES6转换为ES5,需要babel,其实babel也是一个loader

1、npm install --save-dev babel-loader@7 babel-core@6.26.3 babel-preset-es2015@6.24.1

注意,这里的命令可能和官网的有所区别

2、使用如下配置

module: {
  rules: [
    {
      // 匹配JS文件 
      test: /\.js$/,
      // 排除以下文件夹的内容
      exclude: /(node_modules|bower_components)/,
      use: {
        loader: 'babel-loader',
        options: {
          // 配置,它会去寻找es2015这个文件,而es2015我们自己都已经下载好了
          presets: ['es2015']
        }
      }
    }
  ]
}

这个配置可能和官网略有不同,注意修改

配置之后,JS文件将会全部转换为ES5语法


webpack配置Vue

在我们日常开发中,我们会使用VueJS进行开发,并且会用特殊的文件组织Vue组件

所以下面我们来学习一下如何在我们的webpack中集成VueJS

1、npm install --save vue@2.5.21

注意我们不需要-dev,因为我们不仅仅是开发时的依赖,在运行时也需要依赖 使用这种方式之后,我们就不用通过script标签来引用Vue源码然后开发了,这样之后我们是使用的模块化思想

2、使用我们之前的形式使用Vue

  • src/main.js
import Vue from 'vue'

const app = new Vue({
    el: '#app',
    data: {
        message: 'HelloWorld'
    }
})
  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>


<div id="app">
    <h2>{{message}}</h2>
</div>

<script src="./dist/bundle.js"></script>
</body>
</html>

但是这个我们打包之后还不能使用,因为npm打包的时候有两个版本:

  • runtime-only
  • runtime-compiler

其中runtime-only不允许使用任何的template标签(<div id="app">也被看成了template标签runtime-compiler无限制,因为有compiler可以用于编译template 所以我们假如使用了第一个版本,那么必定会报错,所以我们需要使用第二个打包的版本

3、在webpack中配置

const path = require('path')

module.exports = {
    entry: './src/main.js',
    output: {
        // 省略...
    },
    module: {
        // 省略...
    },
    // 在这里配置一下内容
    resolve: {
        alias: {
            'vue$': 'vue/dist/vue.esm.js'
        }
    }
}

Vue的终极解决方案

我们知道最后肯定需要的是*.vue文件,但是这个文件和之前一样,也是需要loader和编译器的

1、npm install --save-dev vue-loader@13.0.0 vue-template-compiler@2.5.21

注意,这里的vue版本要和vue-template-compiler版本一致,否则会爆出编译错误

2、webpack的配置

module: {
    rules: [
        {
            test: /\.vue$/,
            use: ['vue-loader']
        }
    ]
},

3、在src/view/App.vue中编写

<template>
  <div>
    Hello World
  </div>
</template>

<script>
export default {
  name: "APP"
}
</script>

<style scoped>

</style>

vue文件,全部都分离了

4、在src/main.js中编写

import Vue from 'vue'
import APP from './view/APP.vue'

const app = new Vue({
    el: '#app',
    template: '<APP/>',
    data: {
        message: 'HelloWorld'
    },
    components: {
        APP
    }
})

只要在JS中有el:'#app'template属性,那么它就会自动替换掉<div id="app"></div>的内容 所以这里我们直接使用了APP.vue中的内容替换了index.html中的内容

5、在index.html中编写

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>


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

<script src="./dist/bundle.js"></script>
</body>
</html>

我们看到除了一个<div id="app"></div>之外,其他所有内容都没有 这是因为只要在JS中有el:'#app'template属性,那么它就会自动替换掉<div id="app"></div>的内容

6、假如我们想在引入文件的时候,忽略文件的类型,那么就在webpack中做如下配置

const path = require('path')

module.exports = {
    entry: './src/main.js',
    output: {
        // 省略
    },
    module: {
        // 省略
    },
    resolve: {
        // 忽略文件的扩展名
        extensions: ['.js','.css','.vue'],
        // 省略
    }
}

webpack中的plugin

插件:plugin

一般情况下,我们对一些框架对进行扩展,这些扩展就叫做插件

webpack的插件其实就是对webpack现有的各种功能进行扩展,比如打包优化、文件压缩等

plugin的使用过程:

1、通过npm安装(某些内置的不需要安装)

2、在webpack中配置插件

添加版权声明的plugin

这个版权其实就是在bundle.js上面加上一段注释,声明一下版权协议啥的,这个是webpack自带的

1、配置

const path = require('path')
// 引用webpack
const webpack = require('webpack')
module.exports = {
    // ...
    plugins: [
        new webpack.BannerPlugin('最终版权归maple所有')
    ]
}

添加HtmlWebpackPlugin

这个插件是对index.html做处理的,处理之后index.html也会打包到dist文件夹下面,并且会将打包后的js文件自动通过script标签插入到body中

1、npm install --save-dev html-webpack-plugin@3.2.0

2、webpack的配置

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    plugins: [
        new webpack.BannerPlugin('最终版权归maple所有'),
        new HtmlWebpackPlugin()
    ]
}

3、注意了,这个时候打包之后我们就不需要在html中加上dist前缀了,那么删掉内容

module.exports = {
    entry: './src/main.js',
    output: {
        // 省略
        /* 只要涉及到任何的url的东西,都会自动加上这个dist,但是这里我们不需要了 */
        // publicPath: 'dist/'
    },
    // 省略
    plugins: [
        new webpack.BannerPlugin('最终版权归maple所有'),
        new HtmlWebpackPlugin()
    ]
}

4、因为要生成index.html文件到dist文件夹中,所以我们还需要一个模板,我们首先改造一下index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>


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

</body>
</html>

可以看到,我们删掉了script标签,这是因为webpack的这个插件会自动插入 这个就作为我们生成的dist文件夹下面的index.html

5、在webpack中配置这个模板

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    plugins: [
        new HtmlWebpackPlugin({
            template: 'index.html'
        })
    ]
}

添加uglifyjs-webpack-plugin

这个是对JS进行压缩的插件

1、npm install --save-dev uglifyjs-webpack-plugin@1.1.1

2、修改webpack

const uglifyJsPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
    plugins: [
        new uglifyJsPlugin()
    ]
}

搭建本地服务器

webpack提供了一个可选的本地开发服务器,这个服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果

但是这个刷新后的结果是放到内存中的,而不是放到硬盘中的,等到我们全部都测试好之后执行npm run build,才会刷新到磁盘中

不过它是一个单独的模块,所以需要首先安装

1、npm install --save-dev webpack-dev-server@2.9.1

2、devserver也是一个webpack选项,本身可以设置如下属性

  • contentBase:为什么文件夹提供本地服务,默认是根文件夹,我们要填写./dist
  • port:端口号
  • inline:页面实时刷新
  • historyApiFallback:在SPA页面中,依赖H5的history模式

3、webpack修改内容如下

module.exports = {
    devServer: {
        contentBase: './dist',
        inline: true
    }
}

4、启动:npx webpack-dev-server


webpack配置分离

我们现在的目标是编译是一套环境,发布是一套环境

现在我们创建一个和dist平级的文件夹build,里面放三个JS文件

  • base.js:公共的JS文件,无论开发还是部署都需要
  • dev.js:开发JS文件
  • prod.js:部署JS文件

1、我们需要将这几个JS文件进行合并,所以需要npm install webpack-merge@4.1.5 --save-dev

2、各个文件一览

  • base.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.less$/,
                use: [{
                    loader: "style-loader" // creates style nodes from JS strings
                }, {
                    loader: "css-loader" // translates CSS into CommonJS
                }, {
                    loader: "less-loader" // compiles Less to CSS
                }]
            },
            {
                test: /\.(png|jpg|gif|jpeg)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 8192,
                            name: 'img/[name].[hash:8].[ext]'
                        }
                    }
                ]
            },
            {
                test: /\.js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['es2015']
                    }
                }
            },
            {
                test: /\.vue$/,
                use: ['vue-loader']
            }
        ]
    },
    resolve: {
        extensions: ['.js', '.css', '.vue'],
        alias: {
            'vue$': 'vue/dist/vue.esm.js'
        }
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: 'index.html'
        })
    ]
}
  • dev.config.js
// 引入插件
const webpackMerge = require('webpack-merge')
// 引入base.config.js
const baseConfig = require('./base.config')


module.exports = webpackMerge(
    baseConfig,
    {
        devServer: {
            contentBase: './dist',
            inline: true
        }
    }
)

只有开发时需要进行本地服务器

  • prod.config.js
const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config')

const webpack = require('webpack')
const uglifyJsPlugin = require('uglifyjs-webpack-plugin')

module.exports = webpackMerge(
    baseConfig,
    {
        plugins: [
            new webpack.BannerPlugin('最终版权归maple所有'),
            new uglifyJsPlugin()
        ]
    }
)

开发时不需要进行最终版权的声明和JS压缩

3、删掉webpack.config.js这个配置文件

4、在package.json中指定我们想要使用的配置文件

{
  // 省略
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    // 注意这里,我们自己定义的配置文件
    "build": "webpack --config ./build/dev.config.js"
  }
  // 省略
}

这样打包之后有点问题,注意这里不是打包到dist文件夹之后,而是打包到了我们build下面所在配置文件的路径下面的dist文件夹下面,我们显然不想这样做

5、修改base.config.js中,文件输出的路径

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    entry: './src/main.js',
    output: {
        // 注意这里加上了../,意思是打包到上一个文件夹的dist文件夹里面去
        path: path.resolve(__dirname, '../dist'),
        filename: 'bundle.js',
    }
}

6、使用命令npm run build,发现一切正常


脚手架

脚手架介绍

Vue CLI,我们一般俗称是Vue 脚手架。

假如我们只是写几个demo,那么不需要脚手架,但是假如要开发一个大型项目,就不需要使用脚手架。

开发大型项目的时候,我们必然要考虑代码目录结构,项目结果和部署,热加载,单元测试等内容。

假如每一个项目都手动配置,那么这些效率无疑是比较低的,所以我们通常会通过一个脚手架工具来完成这些东西。

Vue CLI的安装

使用Vue CLI的前提:Node

1、安装Vue CLI:npm install -g @vue/cli

2、npm install @vue/cli-init -g

因为我们这里需要讲解脚手架2和脚手架3,但是我们默认使用的都是脚手架3,所以要拉一个脚手架2

Vue CLI2

我们首先来讲解脚手架2,然后再去讲解脚手架3,脚手架2和3之间命令和配置有所区别

使用脚手架安装

1、vue init webpack 文件夹名称

注意,这个文件夹会存放之后项目的内容,默认的项目名称也是这个文件夹的名称 使用脚手架进行初始化,比如我现在使用vue init webpack vueli2test

2、基本的选项

VUE - 图5

我们在这里首先选择第一个内容,但是第二个内容也肯定是有存在的意义的 其实在后面的时候我们大部分时间都是使用的第二个内容,但是现在我们首先选择第一个内容

3、路由,暂时不要,之后会讲

VUE - 图6

4、代码规范,不开

VUE - 图7

5、单元测试,不开

VUE - 图8

6、E 2 E,end to end,端到端测试,是一个利用selenium或者nightwatch等进行自动化测试的框架,不要

VUE - 图9

7、npm还是yarn:其实都行,但是我们使用npm

VUE - 图10

8、等待,脚手架会自动生成内容

脚手架生成的目录讲解

VUE - 图11

VUE - 图12

VUE - 图13


runtime+compiler和runtime-only

我们之前创建的时候有两个选项

VUE - 图14

是选择runtime+compiler还是runtime-only,我们之前讲过template是否能用,其实是如下的区别:

VUE - 图15

在这个图片中,左边是runtime+compiler,右边是runtime-only

可以看到,左边的内容是先引入的component,然后在template中进行使用

但是右边的内容没有进行注册,直接转换为了render函数使用

这两者就是这个区别,但其实事实上,template最终也是转换为了render函数,最后render转换为了虚拟dom然后到了UI

所以事实上,runtime-only的性能更高,所以之后我们选择runtime-only

但是之前我们说过,这个环境不能解析vue文件的template,所以我们需要loader,这个loader就是之前我们说过的

vue-loader和vue-template-compiler,在脚手架中已经默认安装了


Vue CLI3

Vue CLI3初始化

Vue CLI3和Vue CLI2有很大区别:

1、vue-cli3使用webpack 4打造,vue-cli2还是webpack3

2、vue-cli3的设计原则是0配置,移除的配置文件根目录下的build和config等目录

3、vue-cli3提供了vue ui命令,提供可视化配置,更加人性化

4、移除了static文件夹,新增public文件夹,并且index.html移动到了public中,public中的内容会原封不动放到dist中

1、初始化:vue create 文件夹名称

2、配置

VUE - 图16

VUE - 图17

我们接下来讲Vuex,这个可以选择,或者等会在创建一个

VUE - 图18

VUE - 图19

VUE - 图20

3、最终生成

VUE - 图21


配置文件查看和修改

图形化配置

在Vue Cli3中,有很多配置都被隐藏了,但是我们有三种方式去修改默认的配置

1、在任何命令行敲vue ui,会启动一个本地服务器,然后做一些配置,但是在当前项目下运行命令会直接到当前项目下

VUE - 图22

2、导入项目之后,会直接跳转到项目的仪表盘

VUE - 图23

3、可以使用相关的配置了


TabBar样例

<template>
  <div id="tab-bar">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: "TabBar"
}
</script>

<style scoped>
#tab-bar {
  display: flex;

  background-color: #f6f6f6;

  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;

  box-shadow: 0 -3px 1px rgba(100, 100, 100, 0.2);
}
</style>
<template>
  <div class="tab-bar-item">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: "TabBarItem"
}
</script>

<style scoped>
.tab-bar-item {
  flex: 1;
  text-align: center;
  height: 49px;
}
</style>
<template>
  <div id="app">

    <tab-bar>
      <tab-bar-item>首页</tab-bar-item>
      <tab-bar-item>购物车</tab-bar-item>
      <tab-bar-item>我的</tab-bar-item>
    </tab-bar>
  </div>
</template>

<script>

import TabBar from "./components/tabbar/TabBar";
import TabBarItem from "./components/tabbar/TabBarItem";

export default {
  name: 'App',
  components: {
    TabBar,
    TabBarItem
  }
}
</script>

<style>
@import "./assets/css/base.css";


</style>

VUE - 图24


路由

认识路由

我们首先新创建一个项目,使用脚手架2,注意在选择的时候选择vue-router

VUE - 图25

什么是路由

路由是网络工程里面的一个专业术语,路由(routing)就是通过互联的网络将信息从源地址传输到目的地址的活动。

前端渲染和后端渲染

1、后端渲染:早期的JSP就是将页面写好,然后交给前端

2、前端渲染:将数据交给前端,然后前端写好页面

vue-router基本使用

目前前段的三大框架都有自己的路由实现:

1、Angular的ngRouter

2、React的ReactRouter

3、Vue的vue-router

路由框架的搭建

vue-router是Vue.js的官方插件,我们安装路由只需要npm来安装:

1、npm install vue-router --save

这一步之前在我们使用脚手架创建的时候已经完成了

2、在src下创建一个router文件夹,在router文件夹下面创建一个index.js,在JS文件中配置所有的路由信息

// 1、导入Vue
import Vue from 'vue'

// 2、导入VueRouter
import VueRouter from "vue-router";

// 3、Vue导入插件,这里导入的插件是VueRouter
Vue.use(VueRouter)

// 4、配置关系对象,描述路由和组件的关系
const routes = [

]

// 5、配置router对象,配置路由和组件之间的关系
const router = new VueRouter({
  routes
})

// 6、导出router,让Vue实例能够挂载router对象
export default router

3、在main.js中的vue实例中挂载vuerouter

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

// 1、我们导入一个文件夹之后,会默认导入文件夹下面的index文件
import router from './router'
Vue.config.productionTip = false

new Vue({
  el: '#app',
  // 2、Vue实例挂载router
  router,
  render: h => h(App)
})

使用路由

1、首先我们将App.vue中的全部内容都删掉,还他一个干净的页面

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

<script>
export default {
  name: 'App'
}
</script>

<style>
</style>

2、在components下面创建两个Vue页面,让这两个页面等会进行路由

  • Home.vue
<template>
  <div>
    <h2>首页</h2>
  </div>
</template>

<script>
export default {
name: "Home"
}
</script>

<style scoped>

</style>
  • About.vue
<template>
  <div>
    <h2>关于</h2>
  </div>
</template>

<script>
export default {
  name: "About"
}
</script>

<style scoped>

</style>

3、配置路由

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

// 1、导入两个页面
import Home from '../components/Home'
import About from '../components/About'

Vue.use(VueRouter)


// 2、配置路由关系,一个对象就是一个路由的映射
const routes = [
  // 3、path是路由,component配置对应的页面
  {
    path: '/home',
    component: Home
  },
  {
    path: '/about',
    component: About
  }
]


const router = new VueRouter({
  routes
})


export default router

3、在App.vue中编写页面,关联对应的路由

<template>
  <div id="app">
    <!--  1、这个router-link是全局组件,可以在全局使用,最终会渲染称为a标签  -->
    <router-link to="/home">首页</router-link>
    <router-link to="/about">关于</router-link>

    <!--  2、这个router-view是表示router-link所链接的内容,将会渲染到这里  -->
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>
</style>

VUE - 图26

路由的重定向,默认页面

用户进入一个页面之后,应该进入到一个默认的页面,所以我们应该再配置一个映射关系

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

import Home from '../components/Home'
import About from '../components/About'

Vue.use(VueRouter)


const routes = [
  // redirect就是重定向,也就是说当path为空的时候,重定向到path为/home下
  {
    path: '',
    redirect: '/home'
  },
  // 重定向到这里,最终指向Home.vue
  {
    path: '/home',
    component: Home
  },
  {
    path: '/about',
    component: About
  }
]


const router = new VueRouter({
  routes
})


export default router

路由修改为history页面

我们的页面现在是一个hash的样式,有锚点,比较难看

VUE - 图27

下面我们改变为history样式,那么就比较好看

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

import Home from '../components/Home'
import About from '../components/About'

Vue.use(VueRouter)


const routes = [
  {
    path: '',
    redirect: '/home'
  },
  {
    path: '/home',
    component: Home
  },
  {
    path: '/about',
    component: About
  }
]


const router = new VueRouter({
  routes,
  // 将默认样式改变为history样式
  mode: 'history'
})


export default router

VUE - 图28


router-link

之前我们使用了router-link这个标签,之前只用了to属性,其实还有其他属性

1、tag属性:可以将标签渲染为别的样式,比如button

<router-link to="/home" tag="button">首页</router-link>

2、replace属性:用户不能后退页面了,因为使用了这个结构之后,底层不再使用栈结构,而是直接替代当前页面,也就是说不能返回上一层页面了

<router-link to="/about" replace>关于</router-link>

3、router-link-active:这是一个样式,并不是一个属性,当某一个组件位于选中状态的时候,标签上会出现这个样式,我们可以做一些点击效果,比如点击就字体变红之类的

VUE - 图29

4、假如我们认为router-link-active名字太长,那么可以在路由选项中将这个名字更改为别的内容,比如active

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

import Home from '../components/Home'
import About from '../components/About'

Vue.use(VueRouter)


const routes = [
  {
    path: '',
    redirect: '/home'
  },
  {
    path: '/home',
    component: Home
  },
  {
    path: '/about',
    component: About
  }
]


const router = new VueRouter({
  routes,
  mode: 'history',
  // 将router-link-active样式改为active
  linkActiveClass: 'active'
})


export default router

通过代码跳转路由

<template>
  <div id="app">

    <button @click="homeClick">首页</button>
    <button @click="aboutClick">关于</button>

    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App',
  methods: {
    homeClick(){
      // 使用Vue的方式来跳转页面,同样会去寻找vue-router中的内容,最终会找到Home.vue
      this.$router.push('/home')
    },
    aboutClick(){
      this.$router.push('/about')
    }
  }
}
</script>

<style>
</style>

注意,我们的push()代表的是向栈中加入了当前页面,假如我们点击返回上一层页面,还是可以返回的 如果要替换当前页面,那么使用this.$router.replace()


动态路由

某些情况下,路由可能是不确定的,比如我进入/user/1或者/user/2,这个后面是用户的ID,这个我们不太确定

1、为了方便演示,我们首先创建一个页面

<template>
  <div>用户信息</div>
</template>

<script>
export default {
  name: "User"
}
</script>

<style scoped>

</style>

2、在router的配置中配置

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

Vue.use(VueRouter)

import Home from '../components/Home'
import About from '../components/About'
import User from '../components/User'

const routes = [
  {
    // 注意,这里的:userId是不确定的
    path: '/user/:userId',
    component: User
  },
  {
    path: '',
    redirect: '/home'
  },
  {
    path: '/home',
    component: Home
  },
  {
    path: '/about',
    component: About
  }
]


const router = new VueRouter({
  routes,
  mode: 'history',
  linkActiveClass: 'active'
})


export default router

3、在Vue实例中编写

<template>
  <div id="app">

    <router-link :to="'/user/'+userId">用户</router-link>

    <!--  注意不是router是route,这个可以拿到我们路由中的参数  -->
    <h2>{{$route.params.userId}}</h2>

    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      userId: '1'
    }
  }
}
</script>

<style>
</style>

路由懒加载

当打包应用的时候,JS的包会非常大,假如我们一次性请求,可能会产生很长时间的停顿,让页面进入空白,这个是我们不想看到的

那么我们这个时候就需要路由的懒加载了:路由懒加载主要是将路由对应的组件打包为一个个的JS代码块,这样在请求内容的时候是分批请求,而不是一次性请求,这样访问速度大大提升

那么我们之前的路由懒加载和之前加载的区别:

VUE - 图30

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

Vue.use(VueRouter)


const User = ()=>import('../components/User')
const Home = ()=>import('../components/Home')
const About = ()=>import('../components/About')

const routes = [
  {
    path: '/user/:userId',
    component: User
  },
  {
    path: '',
    redirect: '/home'
  },
  {
    path: '/home',
    component: Home
  },
  {
    path: '/about',
    component: About
  }
]


const router = new VueRouter({
  routes,
  mode: 'history',
  linkActiveClass: 'active'
})


export default router

路由嵌套

我们有可能会在一个路由中嵌套一个子路由,那么这就是嵌套路由

VUE - 图31

1、按照这种方式,我们创建这几个Vue文件,创建过程就不写了

2、在router中的JS文件中引入

import Vue from "vue";
import VueRouter from "vue-router";

Vue.use(VueRouter);

const User = () => import("../components/User");

const Home = () => import("../components/Home");
// 引入两个内容
const Message = () => import('../components/home/Message')
const News = () => import('../components/home/News')

const About = () => import("../components/About");

const routes = [
  {
    path: "/user/:userId",
    component: User
  },
  {
    path: "",
    redirect: "/home"
  },
  {
    path: "/home",
    component: Home,
    // 增加子路由
    children: [
      {
        path: "news",
        component: News
      },
      {
        path: "message",
        component: Message
      }
    ]
  },
  {
    path: "/about",
    component: About
  }
];

const router = new VueRouter({
  routes,
  mode: "history",
  linkActiveClass: "active"
});

export default router;

注意,我们的子路由是通过children这个数组来进行实现的 而且子路由并不需要加/home,只是home,前面会自动拼接

3、在父路由对应的Vue文件中,也就是Home.vue中进行以下内容

<template>
  <div>
    <h2>首页</h2>

    <!-- 给一个完整的路由 -->
    <router-link to="/home/news">新闻</router-link>
    <router-link to="/home/message">消息</router-link>

    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "Home",
};
</script>

<style scoped></style>

注意,我们这里的路由是全路由,而不是一个单独的子路由


参数传递

我们肯定想在路由进行切换的时候顺便传递点消息

传递参数有两种方式:

1、params

2、query

params的类型

1、配置路由格式:/router/:id

import Vue from "vue";
import VueRouter from "vue-router";

Vue.use(VueRouter);

const User = () => import("../components/User");
const Home = () => import("../components/Home");
const About = () => import("../components/About");

const routes = [
  {
    path: "/user/:userId",
    component: User
  },
  // ...
];

const router = new VueRouter({
  routes,
  mode: "history"
});

export default router;

2、传递的方式:在path后面跟上对应的值

<template>
  <div id="app">

    <router-link to='/home'>首页</router-link>

    <router-link :to="'/user/'+userId">用户</router-link>

    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      userId: '1'
    }
  }
}
</script>

<style>
</style>

3、传递后形成的路径:/router/123

这种方式就是我们刚才在讲动态路由的时候的方式 VUE - 图32

4、取出内容

<template>
  <div>
    用户信息

    <br>
    {{ userId }}
  </div>

</template>

<script>
export default {
  name: "User",
  data(){
    return{
      userId: this.$route.params.userId
    }
  }
}
</script>

<style scoped>

</style>

query的类型

1、配置路由就是我们的普通配置

2、传递的时候,对象中使用query的key作为传递方式

<template>
  <div id="app">

    <router-link to='/home'>首页</router-link>


    <router-link :to="message">关于</router-link>


    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      userId: '1',
      message: {
        // 路径
        path: '/about',
        // 这个就是query,使用query中的name作为key传递
        query:
          {
            name: 'maple'
          }
      }
    }
  }
}
</script>

<style>
</style>

3、传递后形成的路径:router?id=123

VUE - 图33

4、取出路径中的内容

<template>
  <div>
    <h2>关于</h2>
    {{message}}
  </div>
</template>

<script>
export default {
  name: "About",
   data(){
    return{
      message: this.$route.query.name
    }
  }
}
</script>

<style scoped>

</style>

但是我们在进行参数传递的时候,总不是进行router-link的传递,那么我们应该这样传递:

<template>
  <div id="app">

    <!--    <router-link to='/home'>首页</router-link>-->

    <!--    <router-link :to="'/user/'+userId">用户</router-link>-->

    <!--    <router-link :to="message">关于</router-link>-->

    <button @click="toUser">用户</button>
    <button @click="toAbout">关于</button>

    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      userId: '1',
      message: {
        name: 'maple'
      }
    }
  },
  methods: {
    toUser() {
      this.$router.push(`/user/${this.userId}`)
      // 这两种都可以,但是我们仍然选择使用上面的ES6语法
      // this.$router.push('/user/'+this.userId)
    },
    toAbout() {
      this.$router.push({
        path: '/about',
        query: this.message
      })
    }
  }
}
</script>

<style>
</style>

注意,这里我们将path和query分开了,不再一次性写到data中了


全局导航守卫

有的时候,我们在进行路由跳转的时候,我们需要在跳转过程中进行一些操作,所以我们需要导航守卫

比如控制访问权限就可以使用导航守卫来进行实现

首先我们来看一下几个生命周期的函数

<script>
export default {
  name: "Home",
  created() {
    console.log('组件创建的回调函数')
  },
  mounted() {
    console.log('组件挂载到dom上的回调函数')
  },
  updated() {
    console.log('页面改变的时候的回调函数')
  },
  activated() {
    console.log('页面进入活跃状态调用')
  },
  deactivated() {
    console.log('页面进入不活跃状态调用')
  }
};
</script>

上面的这几个生命周期函数,十分好用,当组件到达对应的生命周期,那么他就会调用对应的函数

但是这样做比较麻烦,我们必须在每一个vue页面上增加这样的代码,所以我们需要一个东西来监听全局的路由跳转

我们看一下router/index.js中的内容

import Vue from "vue";
import VueRouter from "vue-router";

Vue.use(VueRouter);

const User = () => import("../components/User");

const Home = () => import("../components/Home");

const About = () => import("../components/About");

const routes = [
  {
    path: "",
    redirect: "/home"
  },
  {
    path: "/user/:userId",
    component: User,
    meta: {
      title: '用户'
    }
  },
  {
    path: "/home",
    component: Home,
    meta: {
      title: '首页'
    }
  },
  {
    path: "/about",
    component: About,
    meta: {
      title: '关于'
    }
  }
];

const router = new VueRouter({
  routes,
  mode: "history"
});


// 路由跳转之前进行监听,类似拦截器,从from到to
router.beforeEach((to, from, next) => {

  /*
    to的类型是Route,也就是我们在上面定义的这些东西,所以可以直接获取meta数据,得到对应的内容
    我们这里直接就将下一个页面的内容转换为了我们在这里定义的内容
   */
  document.title = to.meta.title

  // 跳转到下一个页面
  next()
})

export default router;

next()有很多用法,假如传入false就是中断当前导航,可以传入一个对应的路径直接跳转到对应路径

我们刚才的这个东西叫做前置钩子,也就是在页面跳转之前进行拦截然后修改其中的内容 假如是后置钩子,就是router.afterEach((to,from)=>{})

我们上面的前置和后置钩子,叫做全局守卫,那么除了全局守卫之外,还有路由独享守卫和组件内的守卫

这些东西和全局导航守卫差不多,有需要可以直接去官网上查询


keep-alive

keep-alive是vue内置的组件,所有被keep-alive包裹的组件都会被缓存起来

等之后再次回到这个组件的时候,就会读取缓存内容,而不是重新创建一个新的组件

<template>
  <div id="app">
    <keep-alive>
      <router-view/>
    </keep-alive>
  </div>
</template>

Promise

Promise介绍

Promise是ES6新引入的一个语法,其实是异步编程的解决方案,比如网络请求

网络请求很复杂的时候,假如有多层嵌套,那么就会陷入回调地狱,但是Promise可以很优雅地解决这个问题

Promise基本语法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>


<script>
    // 1、使用一个异步操作
    setTimeout(() => {
        // 延迟一秒钟打印Hello World
        console.log('Hello World');

        // 打印完成之后,再次调用打印Hello Promise,假如这种情况比较多,那么就陷入了回调地狱
        setTimeout(() => {
            console.log('Hello Promise')

            setTimeout(() => {
                console.log('Hello Vue')
            }, 1000)

        }, 1000)
    }, 1000)


    /*
        2、使用Promise来对异步操作进行封装
        resolve:函数,调用了resolve之后,会直接调用then()
        reject:函数,调用了reject之后,会直接调用
     */
    new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('Hello World')
            resolve
        }, 1000)
    }).then(() => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('Hello Promise')
                resolve
            }, 1000)
        })
    }).then(() => {
        setTimeout(() => {
            console.log('Hello Vue')
        }, 1000)
    })
</script>
</body>
</html>

可以看到这种写法其实就是将树形结构变为了线性结构

那么我们什么时候需要使用到Promise呢?异步操作的时候,使用Promise对操作进行封装


Promise的三种状态

1、pending:等待状态,比如正在进行网络请求的时候,或者定时器没有到时间

2、fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then

3、reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且回调.catch()

我们在上面是一种处理形式,但是我们还有另外的处理形式

/**
 * 使用Promise的另外的处理形式
 */
new Promise((resolve, reject) => {

}).then(
    // 处理数据
    data => {

    },
    // 处理错误信息
    err => {

    }
)

Promise的all方法

假如我们现在有一个需求,这个需求需要两次请求出来的数据拼接出来,那么我们这两个结果都有的情况下才能向下进行

但是我们本身网络请求是异步的,那么我们应该如何确定什么时候两个结果都拿到了呢?

那么这个时候我们就有一个方法,就是Promise.all()方法,这里面的参数是可迭代对象,也就是说可以遍历的对象,这些对象可以是很多的Promise

/**
 * 4、Promise all
 */
Promise.all([
    new Promise(((resolve, reject) => {
        $ajax({
            url: '',
            success: function (data) {
                resolve(data)
            }
        })
    })),
    new Promise(((resolve, reject) => {
        $ajax({
            url: '',
            success: function (data) {
                resolve(data)
            }
        })
    }))
]).then(results => {
    // 第一个请求的结果
    results[0]
    // 第二个请求的结果
    results[1]
})

Vuex

Vuex的概念

Vuex是官网为Vue应用程序开发的状态管理模式,其实就是一个状态管理工具而已

一般来说,有一些共享的状态需要放到Vuex里面

1、用户登录状态、名称、头像

2、地理位置

3、商品的收藏,购物车的物品

4、…….


Vuex的使用

1、npm install vuex --save

2、在src新建一个文件夹store,里面放置一些Vuex的内容

3、在store中创建index.js

import Vue from 'vue';

// 1、引入Vuex
import Vuex from "vuex";

// 2、使用Vuex
Vue.use(Vuex)

// 3、创建Vuex对象,但是我们创建的是vuex的store对象而不是vuex对象
const store = new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  getters: {},
  modules: {}
})

// 4、导出
export default store

其实我们可以看到store对象中的这些东西是比较固定的,这些就是我们使用的一些Vuex的一些状态 这些状态到后面会一一讲到,其中actions里面是用于异步操作的

4、在main.js中引入

import Vue from 'vue'
import App from './App'
import router from './router'

import store from "./store";

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})

之后的内容,我们就会在$store中拿到这个store中的内容

这个Vuex是一个全局的单例模式


state 和 mutations

在state,我们定义一些变量。对于mutations而言,我们一般是用于定义一些方法

1、在Vuex中定义一些变量和方法

import Vue from 'vue';

// 1、引入Vuex
import Vuex from "vuex";

// 2、使用Vuex
Vue.use(Vuex)

// 3、创建Vuex对象,但是我们创建的是vuex的store对象而不是vuex对象
const store = new Vuex.Store({
  state: {
    // state这里就是用于定义一些变量的
    couter: 0
  },
  mutations: {
    // 这个mutations用于定义一些方法,这些方法中的state就是我们这里store中的state
    increment(state) {
      state.couter++
    },
    decrement(state) {
      state.couter--
    }
  },
  actions: {},
  getters: {},
  modules: {}
})

// 4、导出
export default store

2、在我们普通的Vue文件中使用

<template>
  <div id="app">
    <!-- 这里直接使用$store来获取我们的vuex中store中的内容 -->
    <div>{{ $store.state.couter }}</div>

    <button @click="addNum">+</button>
    <button @click="lessNum">-</button>
  </div>
</template>

<script>
export default {
  name: 'App',
  methods: {
    addNum() {
      // 这里注意了,我们调用方法不是直接使用mutations,而是要通过commit事件,传入要调用的方法名字调用
      this.$store.commit('increment')
    },
    lessNum() {
      this.$store.commit('decrement')
    }
  }
}
</script>

除了上面的基本使用,我们还有一些细节需要说明

1、只要是更改state中的内容,一定要经过mutations

2、mutations中定义的函数我们可以看为两部分:事件类型type、回调函数handler

比如我们的decrement(state) {state.couter--}中,decrement是事件类型,其余为回调函数

3、这样我们其实知道了this.$store.commit('decrement')中,decrement其实是一个事件类型

4、mutations中方法的参数除了state之外,还可以接受参数

import Vue from 'vue';

// 1、引入Vuex
import Vuex from "vuex";

// 2、使用Vuex
Vue.use(Vuex)

// 3、创建Vuex对象,但是我们创建的是vuex的store对象而不是vuex对象
const store = new Vuex.Store({
  state: {
    // state这里就是用于定义一些变量的
    couter: 1
  },
  mutations: {
    // 注意这里,我们接受的类型除了state还有data
    decrement(state, data) {
      state.couter -= data
    }
  },
  actions: {},
  getters: {
    power(state) {
      return Math.abs(state.couter)
    }
  },
  modules: {}
})

// 4、导出
export default store
<template>
  <div id="app">
    <div>{{ $store.getters.power }}</div>
    <button @click="lessNum(5)">-</button>
  </div>
</template>

<script>
export default {
  name: 'App',
  methods: {
    lessNum(data) {
      // 这里直接传递参数
      this.$store.commit('decrement',data)
    }
  }
}
</script>

5、事件的提交方式:事件的提交方式有两种,之前我们都是第一种提交方式,下面我说第二种

<template>
  <div id="app">
    <!-- 这里直接使用$store来获取我们的vuex中store中的内容 -->
    <div>{{ $store.getters.power }}</div>

    <button @click="addNum">+</button>
    <button @click="lessNum(5)">-</button>
  </div>
</template>

<script>
export default {
  name: 'App',
  methods: {
    lessNum(data) {
      // 1、第一种提交方式,注意这里的data就是传递过来的值data
      // this.$store.commit('decrement',data)

      // 2、第二种提交方式,注意这里的data不仅仅是data,而是整个对象
      this.$store.commit({
        type: 'decrement',
        // 假如是整个对象的话,我们有一个专业的名称:载荷 payload
        data,
        // 载荷的意思整个对象,也就是当前的commit中的所有内容都会提交过去
        // 所以也包括这个18
        age: 18
      })
    }
  }
}
</script>
import Vue from 'vue';

// 1、引入Vuex
import Vuex from "vuex";

// 2、使用Vuex
Vue.use(Vuex)

// 3、创建Vuex对象,但是我们创建的是vuex的store对象而不是vuex对象
const store = new Vuex.Store({
  state: {
    // state这里就是用于定义一些变量的
    couter: 1
  },
  mutations: {
    // 这里第二个参数是整个对象,那么我们就使用专业的名称载荷payload来接受,可以接受所有参数
    decrement(state, payload) {
      // 可以接受所有的内容参数
      console.log(payload.age)
      state.couter -= payload.data
    }
  },
  actions: {},
  getters: {
    power(state) {
      return Math.abs(state.couter)
    }
  },
  modules: {}
})

// 4、导出
export default store

一般来说我们使用第二种方式传递参数


getter

getter,简单来说就是对state中的某些内容做个封装,然后传出去,比如对数据乘二,或者做一些别的处理

import Vue from 'vue';

// 1、引入Vuex
import Vuex from "vuex";

// 2、使用Vuex
Vue.use(Vuex)

// 3、创建Vuex对象,但是我们创建的是vuex的store对象而不是vuex对象
const store = new Vuex.Store({
  state: {
    // state这里就是用于定义一些变量的
    couter: 1
  },
  mutations: {
    // 这个mutations用于定义一些方法,这些方法中的state就是我们这里store中的state
    increment(state) {
      state.couter++
    },
    decrement(state) {
      state.couter--
    }
  },
  actions: {},
  getters: {
    power(state) {
      return Math.abs(state.couter)
    }
  },
  modules: {}
})

// 4、导出
export default store
<template>
  <div id="app">
    <!-- 这里之前是state,这里改成了getters -->
    <div>{{ $store.getters.power }}</div>

    <button @click="addNum">+</button>
    <button @click="lessNum">-</button>
  </div>
</template>

<script>
export default {
  name: 'App',
  methods: {
    addNum() {
      // 这里注意了,我们调用方法不是直接使用mutations,而是要通过commit事件,传入要调用的方法名字调用
      this.$store.commit('increment')
    },
    lessNum() {
      this.$store.commit('decrement')
    }
  }
}
</script>

这里返回的时候,除了一个结果,甚至还可以是一个函数等,一般来说我们返回函数是很有用的


actions

Action类似Mutation,但是Action是用于异步操作的,所以Action可以替代Mutation进行异步操作

import Vue from 'vue';

// 1、引入Vuex
import Vuex from "vuex";

// 2、使用Vuex
Vue.use(Vuex)

// 3、创建Vuex对象,但是我们创建的是vuex的store对象而不是vuex对象
const store = new Vuex.Store({
  state: {
    couter: 1
  },
  mutations: {
    increment(state) {
      state.couter++
    },
  },
  actions: {
    // context是上下文的意思,上下文的意思是该有的东西它全都有
    up(context) {
      // 可以使用上下文直接调用mutation的内容,进而修改state中的内容
      // 模拟异步操作
      setTimeout(() => context.commit('increment'), 1000)
    }
  },
  getters: {
    power(state) {
      return Math.abs(state.couter)
    }
  },
  modules: {}
})

// 4、导出
export default store
<template>
  <div id="app">
    <!-- 这里直接使用$store来获取我们的vuex中store中的内容 -->
    <div>{{ $store.getters.power }}</div>

    <button @click="addNum">+</button>
    <button @click="lessNum(5)">-</button>
  </div>
</template>

<script>
export default {
  name: 'App',
  methods: {
    addNum() {
        // 这里变为了dispatch,而不是commit,因为我们调用的是actions中的方法
      this.$store.dispatch('increment')
    }
  }
}
</script>

除了这个dispatch之外,其余的没有啥区别


modules

modules的用法就是,可以将store中的内容进行模块的划分,一看你就懂了

import Vue from 'vue';

// 1、引入Vuex
import Vuex from "vuex";

// 2、使用Vuex
Vue.use(Vuex)

// 3、创建Vuex对象,但是我们创建的是vuex的store对象而不是vuex对象
const store = new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  getters: {
  },
  modules: {
    A: {
      state: {
        couter: 1
      },
      mutations: {},
      actions: {},
      getters: {},
      modules: {}
    },
    B: {
      state: {},
      mutations: {},
      actions: {},
      getters: {},
      modules: {}
    }
  }
})

// 4、导出
export default store

其实是很简单的,怕一个Vuex里面的内容太复杂,所以开始套娃

但是注意了,我们modules中的内容最终是放到了state里面的,所以我们在vue页面取得其实是从state中取得内容

<template>
  <div id="app">
    <!-- 这里直接使用$store来获取我们的vuex中store中的内容 -->
    <div>{{ $store.state.A.couter }}</div>
  </div>
</template>

<script>
export default {
  name: 'App',
  methods: {
  }
}
</script>

Axios

Axios基本使用

Axios支持多种方式:

  • axios(config)
  • axios.request(config)
  • axios.get(url[,config])
  • axios.delete(url[,config])
  • axios.head(url[,config])
  • axios.post(url[,data[,config]])
  • axios.put(url[,data[,config]])
  • axios.patch(url[,data[,config]])

1、安装:npm install axios --save

2、在main.js中引入

import Vue from 'vue'
import App from './App'
import router from './router'

// 1、导入axios
import axios from "axios";

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})

// 2、对axios做配置,这个配置是一个对象,可以直接访问
axios({
  url: '目标的访问地址'
})
  .then(res => {
    console.log(res)
  })

VUE - 图34

上面是最基本的使用,但是假如我们想要以get方式发送请求数据,那么我们可以使用下面的两种方式:

1、在method中写为get

import Vue from 'vue'
import App from './App'
import router from './router'

// 1、导入axios
import axios from "axios";

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  render: h => h(App)
})

// 2、对axios做配置,这个配置是一个对象,可以直接访问
axios({
  url: 'xxx/home/data?type=sell&page=3',
  method: 'get'
}).then(res => {
  console.log(res)
})

axios.get({
  url: 'xxx/home/data',
  params: {
    type: 'sell',
    page: 3
  }
}).then(res => {
  console.log(res)
})

可以看到上面的请求两种方式,都是传递参数的,第一个传递的参数只不过是url拼接,第二个则是使用的params 最终拼成了和第一种的一样的效果,而params是专门针对于get的参数,其他的之后再讲


Axios发送并发请求

有的时候,我们一个页面的渲染需要多个数据,而这多个数据需要多次网络请求,那么这种情况Axios也有处理方式

我们之前讲的Promise有一个all方法,那么Axios其实也有一个all方法

import Vue from 'vue'
import App from './App'
import router from './router'

// 1、导入axios
import axios from "axios";

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  render: h => h(App)
})

// 2、对axios做配置,这个配置是一个对象,可以直接访问
axios.all([
  axios.get({
    url: 'http://123.207.32.32:8000/home/data',
    params: {
      type: 'sell',
      page: 3
    }
  }),
  axios.get({
    url: 'http://123.207.32.32:8000/home/multidata'
  })
]).then(res => {
  console.log(res)
})

假如我们想要将数组进行延展分割的时候,也就是说两个结果分开,那么

axios.all([
  axios.get({
    url: 'http://123.207.32.32:8000/home/data',
    params: {
      type: 'sell',
      page: 3
    }
  }),
  axios.get({
    url: 'http://123.207.32.32:8000/home/multidata'
  })
]).then(axios.spread((res1, res2) => {
  console.log(res1);
  console.log(res2);
}))

axios.spread


配置信息相关

这里有一些我们常用的配置信息

// 基本的URL
axios.defaults.baseURL = 'xxxx'
// 请求头
axios.defaults.headers.post['Content-Type'] = 'application/X-www-form-urlencoded'
// 超时时间
axios.defaults.timeout = 5000
// 常用的数据响应格式:json / blob / documet / arraybuffer / text / stream
responseType: 'json'
// 跨域是否带token
withCredentials: false
// 身份验证
auth: {
    uname: '',
    pwd: ''
}
// 常见的请求方式:get、post、patch、delete
method: 'get'
// URL查询对象,当method为get的时候,我们传params
params: {
    id: 22
}
// 请求体,当我们method设置为post的时候,我们传data
data: {
    key: ''
}

Axios其实我们的一些URL前缀或者一些其他的东西是重复的,那么这些东西就需要抽出来,也就是Axios的全局配置

这些东西全部都可以抽出来,甚至可以单独成立一个文件


Axios实例和模块封装

之前我们都是使用的全局的配置,但是我们一般在开发中是要使用Axios实例+模块封装的方式来进行网络请求

Axios实例

import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false
new Vue({
  el: '#app',
  router,
  render: h => h(App)
})


// 1、创建一个Axios的实例
const instance1 = axios.create({
  baseURL: 'http://xxx:8000',
  timeout: 5000
})

// 2、使用实例的基础配置,加上真正的封装,形成真正的请求接口
instance1({
  url: '/home/multidata',
}).then(res => {
  console.log(res)
})

// 第二个网络请求
instance1({
  url: '/home/data',
  params: {
    type: 'pop',
    page: 1
  }
})

封装

为了防止代码冗余,封装肯定是需要的,我们的组件抽离是这样做的:

1、在src下面新建立一个文件夹network

2、在network中新建一个JS文件request.js

// 1、导入axios
import axios from "axios";

// 2、因为考虑到可能有多个服务器的配置,所以不是导出的default
export function request(config) {

  // 3、新建一个axios的实例
  const instance = axios.create({
    baseURL: 'http://123.207.32.32:8000',
    timeout: 5000
  })

  // 4、因为这个axios默认是实现了promise的,所以直接搞一个返回,让调用者去处理
  return instance(config)
}


// 5、可能是有其他的配置
export function request2(config) {

}

3、在main.js中引入

import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false
new Vue({
  el: '#app',
  router,
  render: h => h(App)
})


// 1、导入request
import {request} from './network/request'

// 2、使用最终的配置
request({
  url: '/home/data',
  params: {
    type: 'pop',
    page: 1
  }
}).then(res => {
  console.log(res)
})

Axios拦截器

在发送网络请求,我们可能需要拦截来做一些内容:

1、请求拦截

  • 请求成功拦截
  • 请求失败拦截

2、响应拦截

  • 响应成功拦截
  • 响应失败拦截

Axios的全局拦截和实例拦截

Axios分为实例拦截和全局拦截

import axios from "axios";

/*
  1、Axios的全局拦截器
    - axios.interceptors.request:拦截请求
    - axios.interceptors.response:拦截响应
 */
export function request(config) {

  const instance = axios.create({
    baseURL: 'http://123.207.32.32:8000',
    timeout: 5000
  })

  /*
    2、Axios的实例拦截器,我们以实例拦截器为例子进行讲解
      使用 use()来进行使用
   */

  // 请求拦截
  instance.interceptors.request.use(
    // 这个拦截下来的就是我们定义的配置,也就是instance里面加上config的所有内容,我们可以在这里进行更改
    config => {
      // 中间对config进行一些处理,比如携带token,更改url地址,显示网络请求的加载动画等
      // 我们拦截到config之后,还要进行放行
      return config
    },
    // 请求发送失败来到这里
    err => {

    })

  // 响应拦截
  instance.interceptors.response.use(
    // 拿到结果
    res => {

      // 放行结果,其实我们想要的一般就是服务器中返回的结果,所以其他的信息都不要
      return res.data
    },
    // 响应错误时来到这里
    err => {

    })

  return instance(config)
}