1. vue简介

什么是vue?

Vue是一套用于构建用户界面的渐进式JavaScript框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,方便与第三方库或既有项目整合。

Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件

特点

  • 易用

    • 在有HTML CSS JavaScript的基础上,快速上手。
  • 灵活

    • 简单小巧的核心,渐进式技术栈,足以应付任何规模的应用。
  • 性能

    • 20kb min+gzip 运行大小、超快虚拟 DOM 、最省心的优化。

2. MVC和MVVM

MVC是后端分层开发概念

MVVM是前端视图层的概念,主要关注于视图层分离,也就是说,MVVM把前端的视图层,分为了三部分:Model,View,ViewModel


3.Vue基本指令

3.1 Vue基本代码

  • 创建一个html
  • 导入vue

vue cdn:<script type="text/javascript" src="https://unpkg.com/vue"></script>

  • 创建一个Vue实例

当我们导入包后,在浏览器内存中,就多了一个vue构造函数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="https://unpkg.com/vue"></script>
</head>
<body>
    <!--vue中通过el绑定了该标签-->
    <div id="app">
        <h1>{{msg}}</h1>
    </div>

<script>
    var vm = new Vue({
       el : '#app',  // 表示,当前我们new的这个Vue实例,要控制页面上的哪个区域  #app 绑定了id为app的标签
       data: {
           // data 属性中,存放的是el中要用到的数据
            msg: 'Hello,World!' // 通过Vue提供的指令,很方便的就能把数据渲染到页面上,不再手动操作DOM元素了
       }
    });
</script>
</body>
</html>

3.2 v-cloak

解决插值表达式闪烁问题,闪烁问题:在打开页面时,会显示出{{msg}}表达式,为了提升用户体验,我们在数据还没有加载出来时,让其显示空白

首先我们要给使用了插值表达式的标签,加上v-cloak属性

然后通过css设置具有v-cloak属性的标签的样式[v-cloak] {display: none;}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="https://unpkg.com/vue"></script>
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
</head>
<body>
    <div id="app">
        <h1 v-cloak>{{msg}}</h1>
    </div>

    <script>
        var vm = new Vue({
            el : '#app',
            data : {
                msg: 'Hello,World!'
            }
        });
    </script>
</body>
</html>

3.3 v-text

和插值表达式没有太大区别,但是还是存在一些区别

  • 默认v-text不会具有闪烁问题,因为v-text是属性
  • v-text会覆盖标签中原本的内容,但是插值表达式只会替换自己的占位符,不会清空其他内容
<h1 v-text="msg"></h1></h2>

3.4 v-html

和v-text相似,但是v-html可以解析html标签,v-html和v-text一样,同样会覆盖掉标签中原来的内容

<div id="app">
    <div v-html="msg"></div>
</div>

<script>
    var vm = new Vue({
        el : '#app',
        data : {
            msg: '<h3>i am a good boy!</h3>'
        }
    });
</script>

3.5 v-bind

v-bind:是vue中提供用来绑定属性的指令

<div id="app">
    <input type="button" v-bind:title="mytitle" value="按钮">
</div>

<script>
    var vm = new Vue({
        el : '#app',
        data : {
            mytitle:"这是一个自己定义的title"
        }
    });
</script>

image-20200526141734252

v-bind:支持拼接,可以写合法的js表达式

<input type="button" v-bind:title="mytitle + '123'" value="按钮">

v-bind:可以简写成一个冒号:

<input type="button" v-bind:title="mytitle" value="按钮">

3.6 v-on

Vue中提供了v-on:事件绑定机制

<div id="app">
    <input type="button" value="按钮" v-on:click="sayHi">
</div>

<script>
    var vm = new Vue({
        el : '#app',
        data : {

        },
        methods: {  // 这个methods属性中定义了当前Vue实例所有可用的方法
            sayHi : function () {
                alert("Hi~~~");
            }
        }
    });
</script>

image-20200526142700769

v-on:可以简写成@

<input type="button" value="按钮" @click="sayHi">

3.7 简单跑马灯效果制作

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="https://unpkg.com/vue"></script>
</head>
<body>

<div id="app">
    <button @click="start">开始</button>
    <button @click="end" >暂停</button>
    <div v-text="msg"></div>
</div>

<script>
    var vm = new Vue({
        el : '#app',
        data : {
            msg : '大风起兮云飞扬。威加海内兮归故乡。安得猛士兮守四方!',
            flag : false,    // 存放是否已经开启定时器的标志
            intervalName:""  // 存放定时器名称
        },
        methods:{
            start() {
                var _this = this;  // 解决在定时器内部this的指向问题
                if (!this.flag){
                    this.flag=true;
                    this.intervalName = setInterval(function () {
                        var s = _this.msg.substring(0,1);
                        var e = _this.msg.substring(1);
                        _this.msg = e + s;
                    },100);
                }
            },
            end(){
                if (this.flag){
                    this.flag=false;
                    clearInterval(this.intervalName);
                }
            }
        }
    });
</script>

</body>
</html>

3.8 事件修饰符

事件默认会有冒泡机制

.stop         阻止冒泡
.prevent    阻止默认事件
.capture    添加事件侦听器时使用事件捕获模式
.self        只当事件在该元素本身(比如不是子元素)触发时触发回调,相当于阻止自己被冒泡
.once        事件只触发一次
<div id="app">
    <!--捕获机制,从外到里执行事件,相反方向的冒泡-->  
    <div class="inner" @click.capture="divhandler">
          <!--阻止冒泡-->  
        <input type="button" value="点我一下" @click.stop="btnhandler">
        <!--阻止默认事件-->  
        <a href="https://www.baidu.com" @click.prevent="linkClick">百度</a>
         <!--事件只触发一次 阻止默认行为也只会触发一次-->  
        <a href="https://www.baidu.com" @click.prevent.once="linkClick">百度</a>
    </div>
</div>
<script>
    var vm = new Vue({
        el : '#app',
        data : {

        },
        methods : {
            divhandler(){
                alert("点击了div");
            },
            btnhandler(){
                alert("点击了btn");
            },
            linkClick(){
                alert("出发了linkClick事件");
            }
        }
    });
</script>

3.9 v-model表单元素数据双向绑定

v-model指令,可以实现表单元素和Model中的数据的双向绑定

==注意:v-model只能用用在表单元素中!!==

当页面中的数据修改时,data中的数据也会修改

当data中的数据修改时,页面中的数据也会修改

<div id="app">
    <input type="text" v-model="msg" >
    <p v-text="msg"></p>
</div>

<script>
    var vm = new Vue({
        el : '#app',
        data : {
            msg : ''
        }
    });
</script>

image-20200526153023014

==通过v-model实现一个简单的小计算器==

<div id="app">
    <input type="text" v-model="num1">
    <select v-model="opt">
        <option value="+">+</option>
        <option value="-">-</option>
        <option value="*">*</option>
        <option value="/">/</option>
    </select>
    <input type="text" v-model="num2">
    <input type="button" value="=" @click="test">
    <input type="text" v-model="result">
</div>

<script>
    var vm = new Vue({
        el : '#app',
        data : {
            num1 : '',
            num2 : '',
            result:'',
            opt:'+'
        },
        methods: {
            test(){
                // 方法一:
                // switch (this.opt) {
                //     case '+':
                //         this.result = parseInt(this.num1)+parseInt(this.num2);
                //         break;
                //     case '-':
                //         this.result = parseInt(this.num1)-parseInt(this.num2);
                //         break;
                //     case '*':
                //         this.result = parseInt(this.num1)*parseInt(this.num2);
                //         break;
                //     case '/':
                //         this.result = parseInt(this.num1)/parseInt(this.num2);
                //         break;
                // }
                // 方法二:
                var codeStr = 'parseInt(this.num1)'+this.opt+'parseInt(this.num2)';
                this.result = eval(codeStr);  // eval函数解析执行 尽量少用这种方式
            }
        }
    });
</script>

image-20200526155307253

3.10 通过属性绑定为元素设置class类样式

  • 数组

直接传递一个数组,注意:这里的class需要使用v-bind做数据绑定

<div id="app">
    <h1 :class="['red','thin']">这是一个很大的h1,大到你无法想象~</h1>
</div>
  • 数组中使用三元表达式

    <div id="app">
    <h1 :class="['red','thin',flag==true?'active':'']">这是一个很大的h1,大到你无法想象</h1>
    </div>
    <script>
    var vm = new Vue({
      el : '#app',
      data : {
          flag: true
      }
    });
    </script>
  • 数组中嵌套对象

对象代替三元表达式,提高代码的可读性

<div id="app">

    <h1 :class="['red','thin',{'active':flag}]">这是一个很大的h1,大到你无法想象</h1>
</div>
<script>
    var vm = new Vue({
        el : '#app',
        data : {
            flag: true
        }
    });
</script>
  • 直接使用对象

在为class使用v-bind绑定对象的时候,对象是属性是类名,属性值是一个标识符;对象的属性可以加引号也可以不加引号

<h1 :class="{thin:true,red:true,italic:false,active:true}">这是一个很大的h1,大到你无法想象</h1>

3.11 通过属性绑定为元素设置style行内样式

  • 直接在元素上通过:style的形式,书写样式对象

    <div id="app">
    <h1 :style="{color:'red','font-weight':'200'}">这是一个h1</h1>
    </div>
  • 将样式对象,定义到data中,并直接引用到:style

    <div id="app">
    <h1 :style=styleobj>这是一个h1</h1>
    </div>
    <script>
    var vm = new Vue({
      el:'#app',
      data : {
          styleobj : {color:'red','font-weight':'200'}
      }
    });
    </script>
  • :style中通过数组,引用多个data上的样式对象

    <div id="app">
    <h1 :style=[styleobj,styleobj2]>这是一个h1</h1>
    </div>
    
    
    <script>
    var vm = new Vue({
      el:'#app',
      data : {
          styleobj : {color:'red','font-weight':'200'},
          styleobj2: {'font-style':'italic','letter-spacing': '0.5em'}
      }
    });
    </script>

3.12 v-for

  • 遍历普通数组

    <div id="app">
    <!--item 是每一次遍历到的对象/数据  index 索引-->
    <p v-for="(item,index) in list" v-text="'索引值:'+index+'  每一项:'+item" ></p>
    </div>
    
    <script>
    var vm = new Vue({
      el : '#app',
      data : {
          list : [1,2,3,4,5,6]
      }
    });
    </script>
  • 遍历数组对象

    <div id="app">
    <!--item 是每一次遍历到的对象/数据  index 索引-->
    <p v-for="(item,index) in list" v-text="'索引值:'+index+'  id:'+item.id+'   name:'+item.name" ></p>
    </div>
    
    <script>
    var vm = new Vue({
      el : '#app',
      data : {
          list : [
              {id : 1,name : 'ls'},
              {id : 2,name : 'zs'},
              {id : 3,name : 'ww'},
              {id : 4,name : 'cj'},
          ]
      }
    });
    </script>
  • 遍历对象

在遍历对象身上的键值对的时候,除了有val:值,key:属性名,在第三个位置还有index:索引

<div id="app">
    <!--val 是属性值,  key 是属性名,  index 是索引-->
    <p v-for="(val,key,index) in user " v-text="'索引:'+index+'  key: '+key+' value: '+val"></p>
</div>

<script>
    var vm = new Vue({
        el : '#app',
        data : {
            user : {
                id:1,
                name:'杨幂',
                gender:'woman',
                age : '12'
            }
        }
    });
</script>
  • 迭代数字

v-for迭代数字,i 从1开始

<div id="app">
    <p v-for="i in 10" v-text="'这是第'+i+'次循环~'"></p>
</div>

==v-for中key的使用注意事项==

在2.20+的版本里,当在组件中使用v-for时,key是必须的

在循环时,通过key来标识现在这一项的唯一身份

<div id="app">

    <input type="text" placeholder="id" v-model="id">
    <input type="text" placeholder="name" v-model="name">
    <input type="button" value="添加" @click="add">

    <!--注意:v-for 循环的时候,key属性只能使用number或string-->
    <!--注意:key 在使用的时候,必须使用v-bind: 属性绑定的形式指定key 的值-->
    <!--在组件中,使用v-for循环的时候,或者在一些特殊情况中,如果v-for有问题,必须在使用v-for的同时指定唯一的字符串或数字类型的 key值-->
    <p v-for="(item,index) in list" :key="item.id">
        <input type="checkbox">
        {{item.id}}-------------{{item.name}}
    </p>
</div>
<script>
    var vm = new Vue({
        el : '#app',
        data : {
            id:'',
            name:'',
            list : [
                {id :1,name:'刘备'},
                {id :2,name:'关羽'},
                {id :3,name:'张飞'},
                {id :4,name:'赵云'},
            ]
        },
        methods : {
            add(){
                var user = {id:this.id,name:this.name};
                this.list.unshift(user);
                this.id="";
                this.name="";
            }
        }
    });

3.13 v-for和v-show

==一般来说,v-if更高的切换消耗而v-show有更高的初始渲染消耗,因此,如果需要频繁切换,v-show较好,如果在运行时条件不大可能改变v-if较好==

<div id="app">
    <input type="button" value="切换" @click="change">
    <h2 v-if="flag">这是用v-if控制的元素</h2>
    <h2 v-show="flag">这是用v-show控制的元素</h2>
</div>

<script>
    var vm = new Vue({
        el : '#app',
        data : {
            flag:true
        },
        methods : {
            change(){
                this.flag = !this.flag;
            }
        }
    });
</script>

v-if 每次都会重新删除或创建元素

v-show 每次不会重新进行DOM的创建或删除操作,只是相当于是给标签加上了display:none样式


4. 增删查案例

<div id="app">
    <div class="panel panel-primary">
        <div class="panel-heading">
            <h3 class="panel-title">添加品牌</h3>
        </div>
        <div class="panel-body form-inline">
            <label>
                Id:
                <input type="text" class="form-control" v-model="id">
            </label>
            <label>
                Name:
                <input type="text" class="form-control" v-model="name">
            </label>
            <input type="button" class="btn btn-primary" value="添加" @click="add">
            <label>
                搜索名称关键字:
                <input type="text" class="form-control" @input="search" v-model="keywords">
            </label>

        </div>
    </div>
    <table class="table table-bordered table-hover table-striped">
        <thead>
            <tr>
                <th>Id</th>
                <th>Name</th>
                <th>Ctime</th>
                <th>Operation</th>
            </tr>
        </thead>
        <tbody>
            <!--之前,v-for中的数据,都是直接data上的list中直接渲染过来的-->
            <!--现在,我们自定义了一个search方法,同时,把搜索的关键字通过传参的形式,传递给search方法-->
            <!--search方法内部,通过执行for循环,把所有符合搜索关键字的数据保存到一个新数组中,返回-->
            <tr v-for="(item,index) in search(keywords)" :key="item.id">
                <th v-text="item.id"></th>
                <th v-text="item.name"></th>
                <th v-text="item.ctime"></th>
                <th><input type="button" value="删除" @click="del(item.id)"  class="btn btn-primary"></th>
            </tr>
        </tbody>
    </table>
</div>
<script>
    var vm = new Vue({
        el : '#app',
        data : {
            id:'',
            name:'',
            keywords:'',
            list : [
                {id:1,name:'奔驰',ctime:new Date()},
                {id:2,name:'宝马',ctime:new Date()},
                {id:3,name:'福特',ctime:new Date()},
                {id:4,name:'兰博基尼',ctime:new Date()},
                {id:5,name:'玛莎拉蒂',ctime:new Date()}
            ]
        },
        methods : {
            add(){
                this.list.push({id:this.id,name: this.name,ctime: new Date()});
                this.id="";
                this.name="";
            },
            del(id){
                that = this;
                this.list.forEach(function (item,index) {
                    if (item.id==id){
                        that.list.splice(index,1);
                    }
                });
            },
            search(){
                var keywords = this.keywords;
                // 方法一:
                // var nlist = [];
                // this.list.forEach(function (item,index) {
                //     if (item.name.indexOf(keywords)!=-1){
                //         nlist.push(item);
                //     }
                // })
                // return nlist;

                // 方法二:
                return this.list.filter(function (item) {
                    if (item.name.includes(keywords)){
                        return item;
                    }
                })
            }
        }
    });
</script>

5. 过滤器

Vue中允许自己定义过滤器,可被用作一些常见的文本格式化,过滤器可以用在两个地方:mustachc插值和v-bind表达式,过滤器应该被添加在JavaScript表达式尾部,有“管道”符指示

过滤器在调用时:

<p>{{name | nameope}}</p>
{{属性 | 过滤器名称}}

5.1 全局过滤器

所谓的全局过滤器,就是所有Vue实例共享的

全局过滤器的定义:

Vue.filter('过滤器名称',function (data) {
    // 对传过来的值做处理
})

过滤器中的function第一个参数已经被规定死了,永远都是过滤器管道符前面传递过来的数据,如{{name | nameope}},就是向nameope过滤器中传递name参数

==过滤器要定义在Vue实例之前==

调用过滤器时,也可以传递参数,要在定义过滤器时进行参数接收,可以传递多个参数

<div id="app">
    <h1>{{msg | msgFormat('阳光') }}</h1>
</div>
<script>
    // 定义一个vue的全局过滤器
    Vue.filter('msgFormat',function (data,arg) {
        return data.replace(/单纯/g,arg);
    });
    var vm = new Vue({
        el : '#app',
        data : {
            msg : '曾经,我也是一个单纯的少年,单纯的我傻傻的问,谁是世界上最单纯的男人'
        }
    });
</script>

==可以调用多个过滤器==,多个过滤器之间用管道符 | 隔开

<div id="app">
    <h1>{{msg | msgFormat('疯狂') | test }}</h1>
</div>
<script>
    // 定义一个vue的全局过滤器
    Vue.filter('msgFormat',function (data,arg) {
        return data.replace(/单纯/g,arg);
    });
    Vue.filter('test',function (data) {
        return data + "-------这是第二个过滤器"
    })
    var vm = new Vue({
        el : '#app',
        data : {
            msg : '曾经,我也是一个单纯的少年,单纯的我傻傻的问,谁是世界上最单纯的男人'
        }
    });
</script>

调用多个过滤器时,第一个过滤器处理完成后,交给第二个过滤器,最后一个过滤器处理完成后,输出结果~

时间格式化:

 <th>{{item.ctime | timeFormat}}</th>
// 进行时间的格式化
<script>
    Vue.filter('timeFormat',function (data) {
        var dt = new Date(data);
        var y = dt.getFullYear();
        var m = dt.getMonth()+1;
        var d = dt.getDate();
        var h = dt.getHours();
        var mi = dt.getMinutes();
        var s = dt.getSeconds();
        return `${y}-${m}-${d} ${h}:${mi}:${s}`;   // 模板字符串
    })
</script>

5.2 定义私有(局部)过滤器

<div id="app2">
    <h2>{{dt|dtFormat}}</h2>
</div>
<script>
    var vm2 = new Vue({
        el : '#app2',
        data : {
            dt : new Date()
        },
        methods: {

        },
        filters: {
            dtFormat: function (data) {
                var dt = new Date(data);
                var y = dt.getFullYear();
                var m = dt.getMonth();
                var d = dt.getDate();
                var h = dt.getHours();
                var min = dt.getMinutes();
                var s = dt.getSeconds();
                return `${y}-${m}-${d}  ${h}:${min}:${s}`;
            }
        }
    });
</script>

过滤器调用的时候采取就近原则,如果私有过滤器和全局过滤器名称一致,这时候优先调用私有过滤器

补充:ES6中字符串新方法padStart,padEnd

string.prototype.padStart(maxLength,fillString="");//参数1:填充长度后总长度 参数2:用什么填充
string.prototype.padEnd(2,'0');  // 参数1:填充长度后总长度  参数2:用什么填充

6. 自定义按键修饰符

<!--当回车键抬起时执行add方法-->
<input type="text" @keyup.enter="add"> 

vue中提供的

.enter            .up            .tab
.down            .delete        .left
.esc(捕获删除和退格键)           .right
.space

我们可以查询js里面的键盘时间对应的键盘码

<!--键盘吗113对应的键是F2  当F2键抬起时执行add方法-->
<input type="text" @keyup.113="add"> 

我们可以自定义全局按键修饰符

<input type="text" @keyup.f2="add"> 
<script>
    Vue.config.keyCodes.f2=113;
</script>

这样,按键被定义后就可以直接使用了,相当于对键盘码取了一个别名,如果未定义不可以直接使用f2


7. 指令

在Vue中,所有的指令,在调用的时候都以v-开头

7.1 自定义全局指令

原生js中,文本框自动获取焦点

document.getElementById("search").focus();

但是在Vue中,不提倡这么做,我们可以通过自定义指令来实现

使用Vue.directive定义全局指令,其中参数1:指令的名称,在定义的时候,指令名不与要加前缀v-,在调用时,必须在指令名称前加v-前缀。参数2:是一个对象,这个对象上,有一些指令相关的函数,这些函数可以在特定的阶段,执行相关的操作。

  • bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作
  • inserted:被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于document中)
  • update:所有组件的VNode更新时调用,但是可能发生在其孩子的VNode更新之前,指令的值可能发生了改变,也可能没改变
  • componentUpdated:所有组件的VNode及其孩子的VNode全部更新时调用
  • unbind:只调用一次,指令与元素解绑时调用

在每个函数中,第一个参数永远是el,表示被绑定的那个元素,这个el参数,是一个原生的js对象

定义全局指令

<script>
    // 参数1:指令的名称,在定义的时候,指令的名称前面,不需要加 v- 前缀,在调用的时候必须加上v-前缀
    // 参数2:是一个对象,这个对象上,有一些指令相关的函数,这些函数可以在特定的阶段,执行相关的操作
    Vue.directive('focus',{
        bind:function (el) {
            // 每当指令绑定到元素上的时候,会立即执行这个bind函数,只执行一次
            // 在元素刚绑定了指令的时候,还没有插入到DOM中去,这时候调用focus方法没有作用
        },
        inserted:function (el) {
            // 元素插入到DOM中的时候会执行inserted函数  ,只触发一次
            el.focus();
        },
        updated:function (el) {
            // 当组件VNode更新的时候会执行updated  可能会触发多次
        }
    })
</script>

和JS行为有关的操作,最好在inserted函数中执行,防止JS行为不生效,和样式相关的操作,一般都在bind函数中执行

Vue.directive('color',{
    // 样式,只要通过指令绑定给了元素,不管这个元素有没有被插入到页面中去,这个元素肯定有了一个内联的样式
    //将来元素肯定会显示到页面中,这时候,浏览器的渲染引擎必然会解析样式,应用给这个元素
    bind:function (el) {
        el.style.color="red";
    }
})

钩子函数的参数

  • el:指令所绑定的元素,可以用来直接操作DOM
  • binding:一个对象包含以下属性:

    • name:指令名,不包括 v- 前缀
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • vnode:Vue 编译生成的虚拟节点
  • oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用
<input type="text" v-color="'green'">
Vue.directive('color',{
    // 样式,只要通过指令绑定给了元素,不管这个元素有没有被插入到页面中去,这个元素肯定有了一个内联的样式
    //将来元素肯定会显示到页面中,这时候,浏览器的渲染引擎必然会解析样式,应用给这个元素
    bind:function (el,bindding) {
        el.style.color=bindding.value;   // 通过bindding参数来获取传递的参数
    }
})

7.2 定义私有指令

<div id="app2">
    <p v-size="'50px'" v-text="msg"></p>
</div>
<script>
    var vm2 = new Vue({
        el : '#app2',
        data : {
            msg: 'hello,world!'
        },
        methods: {

        },
        filters: {
            
        },
        directives: {
            'size' : {
                bind : function (el,binding) {
                    el.style.fontSize=binding.value;
                }
            }
        }
    });
</script>

8. Vue实例生命周期函数

什么是生命周期?

从Vue实例创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期!

生命周期钩子:就是生命周期事件的别名!

生命周期钩子=生命周期函数=生命周期事件

主要生命周期函数分类:

  • 创建期间的生命周期函数:

    • beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好data和methods属性
    • created:实例已经在内存中创建OK,此时data和methods已经创建OK,此时还没有开始编译模板
    • beforeMount:此时已经完成了模板编译,但还没有挂载到页面中
    • mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示
  • 运行期间的生命周期函数:

    • beforeUpdate:状态更新之前执行此函数,此时data中的状态值是最新的,但是界面上显示的数据还是旧的,因为此时还没有开始重新渲染DOM节点
    • updated:实例更新完毕之后调用此函数,此时data中的状态值和界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了
  • 销毁期间的生命周期函数:

    • beforeDestory:实例销毁之前调用,在这一步,实例仍然完全可用
    • destroyed:Vue实例销毁后调用,调用后,Vue实例指示的所有东西都会解绑定,所有的时间监听器会被移除,所有的子实例也会被销毁

<div id="app">
    <input type="button" value="改变msg" @click="msg='No'">
    <h3 id="h3">{{msg}}</h3>
</div>

<script>
    var vm = new Vue({
        el : '#app',
        data : {
            msg : 'ok'
        },
        methods: {
            show : function () {
                console.log("执行了show方法~");
            }
        },
        beforeCreate:function () {
            // 这是我们遇到的第一个生命周期函数,实例被创建出来之前,执行它
            // 在beforeCreate生命周期函数执行的时候,data和methods中的数据都还没有初始化
            console.log(this.msg);   // undefined
            // this.show();   // this.show() is not a function
        },
        created: function () {
            // 在created中,data和methods中的数据都已经被初始化好了
            // 如果要调用methods中的方法,或者操作data中的数据,最早只能在该方法中
            console.log(this.msg);   // ok
            this.show();   // 执行了show方法~
        },
        beforeMount :function () {
            // 模板已经编译完成了,但是尚未把模板渲染到页面中
            // 在beforeMount执行的时候,页面中的元素还没有真正被替换过来,只是之前写的一些模板字符串
            console.log(document.getElementById("h3").innerText);   // {{msg}}
        },
        mounted :function () {
            // 内存中的模板,已经真实的挂载到了页面中,用户已经可以看到渲染好的页面了
            // mounted 是实例创建期间的最后一个生命周期函数,当执行完mounted就表示,实例已经被完全创建好了
            // 此时,如果没有其它操作的话,这个实例就静静的存在内存中
            // 如果要通过某些插件操作页面上的dom节点,最早要在该方法中
            console.log(document.getElementById("h3").innerText);   // ok
        },
        beforeUpdate : function () {
            // 这时候表示,我们的界面还没有被更新,但是data数据已经被更新了
            // 当执行该函数的时候,页面中显示的数据还是旧的,此时data中的数据是最新的,页面尚未和最新的数据保存同步
            console.log("界面上元素的内容:"+document.getElementById("h3").innerText); // 界面上元素的内容:ok
            console.log("data中的msg数据:"+this.msg);  // data中的msg数据:No
        },
        updated:function () {
            // 页面和data数据已经保持同步了,都是最新的
            console.log("界面上元素的内容:"+document.getElementById("h3").innerText); // 界面上元素的内容:No
            console.log("data中的msg数据:"+this.msg);  // data中的msg数据:No
        },
        beforeDestroy : function () {
            // 当执行该函数时,就已经从运行阶段进入了销毁阶段
            // 当执行该函数时,实例身上所有的data和所有的methods以及过滤器,指令等都处于可用状态,还没有真正执行销毁过程
        },
        destroyed : function () {
            // 当执行到该函数时,实例已经被完全销毁了,此时实例身上所有的data和所有的methods以及过滤器,指令等都不可用了
        }
    });
</script>

9. vue-resource

实现get,post,jsonp请求

首先要导入vue-resource.js cdn:

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue-resource/1.5.1/vue-resource.min.js"></script>

==注意:vue-resource依赖于vue,所以要先导入vue在导入vue-resource==

// global Vue object
Vue.http.get('/someUrl', [config]).then(successCallback, errorCallback);
Vue.http.post('/someUrl', [body], [config]).then(successCallback, errorCallback);

// in a Vue instance
this.$http.get('/someUrl', [config]).then(successCallback, errorCallback);
this.$http.post('/someUrl', [body], [config]).then(successCallback, errorCallback);
  • get(url, [config])
  • head(url, [config])
  • delete(url, [config])
  • jsonp(url, [config])
  • post(url, [body], [config])
  • put(url, [body], [config])
  • patch(url, [body], [config])

Config

ParameterTypeDescription
urlstringURL to which the request is sent
bodyObject, FormData, stringData to be sent as the request body
headersObjectHeaders object to be sent as HTTP request headers
paramsObjectParameters object to be sent as URL parameters
methodstringHTTP method (e.g. GET, POST, ...)
responseTypestringType of the response body (e.g. text, blob, json, ...)
timeoutnumberRequest timeout in milliseconds (0 means no timeout)
credentialsbooleanIndicates whether or not cross-site Access-Control requests should be made using credentials
emulateHTTPbooleanSend PUT, PATCH and DELETE requests with a HTTP POST and set the X-HTTP-Method-Override header
emulateJSONbooleanSend request body as application/x-www-form-urlencoded content type
beforefunction(request)Callback function to modify the request object before it is sent
uploadProgressfunction(event)Callback function to handle the ProgressEvent of uploads
downloadProgressfunction(event)Callback function to handle the ProgressEvent of downloads

Response

A request resolves to a response object with the following properties and methods:

PropertyTypeDescription
urlstringResponse URL origin
bodyObject, Blob, stringResponse body
headersHeaderResponse Headers object
okbooleanHTTP status code between 200 and 299
statusnumberHTTP status code of the response
statusTextstringHTTP status text of the response
MethodTypeDescription
text()PromiseResolves the body as string
json()PromiseResolves the body as parsed JSON object
blob()PromiseResolves the body as Blob object
<script>
    var vm = new Vue({
        el : '#app',
        data : {

        },
        methods : {
            getInfo : function () {  // 发起get请求
                // 当发起get请求后,通过.then来设置成功的回调函数
                this.$http.get('localhost:8080/getTest',{params:{"arg":3}})
                    .then(function (result) {
                    // 请求成功执行回调函数
                    console.log(result);
                },function () {
                    // 请求失败执行回调函数
                    console.log("请求失败!");
                })
            },
            postInfo:function () {
                // 发起post请求
                // 手动发起的post请求,默认没有表单格式,有的服务器处理不了
                // 通过post方法的第三个参数, {emulateJSON:true} 设置提交的内容类型为普通表单数据
                this.$http.post('localhsot:8080/postTest',{params: {"id":1}},{})
                    .then(function (result) {
                    // 请求成功
                    console.log(result)
                },function () {
                    // 请求失败
                    console.log("请求失败!")
                })
            },
            jsonpInfo : function () { //发起jsonp请求
                this.$http.jsonp('localhost:8080/jsonpTest',{params:{"id":2}}).then(function (result) {
                    // 请求成功
                    //  返回的为字符串,不是json对象
                    console.log(result);
                }, function () {
                    console.log("请求失败");
                })
            }
        }
    });
</script>

vue-rosource可以进行全局配置来设置根路径:

Vue.http.options.root="http://localhost:8080";

// 在发起请求时
// 路径会自动拼接为 http://localhost:8080/test
this.$http.get("/test").then(function(result){
    ...
});

全局配置emulateJSON选项

Vue.http.options.emulateJSON = true;

这样在发起POST请求时,就不用再单独设置提交的内容类型为普通表单数据了

jsonp的实现原理:

  • 由于浏览器的安全性限制,不允许AJAX跨域请求(访问协议不同,域名不同,端口号不同)的数据接口,浏览器认为这种访问不安全
  • 可以通过动态创建script标签的形式,把script标签的src属性,指向数据接口的地址,因为script标签不存在跨域限制,这种数据获取方式,乘坐jsonp(注意:==jsonp只支持get请求==)

所以,在实际的开发中,采用前后端分离,前后端部署在不同的服务器上,为了解决跨域问题,一般都会使用jsonp请求


10. axios

Axios是一个开源的可以用在浏览器和Node.js的异步通信框架,主要作用的实现AJAX异步通信

// get请求
方法里可以只写路径。如果请求失败捕获一下异常。

axios
  .get('http://rap2api.taobao.org/app/mock/23080/resources/search',{
      params: {
         id: 5
      }
   })
  .then(res => {
    console.log('数据是:', res);
  })
  .catch((e) => {  // 处理异常
    console.log('获取数据失败');
  });

// post请求
this.$axios.post('http://rap2api.taobao.org/app/mock/121145/post',{
  name: '小月'
})
.then(function(res){
  console.log(res);
})
.catch(function(err){
  console.log(err);
});


// 一次合并发送多个请求
function getUserAccount(){
  return axios.get('/user/12345');
}
function getUserPermissions(){
  return axios.get('/user/12345/permissions');
}
this.$axios.all([getUserAccount(),getUserPermissions()])
  .then(axios.spread(function(res1,res2){
    //当这两个请求都完成的时候会触发这个函数,两个参数分别代表返回的结果
}))

首先要导入axios,通过cdn

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    var vm = new Vue({
        el : '#app',
        methods : {

        },
        data() {
            return {
                msg:"helloworld!",
                // 请求返回参数合适,必须和json字符串一样
                info : {
                    user : {
                        id :null,
                        name:null
                    }
                }
            }
        },
        mounted:function() {
            that = this;   // 要注意this的指向问题
            axios.get('data.json').then(function (result) {
                that.info = result.data.user;
            })
        }
    });
</script>


<!--下面这种写法也是可以的  只是data中的数据的全局可见的 会造成变量污染  所以我们一般会使用上面那种方法,使用return包裹,数据中的变量只在当前组件中生效,不会影响其他组件-->
<script>
    var vm = new Vue({
        el : '#app',
        data : {
            msg: "hello",
            info : {
                user : {
                    id : null,
                    name : null
                }
            }
        },
        methods : {

        },
        mounted:function() {
            that = this;
            axios.get('data.json').then(function (result) {
                that.info = result.data.user;
            })
        }
    });
</script>

==为什么要写renturn返回==
因为不使用return包裹的数据会在项目的全局可见,会造成变量污染
使用return包裹后数据中变量只在当前组件中生效,不会影响其他组件。

参数

1、url(必写)

请求地址

2、method

请求方法,默认是get

3、baseURL(常用)

baseURL会添加到url前(url是绝对地址除外)。

4、transformRequest

transformRequest选项允许我们在请求发送到服务器之前对请求的数据做出一些改动
该选项只适用于以下请求方式:put/post/patch
5、transformResponse
transformResponse选项允许我们在数据传送到then/catch方法之前对数据进行改动
6、headers(常用,如设置请求头json类型)
自定义请求头信息
7、params(常用,只有get请求设置params,其他请求需设置params,即只有get的请求参数位于url后,其他请求参数都在请求体中)
params选项是要随请求一起发送的请求参数----一般链接在URL后面
8、data(常用)
data选项是作为一个请求体而需要被发送的数据,该选项只适用于方法:put/post/patch
在浏览器上data只能是FormData, File, Blob格式
9、timeout(常用)
超时时间
10、withCredentials
withCredentails选项表明了是否是跨域请求、默认是default
11、onUploadProgress
onUploadProgress上传进度事件
12、onDownloadProgress
下载进度的事件
13、maxContentLength
相应内容的最大值

==注意:axios也要跨域问题!!axios只有两种请求方式:GET和POST==

11. 计算属性

可以把计算属性想象成缓存

当其中的某个数据发生改变时,其数据才会更新!!

在computed中可以定义一些属性,这些属性叫做【计算属性】,计算属性的本质就是一个方法,只不过,我们在使用这些计算属性的时候,是把他们的名称直接当作属性来使用,并不会把计算属性当作方法来使用

==注意:计算属性,在引用的时候,一定不要加()去调用,直接把他当作普通属性==

==注意:计算属性内部,所依赖的任何data中的数据,只要发生变化,必然会重新计算 这个 计算属性的值==

==注意:计算属性的求值结果,会被缓存起来,方便下次直接使用,如果,计算属性方法中,所依赖的任何数据,都没有发生过变化,则不会重新对计算属性求值==

<div id="app">
    <h3>currentTime1  :  {{currentTime1()}}</h3>
    <h3>currentTime2  :  {{currentTime2}}</h3>
</div>

<script>
    var vm = new Vue({
        el : '#app',
        data : {
            msg : "hello,world!"
        },
        methods: {
            currentTime1 : function () {
                return new Date();
            }
        },
        computed:{
            // 计算属性,methods和computed方法名重名后,只会调用methods中的方法
            // 计算属性中的方法通过属性来调用,不用加()
            currentTime2 : function () {
                this.msg;  // 当msg的数据改变时,返回结果才会重新刷新
                return new Date();
            }
        }
    })
</script>

将那些不经常变化的数据,我们可以将其放入缓存中!


12. 动画

12.1 使用过度类名实现动画

Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡

  • 条件渲染 (使用 v-if)
  • 条件展示 (使用 v-show)
  • 动态组件
  • 组件根节点

在进入/离开的过渡中,会有 6 个 class 切换。

  1. v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
  2. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
  3. v-enter-to2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
  4. v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
  5. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
  6. v-leave-to2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。
<!-- 自定义两组样式,来控制transition内部的元素实现动画 -->
<style>

    /* v-enter【这是一个时间点】 进入之前,元素的起始状态,此时还没有开始进入 */
    /* v-leave-to 【这是一个时间点】 是动画离开之后,离开的终止状态,此时,元素动画已经结束了 */
    .v-enter,
    .v-leave-to {
        /* 透明度为0 */
        opacity: 0;
        transform: translate(80px);
    }

    /* v-enter-active 【入场动画的时间段】 */
    /* v-leave-active 【离场动画的时间段】 */
    .v-enter-active,
    .v-leave-active {
        transition: all 0.4s ease;
    }
</style>
</head>
<body>
    <div id="app">
        <!-- 需求:点击按钮,让h3显示,再点击按钮,让h3隐藏 -->
        <input type="button" value="切换" @click="flag=!flag">

        <!-- 使用transition元素,把需要被动画控制的元素包裹起来 -->
        <!-- transition元素是Vue官方提供的元素 -->
        <transition><h3 v-if="flag">这是一个h3</h3></transition>
    </div>

    <script>
        var vm = new Vue({
            el : '#app',
            data : {
                flag: false
            }
        });
    </script>
</body>

对于这些在过渡中切换的类名来说,如果你使用一个没有名字的<transition> ,则 v-是这些类名的默认前缀。如果你使用了<transition name=me>,那么v-enter会替换为me-enter

12.2 使用第三方animate.css类库实现动画

Animate.css官网:https://animate.style/

animate.css cdn:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.0.0/animate.min.css">
<div id="app">
    <input type="button" value="切换" @click="flag=!flag">
    <transition enter-active-class="animate__animated animate__bounce" leave-active-class="animate__animated animate__wobble" :duration="{enter:200,leave:400}">
        <h3 v-show="flag">这是一个H3</h3>
    </transition>
</div>

<script>
    var vm = new Vue({
        el : '#app',
        data : {
            flag : false
        }
    });
</script>

12.3 钩子函数实现半场动画

半场动画:只有入场,没有离场的动画

<!--相当于动画的生命周期-->
<transition      
  v-on:before-enter="beforeEnter"
  v-on:enter="enter"
  v-on:after-enter="afterEnter"
  v-on:enter-cancelled="enterCancelled"

  v-on:before-leave="beforeLeave"
  v-on:leave="leave"
  v-on:after-leave="afterLeave"
  v-on:leave-cancelled="leaveCancelled"
>
  <!-- ... -->
</transition>
// ...
methods: {
  // --------
  // 进入中
  // --------

    // 动画钩子函数的第一个参数,el 表示要执行动画的那个DOM元素
  beforeEnter: function (el) {
    // ...
  },
  // 当与 CSS 结合使用时
  // 回调函数 done 是可选的
  enter: function (el, done) {
    // ...
    done()
  },
  afterEnter: function (el) {
    // ...
  },
  enterCancelled: function (el) {
    // ...
  },

  // --------
  // 离开时
  // --------

  beforeLeave: function (el) {
    // ...
  },
  // 当与 CSS 结合使用时
  // 回调函数 done 是可选的
  leave: function (el, done) {
    // ...
    done()
  },
  afterLeave: function (el) {
    // ...
  },
  // leaveCancelled 只用于 v-show 中
  leaveCancelled: function (el) {
    // ...
  }
}

如果要实现半场动画,只需要写入场或者离场的就可以了!!

12.4 小球半场动画

<div id="app">
    <input type="button" value="执行" @click="flag=!flag">
    <transition v-on:before-enter="beforeEnter"
                v-on:enter="enter"
                v-on:after-enter="afterEnter">
        <div class="ball" v-show="flag"></div>
    </transition>
</div>

<script>
    var vm = new Vue({
        el : '#app',
        data : {
            flag : false
        },
        methods : {
            beforeEnter : function(el){
                // 入场之前 , 此时动画尚未开始
                // 可以在beforeEnter中设置元素开始动画之前的起始样式
                // 设置小球开始动画之前的起始位置
                el.style.transform = "translate(0,0)";
            },
            enter : function(el,done){
                // 这句话没有实际的作用,但是不写出不来动画效果
                // 可以任务el.offsetWidth会强制动画刷新
                el.offsetWidth;
                // enter表示动画开始之后的样式,这里,可以设置小球完成动画之后的结束状态
                el.style.transform = "translate(150px,450px)";
                el.style.transition = "all 1s ease";

                // 这里的done起始就是afterEnter函数,也就是说:done是函数afterEnter的引用
                // 当只用 JavaScript 过渡的时候,在 enter 和 leave 中必须使用 done 进行回调。否则,它们将被同步调用,过渡会立即完成。
                done();
            },
            afterEnter : function(el){
                // 动画完成之后,会调用该函数
                this.flag = !this.flag;
            }
        }
    })
</script>

12.5 列表动画

==在实现列表过渡的时候,如果需要过渡的元素是通过v-for循环渲染出来的,不能使用<transition>标签包裹,需要使用<transition-group>标签包裹 ==

==如果要为 v-for 循环创建的元素设置动画,必须为每一个元素设置 :key 属性==

==给transition-group添加appear属性,实现当页面刚展示出来的时候,入场时候的效果==

==通过为transiont-group设置tag属性,指定transition-group渲染为指定的元素,如果不指定tag属性,则默认渲染为span==

<style>
    li {
        border: 1px dashed #999;
        margin: 5px;
        line-height: 35px;
        padding-left: 5px;
        font-size: 12px;
        width: 100%;
    }

    li:hover {
        background-color: #999;
        transition: all 0.4s ease;
    }

    .v-enter,
    .v-leave-to {
        opacity: 0;
        transform: translateY(50px);
    }

    .v-enter-active,
    .v-leave-active {
        transition: all 1s ease;
    }

    /* v-move  和 v-leave-acvice 配合使用能够实现后续的元素渐渐的补齐 */
    .v-move {
        transition: all 0.4s ease;
    }

    .v-leave-active {
        position: absolute;
    }
</style>
</head>
<body>
    <div id="app">
        <div>
            <label>id: <input type="text" placeholder="id" v-model="id"> </label>
            <label>name: <input type="text" placeholder="name" v-model="name" @keyup.enter="add"></label>
            <input type="button" value="添加" @click="add">
        </div>
        <!-- 在实现列表过渡的时候,如果需要过渡的元素是通过v-for循环渲染出来的,不能使用<transition>标签包裹,需要使用<transition-group>标签包裹 -->
        <!-- 如果要为 v-for 循环创建的元素设置动画,必须为每一个元素设置 :key 属性 -->
        <!--给transition-group添加appear属性,实现入场时候的效果-->
        <!--通过为transiont-group设置tag属性,指定transition-group渲染为指定的元素,如果不指定tag属性,则默认渲染为span-->
        <transition-group appear tag="ul">
            <li v-for="(user,index) in list" :key="user.id" @click="del(index)">{{user.id}}------------{{user.name}}</li>
        </transition-group>
    </div>

    <script>
        var vm = new Vue({
            el : '#app',
            data : {
                id: "",
                name:"",
                list : [
                    {id:1,name:'唐嫣'},
                    {id:2,name:'陈乔恩'},
                    {id:3,name:'刘诗诗'},
                    {id:4,name:'刘亦菲'},
                    {id:5,name:'白百合'}
                ]
            },
            methods : {
                add: function(){
                    this.list.push({id:this.id,name:this.name});
                    this.id=this.name="";
                },
                del:function(index){
                    this.list.splice(index,1);
                }
            }
        })
    </script>
</body>

13. 组件

什么是组件:组件的出现,就是为了拆分Vue实例的代码量的,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件!

组件化和模块化的不同:

  • 模块化是从代码逻辑的角度进行划分,方便代码分层开发,保证每个功能模块的只能单一
  • 组件化是从UI界面的角度进行划分的,前端的组件化,方便UI组件的重用

13.1 创建组件的方式

方式一: 使用Vue.extend 来创建全局的Vue组件

  • 使用Vue.extend来创建全局的Vue组件 (通过template属性指定了组件要展示的HTML结构)
  • 使用Vue.component('组件名称',创建出来的组件模板对象)
  • 如果要使用组件 直接,把组件名称以Html的形式引入到页面中即可

==注意:!!在创建全局组件时,组件名称可以用驼峰命名,但是在引用组件时,要使用 - 的形式连接,大写变小写==

<div id="app">
    <!-- 如果要使用组件 直接,把组件名称以Html的形式引入到页面中即可 -->
    <!-- 注意:!!在创建全局组件时,组件名称可以用驼峰命名,但是在引用组件时,要使用 - 的形式连接,大写变小写 -->
    <my-com1></my-com1>
</div>

<script>
    // 1 使用Vue.extend来创建全局的Vue组件
    var com1 = Vue.extend({
        template : '<h3>hello,world</h3>', // 通过template属性指定了组件要展示的HTML结构

    });

    // 2 使用Vue.component('组件名称',创建出来的组件模板对象)
    Vue.extend('myCom1',com1);


    var vm = new Vue({
        el : '#app',
        data : {

        }
    });
</script>

==Vue.extend 和 Vue.extend 合并为一步==

Vue.component 第一个参数:组件名称 第二个参数:Vue.extend 创建的组件,其中template属性就是组件将来要展示的html

<div id="app">
    <!-- 如果要使用组件 直接,把组件名称以Html的形式引入到页面中即可 -->
    <!-- 注意:!!在创建全局组件时,组件名称可以用驼峰命名,但是在引用组件时,要使用 - 的形式连接,大写变小写 -->
    <my-com1></my-com1>
</div>

<script>
    // Vue.component 第一个参数:组件名称  第二个参数:Vue.extend 创建的组件,其中template属性就是组件将来要展示的html
    Vue.component('myCom1',Vue.extend({
        template : '<h3>hello world!</h3>'
    }));

    var vm = new Vue({
        el : '#app',
        data : {

        }
    });
</script>

方式二:相当于对方式一的简化

<script>
    Vue.component('mycom2',{
        //  注意,无论是哪种方式创建出来的组件,组件的template属性指向的模板内容必须有且只能有唯一的一个根元素
        template : '<h3>hello,world!</h3>'
    })

    var vm = new Vue({
        el : '#app',
        data : {

        }
    });
</script>

方式三:使用<template>标签定义组件模板

在被控制的 #app 外面,使用template元素定义组件的模板结构

<!-- 在被控制的 #app 外面,使用template元素定义组件的模板结构 -->
<template id="temp">
    <h2>hello,world!1</h2>
</template>

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


<script>

    Vue.component('mycom',{
        template : '#temp'
    })

    var vm = new Vue({
        el : '#app',
        data : {

        }
    });
</script>

13.2 使用components定义私有组件

<template id="mycom">
    <h2>hello,world!</h2>
</template>

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


<script>
    var vm = new Vue({
        el : '#app',
        data : {

        },
        components : {
            mycom : {
                template : '#mycom'
            }
        }
    })
</script>

13.3 组件中的data

data

组件可以有自己的data数据

组件中的data和实例的data有点不一样,实例中的data可以为一个对象,但是组件中的data必须是一个方法

组件中的data除了必须为一个方法外,这个方法内部还必须返回一个对象

组件中的data数据,使用方式,和实例中的data使用方式完全一样!!

<template id="temp">
    <h3 v-text="msg"></h3>
</template>
<div id="app">
    <mycom></mycom>
</div>

<script>
    Vue.component('mycom',{
        template : '#temp',
        data(){
            return {
                msg : 'hello,world!'
            }
        }
    })
    var vm = new Vue({
        el : '#app',
        data : {

        }
    });
</script>

组件中的data必须是一个方法,这样组件中data数据就是组件私有的了,不会影响到其他组件!

<template id="temp">
    <div>
        <input type="button" value="+1" @click = "increment">
        <h3>{{num}}</h3>
    </div>
</template>
<div id="app">
    <mycom></mycom>
    <hr>
    <mycom></mycom>
    <hr>
    <mycom></mycom>
    <hr>
</div>
<script>
    var dataObj = {num : 0}
    Vue.component('mycom',{
        template : '#temp',
        data(){
            return dataObj
        },
        methods : {
            increment : function(){
                console.log(this.num)
                this.num++;
            }
        }
    })
    var vm = new Vue({
        el : '#app'
    });
</script>

==如果像这样,组件中的data数据就不是私有的了,多次引用同一个组件mycom,其中一个变化,其他组件中的data数据也会随之变化==,所以组件中的data必须是一个方法

<template id="temp">
    <div>
        <input type="button" value="+1" @click = "increment">
        <h3>{{num}}</h3>
    </div>
</template>
<div id="app">
    <mycom></mycom>
    <hr>
    <mycom></mycom>
    <hr>
    <mycom></mycom>
    <hr>
</div>
<script>
    Vue.component('mycom',{
        template : '#temp',
        data(){
            return {
                num : 0
            }
        },
        methods : {
            increment : function(){
                console.log(this.num)
                this.num++;
            }
        }
    })
    var vm = new Vue({
        el : '#app'
    });
</script>

image-20200528100708138

13.4 组件切换

方式一:使用v-if和v-else结合flag进行切换

<template id="login">
    <h1>登录</h1>
</template>
<template id="register">
    <h1>注册</h1>
</template>

<div id="app">
    <input type="button" value="登录" @click = "flag = true" >
    <input type="button" value="注册" @click = "flag = false">
    <login v-if="flag"></login>
    <register v-else="flag"></register>
</div>
<script>
    Vue.component('login',{
        template : '#login'
    })

    Vue.component('register',{
        template : '#register'
    })

    var vm = new Vue({
        el : '#app',
        data : {
            flag : true
        }
    })
</script>

方式二:使用Vue提供的component元素实现组件切换

vue提供了component元素来展示对应名称的组件

<template id="login">
    <h1>登录</h1>
</template>
<template id="register">
    <h1>注册</h1>
</template>

<div id="app">
    <!-- vue提供了component元素来展示对应名称的组件  -->
    <!-- component 是一个占位符   :is 属性,可以用来指定要展示的组件的名称 -->
    <input type="button" value="登录" @click = "comName = 'login'">
    <input type="button" value="注册" @click = "comName = 'register'">
    <component :is="comName"></component>
</div>

<script>
    Vue.component('login',{
        template : '#login'
    })

    Vue.component('register',{
        template : '#register'
    })

    var vm = new Vue({
        el : '#app',
        data : {
            comName : 'login'
        }
    })
</script>

方式三:应用切换动画和model方式

直接用<transition>包裹<component>组件标签,<transition>标签有一个属性mode,通过mode属性设置组件切换时候的方式,如out-in是先离场,后进场

<style>
    .v-enter,
    .v-leave-to {
        opacity: 0;
        transform: translateY(150px);
    }

    .v-enter-active,
    .v-leave-active{
        transition: all 1s ease;
    }

</style>
<body>

    <template id="login">
        <h1>登录</h1>
    </template>

    <template id="register">
        <h1>注册</h1>
    </template>

    <div id="app">
        <!-- vue提供了component元素来展示对应名称的组件  -->
        <!-- component 是一个占位符   :is 属性,可以用来指定要展示的组件的名称 -->
        <input type="button" value="登录" @click = "comName = 'login'">
        <input type="button" value="注册" @click = "comName = 'register'">

        <!-- 通过mode属性设置组件切换时候的方式  out-in 先离场后进场 -->
        <transition mode="out-in">
            <component :is="comName"></component>
        </transition>
    </div>
    

    <script>
        Vue.component('login',{
            template : '#login'
        })

        Vue.component('register',{
            template : '#register'
        })

        var vm = new Vue({
            el : '#app',
            data : {
                comName : 'login'
            }
        })
    </script>
</body>

13.5 组件传值

父向子组件传值

  • 子组件,默认不能够直接访问到父组件data中的数据和methods中的方法
  • 父组件,可以在引用子组件的时候,通过属性绑定(v-bind)的形式,把需要传递给子组件的数据,以属性绑定的形式,传递到子组件内部,供子组件使用
  • 注意:组件中的所有props中的数据,都是通过父组件传递给子组件的
  • props 中的数据都是只读的,无法重新赋值,data中的数据都是可读可写的
<div id="app">
    <!-- 父组件,可以在引用子组件的时候,通过属性绑定(v-bind)的形式,把需要传递给子组件的数据,以属性绑定的形式,传递到子组件内部,供子组件使用 -->
    <com1 :parentmsg="msg"></com1>
</div>

<script>
    var vm = new Vue({
        el : '#app',
        data : {
            msg : '123 父组件中的数据'
        },
        components : {
            // 子组件,默认不能够直接访问到父组件data中的数据和methods中的方法
            com1 : {
                template : '<h1>这是子组件---{{parentmsg}}</h1>',
                // 注意:组件中的所有props中的数据,都是通过父组件传递给子组件的
                // props 中的数据都是只读的,无法重新赋值,data中的数据都是可读可写的
                // 把父组件传递过来的 parentmsg 属性,现在props 数组中,定义一下,这样,才能使用这个数据
                props:['parentmsg'],
                data(){
                    return {
                        parentmsg : 'hello'
                    }
                }
            }
        }
    })
</script>

父组件把方法传递给子组件,子组件通过事件调用向父组件传值

  • 父组件 向子组件传递方法,使用的是事件绑定机制 v-on: 当我们自定义了一个事件属性之后,那么子组件就能通过某些方式来调用传递进去的这个方法了
  • 通过 this.$emit('绑定的事件属性名') 来拿到并执行父组件的方法
  • 相当于子组件调用父组件的方法,方法还是由父组件来执行
<template id="temp">
    <div>
        <h1>这是子组件</h1>
        <input type="button" @click="myclick" value="点击一下">
    </div>
</template>
<div id="app">
    <!-- 父组件 向子组件传递方法,使用的是事件绑定机制 v-on: 当我们自定义了一个事件属性之后,那么子组件就能通过某些方式来调用传递进去的这个方法了 -->
    <com1 @func="show"></com1>
</div>

<script>
    //  定义了一个字面量类型的组件模板对象
    var com1 = {
        template : "#temp",
        methods : {
            myclick : function(){
                // 当点击子组件按钮的时候 拿到父组件传递过来的方法并调用这个方法
                // emit 触发
                this.$emit('func',this.sonmsg,789)
            }
        },
        data(){
            return {
                sonmsg : {id : 1 ,name : '唐嫣'}
            }
        }
    }

    var vm = new Vue({
        el : '#app',
        data : {
            dataFromSon : ''
        },
        methods : {
            show : function(arg1,arg2){
                this.dataFromSon = arg1;  // 把从子组件传递的数据存放到data中
                console.log("调用了父组件的show方法~---------"+arg1.id+"----"+arg1.name+"--"+arg2);
            }
        },
        components : {
            com1 : com1 
        }
    })
</script>

image-20200528122347447

image-20200528122540119


14. 综合小案例

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css">
        <script type="text/javascript" src="https://unpkg.com/vue"></script>
        <style>
            li {
                border: 1px dashed #999;
                margin: 5px;
                line-height: 35px;
                padding-left: 5px;
                font-size: 12px;
                width: 100%;
            }
            li:hover {
                background-color: #999;
                transition: all 0.4s ease;
            }
            .v-enter,
            .v-leave-to {
                opacity: 0;
                transform: translateY(50px);
            }

            .v-enter-active,
            .v-leave-active {
                transition: all 1s ease;
            }

            .v-move {
                transition: all 1s ease;
            }

            .v-leave-active {
                position: absolute;
            }
        </style>
    </head>
    <body>

        <template id="temp">
            <div>
                <div class="form-group">
                    <label>评论人:</label>
                    <input type="text" class="form-control" v-model="user" v-focus>
                </div>

                <div class="form-group">
                    <label>内容:</label>
                    <textarea class="form-control" v-model="content"></textarea>
                </div>
                <div class="form-group">
                    <input type="button" value="发表评论" class="btn btn-primary" @click="add">
                </div>
            </div>
        </template>

        <div id="app">

            <comment-box @func="add"></comment-box>

            <transition-group appear tag="ul" class="list-group">
                <li class="list-group-item" v-for="(item,index) in list" :key="item.id"  @click="del(index)">
                    <span class="badge">评论人:{{item.user}}</span>
                    {{item.content}}
                </li>
            </transition-group>

        </div>

        <script>

            var commentBox = {
                template : '#temp',
                data(){
                    return {
                        user : '',
                        content : ''
                    }
                },
                methods : {
                    add : function(){
                        var comment = {id:Date.now(),user:this.user,content:this.content};
                        var list = JSON.parse(localStorage.getItem('cmts') || '[]');  // 读取本地缓存 如果读去不到 则返回一个空数组
                        list.unshift(comment);   // 向从本地缓存中读取到的数组添加
                        localStorage.setItem('cmts',JSON.stringify(list));  // 将添加后的新数组重新存储到本地缓存中
                        this.user=this.content="";
                        this.$emit('func');
                    }
                },
                props : []
            }

            Vue.directive('focus',{
                inserted : function(el) {
                    el.focus();
                }
            })

            var vm = new Vue({
                el : '#app',
                data : {
                    list : [
                        // {id:Date.now()+"1",user:'唐嫣',content:'天生我材必有用'},
                        // {id:Date.now()+"2",user:'陈乔恩',content:'千金散尽还复来'},
                        // {id:Date.now()+"3",user:'刘诗诗',content:'大漠孤烟直'},
                        // {id:Date.now()+"4",user:'关晓彤',content:'长河落日圆'},
                        // {id:Date.now()+"5",user:'刘亦菲',content:'劝君更进一杯酒,西出阳关无故人'}
                    ]
                },
                methods : {
                    add : function(){
                        var list = JSON.parse(localStorage.getItem('cmts') || '[]');
                        this.list = list;
                    },
                    del : function(index){
                        var list = JSON.parse(localStorage.getItem('cmts') || '[]');    // 获取到本地缓存中的数组
                        list.splice(index,1);       // 根据index 删除一个数据
                        localStorage.setItem('cmts',JSON.stringify(list));      // 把删除后的数据重新存储到本地缓存中
                        this.list=list;
                        // this.list.splice(index,1);
                    }
                },
                components : {
                    commentBox : commentBox
                },
                beforeCreate() {

                },
                created(){
                    this.add();
                }
            })
        </script>
    </body>
</html>

15. 使用ref获取DOM元素和组件引用

==子组件向父组件传值和父组件调用子组件方法,通过$ref==

<div id="app">
    <input type="button" value="获取元素" @click="getElement">
    <h3 ref="myh3">哈哈哈,今天天气太好了!</h3>
    <hr>
    <!--通过ref属性 指定ref的值,这样通过$ref中就可以获取到组件了-->
    <login ref="login"></login> 
</div>

<script>

    var login ={
        template : '<h1>登录</h1>',
        data(){
            return {
                msg : 'son msg'
            }
        },
        methods: {
            show() {
                console.log("调用了子组件的方法!")
            }
        }
    }

    var vm = new Vue({
        el : '#app',
        data : {

        },
        methods : {
            getElement: function(){
                // ref 是英文单词 reference
                console.log(this.$refs)
                this.$refs.login.show();  // 执行了子组件的show()方法
            }
        },
        components : {
            login : 'login'
        }
    })
</script>

16. 路由

什么是路由?

后端路由:对于普通网站,所有的超链接都是URL地址,所有的URL地址都对应服务器上对应的资源

前端路由:对于单页面应用程序员来说,主要通过URL中的hash(#号)来实现不同页面之间的切换,同时,hash有一个特点:HTTP请求中不包含hash相关的内容,所以,单页面程序中的页面跳转主要用hash实现

在单页面应用程序中,这种通过hash改变来切换页面的方式,称作前端路由(区别于后端路由)

16.1 vue-router

vue-router cdn:

<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

在引入vue-router之前,一定要先引入vue,因为vue-router依赖于vue

如果使用npm安装,在一个模块化工程中使用它,必须要通过Vue.use()明确地安装路由功能

如果使用全局的script标签,则无需手动安装

步骤:

  • 导入vue-router.js
  • 创建一个路由对象,当导入 vue-router包之后,在 window全局对象中,就有了一个路由的构造函数,叫做VueRouter,在 new 路由对象的时候,可以为构造函数,传递一个配置对象route,route 表示【路由匹配规则】的意思,每个路由规则都是一个对象,这个规则对象,有两个必须的属性:属性1:path,表示监听哪个路由链接地址;属性2:component,表示如果路由是前面匹配到的path,则暂时component属性对应的组件。component的属性值必须是一个组件的模板对象,不能是组件的引用名称(字符串)
  • 将路由规则对象,注册到vm实例上,用来监听URL地址变化,然后展示对应的组件
  • router-link用来实现路由跳转,是有vue-router提供,它有一个属性to,用来指定组件的路径,

router-link渲染出来后也是<a>标签,如果直接使用<a>标签,其href属性指定的路径前要加上#

  • router-view是vue-router提供的元素,专门用来当作占位符的,将来,路由规则匹配到的组件就会展示到这个router-view中
<div id="app">
    <a href="#/login">登录</a>
    <a href="#/register">注册</a>
    <router-link to="/login">登录</router-link>
    <router-link to="/register">注册</router-link>
    <!-- 这是vue-router提供的元素,专门用来当作占位符的,将来,路由规则匹配到的组件就会展示到这个router-view中 -->
    <router-view></router-view>
</div>

<script>
    var login = {
        template : '<h1>登录</h1>'
    }
    var register = {
        template : '<h1>注册</h1>'
    }

    // 创建一个路由对象,当导入 vue-router包之后,在 window全局对象中,就有了一个路由的构造函数,叫做VueRouter
    // 在 new 路由对象的时候,可以为构造函数,传递一个配置对象
    var routerObj = new VueRouter({
        //route // 这个配置对象中的route 表示【路由匹配规则】的意思
        routes : [  // 路由匹配规则
            mode: 'history',  //去掉url中的#
            // 每个路由规则都是一个对象,这个规则对象,有两个必须的属性:
            // 属性1:path,表示监听哪个路由链接地址
            // 属性2:component,表示如果路由是前面匹配到的path,则暂时component属性对应的组件
            // component的属性值必须是一个组件的模板对象,不能是组件的引用名称(字符串)
            {path:'/login',component:login},
            {path:'/register',component:register}
        ]
    })
    var vm = new Vue({
        el : '#app',
        components : {
            login : 'login',
            register : 'register'
        },
        router:routerObj  // 将路由规则对象,注册到vm实例上,用来监听URL地址变化,然后展示对应的组件
    })
</script>

16.2 路由redirect重定向

  • 可以使用这中方式来实现,跳转,在访问/根路径的时候,会自动跳转到login组件,但此时浏览器地址栏还是会显示/根路径,所以不推荐这种方式

    {path:'/',component:login}
  • 我们可以通过,redirect属性来实现重定向

    {path:'/',redirect:'/login'}

16.3 设置选中路由高亮的两种方式

方式一

<router-link>标签被选中时,会自动加上两个类,router-link-activerouter-link-exact-active

所以我们可以给router-link-active类添加样式,来实现选中时的高亮效果

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

这样,在被选中的<router-link>标签就会有高亮效果了

方式二

如果要修改链接激活时使用的css类名,默认值可以通过路由的构造选项linkActiveClass来全局配置,如

var routerObj = new VueRouter({
    routes : [
        {path:'/login',component:login},
        {path:'/register',component:register},
        {path:'/',redirect:'/login'},
    ],
    // 修改链接激活时的css类名,在链接选中时,就会自动为标签添加active类
    linkActiveClass : 'active'  
})
.active {
    font-size: 80px;
}

16.4 路由切换动画

直接给<router-view>标签包裹<transition>标签就可以了!

<style>
    .v-enter,
    .v-leave-to {
        opacity: 0;
        transform: translateX(150px);
    }

    .v-enter-active,
    .v-leave-active {
        transition: all 0.5s ease;
    }
</style>
<transition mode="out-in">
    <router-view></router-view>
</transition>

16.5 路由传参

使用query方式传递参数

如果在路由中,使用?问号拼接参数,给路由传递参数,则不需要修改路由规则的path属性

this.$route.query 可以获取到路由传递的参数,一般在组件的生命周期函数 created 函数中获取路由传递的参数

  • 传递参数
<router-link to="/login?id=10&name=zs">登录</router-link>
  • 获取参数
var login = {
    template : '<h1>登录---{{id}}----{{name}}</h1>',
    data(){
        return {
            id : '',
            name : ''
        }
    },
    created(){  // 组件的生命周期钩子函数
        this.id = this.$route.query.id ;  // this.$route.query  可以获取到路由传递的参数
        this.name = this.$route.query.name;
    }   
}

这种方式,有缺点,参数名,和参数值全部暴露在浏览器地址栏中了

使用params方式传递参数

很像restful风格

this.$route.params 可以获取到路由传递的参数,一般在组件的生命周期函数 created 函数中获取路由传递的参数

  • 修改路由规则 /:参数名
routes : [
      {path:'/login/:id/:name',component : login}
],
  • 在路由跳转时,传递参数
<router-link to="/login/20/zs">登录</router-link>
  • 获取参数
var login = {
    template : '<h1>登录----{{id}}---{{name}}</h1>',
    data(){
        return {
            id : '',
            name : ''
        }
    },
    created(){
        this.id = this.$route.params.id;  // 通过 this.$route.params 获取路由传递的参数
        this.name = this.$route.params.name; 
    }
}

16.6 使用children属性实现路由嵌套

==子组件 路由路径要写在父组件路径下==

==在children中 定义的子组件的路由规则 path属性 不要加 /==

<template id="temp">
    <div>
        <h1>这是account组件</h1>
        <!-- 子组件 路由路径要写在父组件路径下 -->
        <router-link to="/account/login">登录</router-link>
        <router-link to="/account/register">注册</router-link>
        <!--在子组件中写上 router-view -->
        <router-view></router-view>
    </div>
</template>


<div id="app">
    <router-link to="/account">Account</router-link>
    <router-view></router-view>
</div>

<script>
    var account = {
        template : '#temp'
    }
    var login = {
        template : '<h1>登录</h1>'

    }
    var register = {
        template : '<h1>注册</h1>'
    }

    var routerObj = new  VueRouter({
        routes : [
            {
                path : '/account',
                component : account,
                children : [
                    // 定义子组件路由 path 不用加 /
                    {path:'login',component:login},
                    {path:'register',component:register}
                ]
            }
        ]
    });

    var vm = new Vue({
        el : '#app',
        data : {

        },
        components : {
            account,
            login,
            register
        },
        router : routerObj
    })
</script>

16.7 使用命名视图实现经典布局

在一个页面需要多个组件时,就需要给<router-view>标签添加name属性,来指定对应的组件

在定义路由规则时,访问 / 根路径时,将多个组件共同显示出来,就要使用components来定义一个路径下多个组件

<div id="app">
    <router-view></router-view>
    <router-view name="left"></router-view>
    <router-view name="main"></router-view>
</div>

<script>
    var header = {
        template : '<h1>Header头部区域</h1>'
    }
    var leftBox = {
        template : '<h1>leftBox左侧侧边栏区域</h1>'
    }
    var mainBox = {
        template : '<h1>mainBox右侧主体区域</h1>'
    }
    var routerObj = new VueRouter({
        routes :[{
            path : '/',
            components :{
                'default' : header,
                'left': leftBox,
                'main' : mainBox
            } 
        }]
    });

    var vm = new Vue({
        el : '#app',
        components : {
            header,
            leftBox,
            mainBox
        },
        router : routerObj
    });
</script>

17. slot 插槽

<template id="temp">
    <div>
        <h1>组件一</h1>
        <slot name="s1"></slot>
        <slot name="s2"></slot>
    </div>
</template>
<div id="app">
    <com>
        <s1 slot="s1"></s1>
        <s2 slot="s2"></s2>
    </com>
</div>

<script>
    Vue.component('com',{
        template : '#temp'
    })
    Vue.component('s1',{
        template : '<h1>s1s1s1</h1>'
    })
    Vue.component('s2',{
        template : '<h1>s2s2s2</h1>'
    })
    var vm = new Vue({
        el : '#app'
    });
</script>

18. watch监听

18.1 监听data数据变化

使用这个属性 可以监视data中指定数据的变化,然后触发这个watch中对应的function处理函数

<div id="app">
    <input type="text" placeholder="firstname" v-model="firstname" >
    <input type="text" placeholder="lastname" v-model="lastname">
    <input type="text" placeholder="name" v-model="name">
</div>
<script>
    var vm = new Vue({
        el : '#app',
        data : {
            lastname : '',
            firstname: '',
            name : ''
        },
        watch : {
            // 使用这个属性 可以监视data中指定数据的变化,然后触发这个watch中对应的function处理函数
            firstname : function(){
                // 当firstname的值改变时,就会执行该函数
                this.name = this.firstname + this.lastname;
            },
            lastname : function(){
                // 当lastname的值改变时,就会执行该函数
                this.name = this.firstname + this.lastname;
            }
        }
    });
</script>

watch 属性中的方法中 有两个参数:

参数一:newVal

参数二:oldVal

watch : {
    // 使用这个属性 可以监视data中指定数据的变化,然后触发这个watch中对应的function处理函数
    firstname : function(newVal,oldVal){
        // 当firstname的值改变时,就会执行该函数
        this.name = newVal + this.lastname;
    },
        lastname : function(newVal,oldVal){
            // 当lastname的值改变时,就会执行该函数
            this.name = newVal + this.lastname;
        }
}

18.2 监听非DOM元素的改变

监听路由的改变

watch : {
    '$route.path' : function(newVal,oldVal){
        console.log(newVal);
        console.log(oldVal);
        if(newVal == '/login'){
            console.log("欢迎进入登录页面~~");
        }
    }
}

19. nrm的使用

作用:提供了一些最常用的NPM包镜像地址,能够让我们快速的切换安装包时候的服务器地址

什么是镜像:原来包刚一开始是只存在于国外的NPM服务器,但是由于网络原因,经常访问不到,这时候,我们可以在国内,创建一个和官网完全一样的NPM服务器,只不过,数据都是从人家那里拿过来的,除此之外,使用方式完全一样

  • 运行npm i nrm -g 全局安装nrm
  • 使用nrm ls 查看当前所有可用的镜像地址以及当前所使用的镜像的地址
  • 使用nrm use npmnrm use taobao 切换不同的镜像源地址

image-20200529133927457

换成国内的淘宝镜像,可以提高装包时的速度

注意:nrm只是单纯的提供了几个常用的下载包的URL地址,并能够让我们在这几个地址之间很方便的进行切换,但是我们每次装包的时候,使用的装包工具还是npm

npm i cnpm -g 安装 cnpm 装包工具


20. webpack

20.1 概念

在网页中会引用哪些常见的静态资源?

  • JS

    • .js .jsx .coffee .ts(TypeScript)
  • CSS

    • .css .less .sass(.scss)
  • Image

    • .jpg .png .gif .bmp
  • 字体文件(Fonts)

    • .svg .ttf .eot .woff .woff2
  • 模板文件

    • .ejs .jade .vue(这是在webpack中自定义组件的方式,推荐)

网页中引入的静态资源多了以后有什么问题?

  • 网页加载速度慢,我们要发起很多的二次请求
  • 要处理错综复杂的依赖关系

如何解决上诉问题?

  • 合并,压缩,精灵图,图片的Base64编码
  • 可以使用requestJS,也可以使用webpack

什么是webpack?

  • webpack 是前端的项目构建工具,它是基于Node.js开发出来的前端工具
  • 我们可以使用webpack这个前端自动化构建工具,可以完美实现资源的合并,打包,压缩,混淆等诸多功能

image-20200529135717898

webpack安装的两种方式

  • 运行npm i webpack -g是全局安装webpack,这样能在全局使用webpack的指令
  • 在项目根目录中运行npm i webpack --save-dev安装到项目依赖中

20.2 webpack最基本的使用方式

  • 手动创建目录结构,dist目录代表发表的产品级的目录,main.js 是我们项目的JS入口文件
  • 运行npm init初始化项目,会生成一个package.json的文件

image-20200529140802869

  • 在index.html中我们只引入main.js,不再引入其他js文件,在main.js中,引入其他所有的js文件

在index.html中引入main.js

  • 使用cnpm i jquery --save安装jquery类库

image-20200529141540312

​ 这样就会生成一个node_modules目录和package-lock.json文件,jquery就在node_modules

  • mian.js中引入jquery

import xx from xx为ES6中导入模块的方式

import $ from 'jquery'

这样我们就可以使用jquery来编写代码了

  • 由于import xx from xx属于ES6的语法,浏览器不能够解析,所以我们需要使用webpack

webpack 要打包的资源路径 输出路径,如

webpack .\src\main.js .\dist\bundle.js,将 main.js打包,输出到dist目录下,命名为bundle.js

这样,在index.html中,我们就不能够直接引入main.js,我们需要引入bundle.js

==注意:如果是webpack4 可能会出现错误!==

image-20200529143500317

解决错误:

  1. 以管理员身份运行vs code
  2. 执行:get-ExecutionPolicy,显示Restricted,表示状态是禁止的
  3. 执行:set-ExecutionPolicy RemoteSigned
  4. 这时再执行get-ExecutionPolicy,就显示RemoteSigned

如果提示 Cannot find module 'webpack-cli'

image-20200529144138049

我们需要先全局安装 webpack-cli,执行命令 npm i webpack-cli -g

这样在执行webpack .\src\main.js -o .\dist\bundle.js就成功了!

这样就在dist目录下,生成了bundle.js文件

  • webpack处理了JS文件的相互依赖关系
  • webpack能够处理js的兼容性问题,把高级的,浏览器不识别的语法,转为低级的浏览器能正常识别的语法

20.3 webpack最基本的配置文件的使用

我们想只执行webpack命令,就可以让其将main.js打包成bundle.js

我们需要在项目根目录下建一个配置文件webpack.config.js

在配置文件中进行配置

// 这个配置文件,其实就是一个js文件,通过Node中的模块操作,向外暴露了一个配置对象
const path = require('path')
module.exports = {
    // 指定入口,main.js
    entry : path.join(__dirname,'./src/main.js'),  // 入口,表示要使用webpack打包哪个文件
    output : {
        path : path.join(__dirname,'./dist'),  // 指定打包好的文件,输出到哪个目录中去
        filename : 'bundle.js'  // 指定输出的文件的名称
    }
}

这样,我们只需要输入webpack,就会自动帮我们打包main.js,输出到bundle.js

20.4 webpack-dev-server的基本使用

使用webpack-dev-server这个工具,来实现自动打包编译的功能

  • 在项目内执行命令 npm i webpack-dev-server -D 把这个工具安装到项目的本地开发依赖
  • 安装完毕后,这个工具的的用法和webpack的命令的用法完全一样
  • 由于,我们是在项目本地安装的webpack-dev-server,所以无法把它当作脚本命令在powershell终端中直接运行 (只有那些安装到全局 -g 的工具才能在终端中执行)
  • 所以,我们可以在package.json文件中进行配置

image-20200529152430008

  • 配置完成后,我们直接执行命令npm rund dev,就可以通过localhost:8080进行访问了
  • 但是,这是,我们在index.html引入的js文件不能再是../dist/bundle.js了,要改为

    <script src="/bundle.js"></script>

这样,我们再修改main.js中的代码时,就不用再去单独的执行打包命令了!!

webpack-dev-server帮我们打包生成的bandle.js文件,并没有存放到实际的物理磁盘上,直接托管到了内存中!

20.5 webpack-dev-server的常用参数命令

方式一(推荐)

package.json中的配置

  • "dev":webpack-dev-server --open,自动打开浏览器
  • "dev":webpack-dev-server --open --port 3000,自动打开浏览器,同时运行的端口为 3000
  • "dev":webpack-dev-server --open --port 3000 --contentBase src,自动打开浏览器,同时运行的端口为 3000,并且打开页面为src下的index.html
  • "dev":webpack-dev-server --open --port 3000 --contentBase src --hot,自动打开浏览器,同时运行的端口为 3000,并且打开页面为src下的index.html,不重新生成bundle.js文件,只生成局部更新的文件

方式二

不在package.json中进行配置,在webpack.config.js中进行配置

devServer : {
    // 这是配置dev-server命令参数的第二种形式
    open : true, // 自动打开浏览器
    port : 3000,  // 设置启动时的运行端口
    contentBase : 'src', // 指定托管的根目录
    hot : true // 启用热部署
}

20.6 html-webpack-plugin的基本使用

作用:在内存中,根据我们的index模板页面生成一个内存中的首页,我们就可以访问内存中的页面了

运行npm i html-webpack-plugin --save-dev安装到开发环境

修改配置文件webpack.config.js

// 导入在内存生成html页面的插件
// 只要是插件,都一定要放到plugins节点中去
const htmlWebpackPlugin = require('html-webpack-plugin'); 

在配置文件webpack.config.js,中的plugins节点中配置

plugins : [
    new htmlWebpackPlugin({
        // 创建一个在内存中生成html页面的插件
        template : path.join(__dirname,'./src/index.html'),  // 指定模板页面,将来会根据指定的页面路径,去生成内存中的页面
        filename : 'index.html' // 指定生成的页面的名称
    })

当使用了html-webpack-plugin之后,我们不需要手动处理bundle.js路径了,因为这个插件,已经帮我们自动创建了一个合适的script标签,并且,引用了正确的路径

20.7 loader配置处理css样式

  • 如果想要打包处理css文件,需要安装npm i style-loader css-loader -D
  • 打开webpack.config.js配置文件,在里面新增一个配置节点module,它是一个对象,在这个对象上,有一个rules属性,这个属性是个数组,这个数组中,存放了所有第三方文件 匹配和处理规则

    module : { // 这个节点,用于配置所有的第三方模块加载器
        rules : [
            // 所有第三发模块的匹配规则
            {test: /\.css$/,use:['style-loader','css-loader']}, //配置处理.css文件的第三方loader规则
        ]
    }
  • 在main.js中导入css文件

    import './css/index.css'

这样,我们的css文件就被打包一起导入了

loader配置处理less文件

  • 安装less npm i less-loader -Dnpm i less -D
  • 打开webpack.config.js配置文件,在里面进行配置

    module : { // 这个节点,用于配置所有的第三方模块加载器
        rules : [
            // 所有第三发模块的匹配规则
            {test: /\.css$/,use:['style-loader','css-loader']}, //配置处理.css文件的第三方loader规则
            {test: /\.less$/,use:['style-loader','css-loader','less-loader']}, //配置处理.css文件的第三方loader规则
        ]
    }
  • 在main.js中导入less文件

    import './css/index.less'

20.8 url-loader 的使用

默认情况下,webpacke无法处理css文件中的url地址,不管是图片还是字体库,只要是url地址都无法处理

安装url-loader和file-loader npm i url-loader file-loader -D

在webpack.config.js中进行配置

{test:/\.(jpg|png|gif|bmp|jpeg)$/,use:'url-loader?limit=1024&name=[hash:8]-[name].[ext]'}  // 处理图片路径的loader,通过?传递参数,当图片小于1024kb的时候,以base64编码的方式加载,大于1024kb的时候以url的方式加载;图片的名字等于打包之前图片的名字前加上一个八位的hash且后缀名也相同

20.9 webpack中babel的配置

作用:处理高级的ES6语法

在webpack中,默认只能处理一部分ES6的新语法,一些更高级的ES6语法或者ES7语法,webpack是无法处理的,这时候就要借助于第三方的loader,把高级的语法转为低级的语法后,会把结果交给webpack去打包到bundle.js

通过bable可以帮我们将高级的语法转换为低级的语法

在webpack中,可以运行如下两套命令,安装两套包,去安装babel相关的loader功能:

第一套包

npm i babel-core babel-loader babel-plugin-transform-runtime -D

第二套包

npm i babel-preset-env babel-preset-stage-0 -D

在webpack的配置文件中,在moudle节点下的rules数组中,添加一个新的匹配规则

{test:/\.js$/,use:'babel-loader',exclude:/node_modules/}  // 排除掉node——modules中的包

在项目根目录中,新建一个叫做.babelrc 的babel配置文件,这个配置文件属于JSON格式,所以在写.babelrc配置的时候,必须符合json语法规范:不能写注释,字符串必须用双引号

在.babelrc写如下配置:

{
    "presets":["env","stage-0"],
    "plugins":["transform-runtime"]
}

21. Vue实例的render方法渲染组件

使用components 的话,是将组件放入id为app的容器内,使用render是把id为app的容器替换为指定的组件

<div id="app">
    <login></login>
</div>
<script>
    var login = {
        template : '<h1>这是登录组件</h1>'
    }
    var vm = new Vue({
        el : '#app',
        render : function(createElements){ //createElements 是一个方法,调用它能把指定的组件模板渲染为html结构
            return createElements(login);  // 注意:这里的return的结果,会替换页面中el指定的那个容器
            // 使用components 的话,是将组件放入id为app的容器内,使用render是把id为app的容器替换为指定的组件
        }
    });
</script>

22.Vue 和 Webpack

22.1 webpack中导入vue

安装vue npm i vue -S

在main.js中导入vue

import Vue from 'vue'

注意:在webpack中,使用import Vue from 'vue' 导入的Vue构造函数,功能不完整,只提供了 runtime-only的方式,并没有提供像script标签导入vue那样的使用方式

解决办法:

方法一:在package.json配置文件中修改main的值"main":"dist/vue.js"

方法二:在导入时,指定vue的路径import Vue from '../node/node_modules/vue/dist/vue.js'

方法三:在webpack.config.js中,新增一个节点resolve,进行配置

resolve : {
    alias : {
        // 修改vue被导入时候的包的路径
        'vue$':'vue/dist/vue.js'
    }
}

22.2 vue中结合render函数渲染指定组件到容器中

在login.vue中

<template>
    <div>
        <h1>这是登录组件,使用.vue文件定义出来的</h1>
    </div>
</template>

<script>

</script>

<style>

</style>

在main.js中

import Vue from 'vue'
import login from './login.vue'

var vm = new Vue({
    el : '#app',
    data : {
        msg : '123'
    },
    render :function(createElements) {
        // 在webpack中,如果想要通过vue,把一个组件放入页面中去展示,vm实例中的render函数可以实现
        return createElements(login);
    }
})

默认情况下webpack无法打包.vue文件,需要安装相关的loader

npm i vue-loader vue-template-compiler -D

在配置文件webpack.config.js中,新增loader配置对象

{test : '/\.vue$/',use : 'vue-loader'} // 处理 .vue文件的loader

22.3 export default 和 export 的使用方式

使用exprot default 和 export 向外暴露成员

在.vue中

<script>
    export default {
        data () {
            return {
                msg : '123'
            }
        },
        methods : {
            show(){
                console.log("调用了login.vue的show方法")
            }
        }
    }
</script>

注意:export default 向外暴露的成员,可以使用任意的变量来接收

在一个模块中,export default 只允许向外暴露一次

在一个模块中,可以同时使用和export default 和 export 向外暴露成员

export var name : 'zs'
export var age : '23'

在接收时,

import {name,age} from 'xxx'

注意:使用export向外暴露的成员,只能使用{ }的形式来接收,这种形式,叫做【按需导出】

注意:使用export导出的成员,必须严格按照导出时候的名称,来使用 { } 按需接收

可以使用 as 来取别名

import {name as name123} from 'xxx'

22.4 在webpack中使用vue-router

导包 npm install vue-router

如果在一个模块化工程中使用它,必须要通过Vue.use() 明确地安装路由功能

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

Vue.use(VueRouter)

如果使用全局的script标签,则无需手动安装

22.5 组件中style标签

scoped:使style标签内的样式,只作用于该组件

<style scoped></style>

普通的style标签只支持普通的样式,如果要启用scss或less,需要为style标签元素,添加lang属性

<style lang="less"></style>

这样,在组件中的style中,就可以写less了

22.6 webpack中使用axios

导入 axios npm i axios -S

import Vue from 'vue';  //导入vue模块

import axios from 'axios'; //导入axios
axios.defaults.baseURL = 'http://www.xxxxx.cn';  //配置根域名
Vue.prototype.$axios = axios;  //把axios挂载到Vue的原型上,这样就可以通过 $axios.get()发起请求了

23. Vuex

23.1 vuex概述

组件之间共享数据的方式

父向子传值:v-bind 属性绑定

子向父传值:v-on 事件绑定 或 this.$refs

兄弟组件之间数据共享:EventBus

Vuex是什么?

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

image-20200530131619831

使用Vuex统一管理状态的好处:

  • 能够在vuex中集中管理共享的数据,易于开发和后期维护
  • 能够高效地实现组件之间的数据共享,提高开发效率
  • 存储在vuex中的数据都是响应式的,能够实时保持数据与页面的同步

什么样的数据适合存储到vuex中?

一般情况下,只有组件之间共享的数据,才有必要存储到vuex中,对于组件中私有的数据,依旧存储在组件自身的data中即可

23.2 vuex的基本使用

安装vuex的依赖包 npm install vuex --save

导入vuex包

import Vuex from 'vuex'

Vue.use(Vuex)

创建store对象

const store = new Vuex.Store({
    // state 中存放的就是全局共享的数据
    state : {count : 0}
})

将stroe对象挂载到vue实例中

import store from '...'

new Vue({
    el : '#app',
    data : {},
    router,
    // 将创建的共享数据对象,挂载到Vue实例中
       // 所有的组件,就可以直接从store中获取全局的数据了
    store
})

安装vue-cli

npm install @vue/cli -g

使用vue ui来创建一个项目

image-20200530134102994

image-20200530134449203

23.3 State

State 提供唯一的公共数据源,所有共享的数据都要统一放到Store的State中进行存储

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

Vue.use(Vuex)

export default new Vuex.Store({
    state : {
        // State 提供唯一的公共数据源,所有共享的数据都要统一放到Store的State中进行存储
        count : 0
    },
    mutations :{},
    actions : {}
})

组件中访问State中数据的第一种方式

this.$store.state.全局数据名称

 <h1>当前最新的count值为:{{$store.state.count}}</h1>

第二种方式:

从vuex中按需导入mapState函数

通过刚才导入的mapState函数,将当前组件需要的全局数据,映射为当前组件的computed计算属性

import {mapState} from 'vuex'
export default {
    data(){
        return {

        }
    },
    computed : {
        ...mapState(['count'])  // ...为展开运算符,这样就拿到了全局数据 count
    }
}
</script>

23.4 Mutation

Mutation用于变更Store中的数据

  • 只能通过mutation变更Store数据,不可以直接操作Store中的数据
  • 通过这种方式虽然操作起来稍微繁琐,但是可以集中监控所有数据的变化

组件通过调用Mutation中的函数来实现对全局数据的改变

export default new Vuex.Store({
    state : {
        // State 提供唯一的公共数据源,所有共享的数据都要统一放到Store的State中进行存储
        count : 0
    },
    mutations :{
        // 用mutation来操作数据,组件电泳mutations中的方法来操作数据
        add(state){
            state.count++
        },
        del(state){
            state.count--
        }
    },
    actions : {}
})

调用Mutation中的方法

this.$store.commit('函数名字');

export default {
    data(){
        return {

        }
    },
    methods : {
        add(){
            this.$store.commit('add');  // 调用mutation中的函数
        }
    }
}

Mutation传递参数

在Mutation中定义了方法

mutations :{
    // 用mutation来操作数据,组件电泳mutations中的方法来操作数据
    addN(state,step){   // 第二个是要传递的参数
        state.count=state.count+step
    }
},

组件中调用Mutation中方法时,传递参数

addn(){
    this.$store.commit('addN',3);   // 传递了一个参数 3
}

在组件中调用Mutation中函数的第二种方式

从vuex中按需导入mapMutations函数

通过刚才导入的mapMutations函数,将需要的mutations函数,映射为当前组件的methods方法

methods:{
    ...mapMutations(['del']),
        deleteNum(){
        this.del();
    }
}

==注意:在Mutations的函数中不能写异步的代码!!==

23.5 Action

Action用于处理异步任务

如果通过异步操作变更数据,必须通过Action,而不能使用Mutation,但是在Action中还是要通过触发Mutation的方式间接变更数据

actions : {
    addAsync(context){
        setTimeout(function(){  // 延时1秒执行add函数
            context.commit('add')
        },1000)
    }
}

触发Action

methods : {
    add(){
        this.$store.dispatch('addAsync')
    }
}

触发Action异步任务时传递函数

actions : {
    addAsync(context,arg){
        setTimeout(function(){  // 延时1秒执行add函数
            context.commit('add',arg)
        },1000)
    }
}

触发Action

methods : {
    add(){
        this.$store.dispatch('addAsync',5)
    }
}

触发Action函数的第二种方式

从vuex中按需导入mapActions 函数

import {mapActions} from 'vuex'

methods : {
    ...mapActions(['addAsync'])
}

23.6 Getter

Getter 用于对Store中的数据进行加工处理形成新的数据

  • Getter可以对Store中已有的数据加工处理之后形成新的数据,类似Vue的计算属性
  • Store中的数据发生变化,Getter的数据也会跟着变化

定义Getter

new Vue.Store({
    state: {
        count = 0  
    },
    getters : {
        showNum : state => {
            return '当前最新的数量是【'+state.count+'】'
        }
    }
})

使用getter的第一种方式:

this.$store.getters.名称

this.$store.getters.showNum

使用getter的第二种方式:

import {mapGetters} from 'vuex'

computed : {
    ...mapGetters(['showNum'])
}
本文作者: Author:     文章标题: Vue让开发更加简单!
本文地址: https://codewei.cn/archives/160/      
版权说明:若无注明,本文皆为“阿伟的小屋”原创,转载请保留文章出处
Last modification:June 14th, 2020 at 04:05 pm
贫困山区儿童,谢谢打赏