近况

最近把vue网课快看完了,还差最后的实训项目部分,总之所有vue所有知识点都学完了,
想着先不着急跟着敲项目,把之前学习的内容都整理整理,写几篇vue博客再去学写项目,
本篇就是关于vue学习的第一篇,vue的基础指令,看看都有哪些内容吧。

基础介绍

vue是一个响应式的JS框架,要说什么是响应式,我还是先说说一个叫mvvm的概念,
mvvm指的是Model,View,ViewModel,前端把项目分成这三块,Model指的是数据,前端从后端获取的数据
View指的是Dom元素,就是用户看到的网页,Html标签,ViewModel你可以理解为一个助手,或者说是vue
它的作用是将Model数据及时刷新到Dom元素中,让页面展示数据,或者当用户点击了按钮,对页面发生了改变
ViewModel可以及时的将数据保存起来,总之,ViewModel在View和Model中起到中间桥梁的作用。

vue就是实现了mvvm架构的框架,vue将数据传递到页面,让页面及时刷新数据,vue也可以根据页面的更改,
动态更新数据,vue就是做了ViewModel的事情,而响应式说的就是,当数据发生改变时,页面能立即发生变化。

还有一个双向绑定的概念,其实上面已经说了,当页面发生改变,或者数据发生改变,ViewModel都能动态
更新双方,这就是双向绑定,你可能会好奇,既然这就是双向绑定,那有没有单向绑定的概念呢?不急接着看下去

说完mvvm,响应式的概念,你可能还是不太理解具体内容,下面举个栗子。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <script src="vue.js"></script>	//页面中直接导入vue依赖
  <title>vue</title>
</head>
<body>
  <div id="app">
      <span>{{message}}</span>	//vue将data中数据渲染到页面中,{{}}操作就是从data中获取数据
  </div>
  <script>
      let app = new Vue({
          el: '#app',		//el绑定id为app的div标签	
          data: {			
              message:'加油!'
          },
      });
  </script>
</body>
</html>

上面示例中,引入vue的依赖,打开浏览器就能看到message的信息,View指的就是body中标签元素,整个页面
Model指的是data中的数据,当然不止data,只要是数据都属于Model,ViewModel指的是Vue实例,它内部做
的就是将data中数据及时响应到Dom中,每当数据发生变化,都会及时更新Dom,这就是响应式。

插值操作

插值操作说的是,vue通过什么指令将数据响应到Dom元素中,看本文目录还会讲绑定属性,绑定属性和插值操作
不同的是:插值操作指将数据插入到DOM元素之中,可以展示给用户看,而绑定属性则是将数据插入到DOM元素
的属性中。下面关于插值操作的几个指令:

  • Mustache语法

    mustache语法指的是标签中的{{}},在Dom元素标签内使用双括号,就能取到vue实例的data中的数据。
    例如:<span>{{message}}</span>,当然你还可以写简单的表达式:{{message+"万一"}},这里就当是在JS中写语句。
  • v-once指令
    表示该DOM只会更改一次,之后并不会随着data数据改变而改变DOM中的数据。
    如果没有该指令,则只要data中数据改变后,对应的DOM会响应该数据,从而DOM中数据发生改变。
    使用方式:<span v-once>{{message}}</span>,该数据只会渲染一次。

  • v-html指令
    解析数据中的标签属性,如果data对象中的变量值本身就是一个标签,则使用该指令可以对其进行解析
    使用如下:

    <span v-html="message"></span>	//dom页面,将在span标签中生成a标签,点击跳转页面
    data: {		//vue实例中data对象
        message:'<a href="http://www.keyi.world">个人网站</a>'
    }
  • v-text指令
    与Mustache语法类似,将数据插入到DOM中,相对而言,没有Mustache语法好
    使用如下:

    <span v-text="message"></span>
    data: {	
        message:'你好啊'
    }
    //该指令和mustache作用一样,不过双括号的方式更加简单易懂。
  • v-pre指令
    表示该DOM不进行DOM数据绑定,将该DOM中的数据原封不动的展示出来
    使用:<span v-pre>{{message}}</span>,将会在页面显示{{message}},说白了,不对双括号进行解析。

  • v-cloak指令
    解决插值闪烁问题,插值闪烁指的是当js代码还未被加载时,页面显示,当vue.js加载后,
    才会显示实际数据,就会存在这种画面:{{message}},过了一段时间变成你好啊,这就是插值闪烁。
    而当DOM标签使用v-cloak标签后,配合css的使用,即可解决插值闪烁。案例如下:

    <style>
        [v-cloak] {
            display: none;
        }
    </style>
    <div id="app">
        <span v-cloak>{{message}}</span>  //vue.js还没被加载时,存在该属性,加载后,vue删除该属性
    </div>
    <script>
    setTimeout(function () {	//模拟延迟加载vue.js
        let app = new Vue({
            el: '#app',			//绑定id为app的div标签,该div内归vue管理
            data: {
                message:'你好啊'
            },
        });
    },1000)
    </script>
    /*
    当js代码未被加载时,DOM标签因为存在v-cloak属性,被css限制展示,隐藏起来了
    当js代码加载后,vue会删除v-cloak属性,使得DOM标签再次被展示,同时数据也被渲染出来。
    不过听说这个指令已经不用了,都用的是虚拟DOM,继续学习吧.
    */

绑定属性

插值操作是把数据绑定到文本中,用于展示数据,而绑定属性是将数据绑定到DOM标签的属性中
绑定属性就一个重要指令:v-bind

  • v-bind的基本使用

    <img v-bind:src="imgUrl" alt="">
    data: {
        imgUrl:'http://图片地址'
    }
    //v-bind还可以使用简写方式,即去掉v-bind,只保留:属性方式,就是从data中获取数据,是个语法糖
    <img :src="imgUrl" alt="">
  • v-bind绑定class属性
    v-bind绑定属性有对象语法和数组语法两种

    • 对象语法
      使用对象语法则class属性值是一个对象,对象的属性表示class名称,对象的属性值是一个boolean值
      当boolean值为true时,则BOM标签的class值就会应用对象的对应属性名,否则不应用,下面是简单的案例:

      <style>
          .a{
          color: red;
          }
          .b{
              color: aqua;
          }
      </style>
      <h2 :class="{a:isA,b:isB}">{{message}}</h2>
      data: {
        message : '你好,李银河',
        isA: true,
        isB: false
      }
      //isB为false,则h2标签的class属性只有a样式生效,<h2 class="a">{{message}}</h2>

      你还可以添加一个固定的class值,这个值经过vue解析后,会和动态的class值进行合并,
      编译后,class属性值为title,a,b(如果a,b都为true的话),例如下面这样:
      <h2 class="title" :class="{a:isA,b:isB}">{{message}}</h2>

      另外,值得注意的是,如果对象语法方式要添加的字段太长,你也可以写成methods的形式或者computed(计算属性)的形式,
      下面同样是一个案例,不过把对象抽离到methods中了

      //vue解析时,调用该getClasses方法,会返回一个对象,和上面案例一样的。
      <h2 :class="getClasses()">{{message}}</h2>	
      let app = new Vue({
          el: '#app',
          data: {
              message : '你好,李银河',
              isA: true,
              isB: false
          },
          methods:{
              getClasses:function () {
                  /*
                  	methos中获取data中的数据,需要this
                  	this指的是vue实例,因为是vue调用这个方法
                  */
                  return {a:this.isA,b:this.isB};	
              }
          }
      });
    • 数组语法
      除了在class属性值使用对象,你还可以使用数组,例如:<h2 :class="['a','b']">{{message}}</h2>
      这种方式class的a,b是固定的死值,字符串,如果去掉单引号,则表示一个变量,引用data中的实际数据了。
      同样的,数组语法的方式也可以使用methods或者computed的形式。
      值得注意的是:千万不要忘记v-bind最初的使用方式,仅仅使用固定值和变量即可,v-bind初始方法适用于所有属性绑定。

  • v-bind绑定style属性
    绑定style当然也可以使用对象语法或数组语法

    • 对象语法
      值得注意的是,我们在写key的时候,可以写font-size或者FontSize,另外对象的值可以是固定值'50px',也可以是data中的变量如果是固定值,就是字符串,一定要用单引号包含,变量才不用单引号包含,这个规则同样适用于绑定class。
      最后,就像绑定class属性一样,你也可以写成函数的形式去调用,自定义函数写在methods中。
      案例:<h2 :style="{fontSize:'50px'}">{{message}}</h2>,这种固定值一般很少写,可能用引用变量方式多点。

    • 数组语法
      与绑定class属性一致,值得说明的是,class属性绑定的是单个值,而style属性要绑定一个对象,
      因为style样式必须有key和value又是怎么实现的呢?下面是示例代码:

      <div id="app">
          <span :style="[baseStyle]">{{message}}</span>    //数组中可以有更多的元素,该元素是一个引用data中的对象
      </div>
      <script>
              let app = new Vue({
                  el: '#app',
                  data: {
                      message:'加油!',
                      baseStyle:{
                          fontSize:'100px',
                          color: 'red'
                      }
                  },
              });
      </script>
  • 2021年9月24日更新

    • 绑定class样式的三种方式

      1)字符串写法,适用于:样式的类名不确定,需要动态指定
      2)数组写法,适用于:要绑定的样式个数不确定,名字也不确定
      3)对象写法,适用于:要绑定的样式个数确定,名字也确定,但要动态决定用不用
      下面是一个案例对应上面的3种方式:
      <style>
      .a{
        width: 100px;
        height: 100px;
        background-color: #0e90d2;
      }
      .b{
        width: 100px;
        height: 100px;
        background-color: #3ca1ff;
      }
      .c{
        width: 100px;
        height: 100px;
        background-color: #9acfea;
      }
      </style>
      
      <body>
        <div id="app">
          <div :class="bgColor" @click="changeColor1">字符串方式</div>
          <div :class="colorArr" @click="changeColor2">数组方式</div>
          <div :class="colorObj" @click="changeColor3">对象方式</div>
      	</div>
      
      <script>
        let app = new Vue({
          el: '#app',
          data: {
            bgColor: 'a',
            colorArr: ['a','b','c'],
            colorObj: {
              a:false,
              b:false,
              c:false
            }
          },
          methods: {
            //字符串方式
            changeColor1(){
              let colors =['a','b','c'];
              //随机取数组中样式
              this.bgColor=colors[Math.floor(Math.random()*3)]
            },
            //数组方式
            changeColor2(){
              this.colorArr.pop();
            },
            //对象方式
            changeColor3(){
              this.colorObj.a=true
              this.colorObj.b=true
              this.colorObj.c=true
            }
          }
        });
      </script>
      </body>
    • 绑定style的两种方式

      对象方式,数组方式,下面是一个案例:
      <div id="app">
        <span :style="styleObj">{{message}}</span>
        <span :style="styleArr">{{message}}</span>
      </div>
          
      <script>
        let app = new Vue({
          el: '#app',
          data: {
            message:'加油!',
            //对象语法
            styleObj: {
              color: 'red',
              fontSize: '30px'
            },
            //数组语法
            styleArr: [
              {
                color: 'blue',
                fontSize: '50px'
              },{
                backgroundColor: 'orange',
              }
            ]
          }
        });
      </script>

计算属性

计算属性指的是在插值的过程中,为了方便,将值重新设置成一个新值,插入到DOM结点中。
如果你从后端拿到数据,需要先处理一下数据,才能在DOM页面中展示出来,那么你可能就需要使用计算属性。

  • 计算属性的基本使用

    <div id="app">
        <span>{{firstName}} {{lastName}}</span><br>
        <span>{{firstName+" "+lastName}}</span><br>
        <span>{{getFullName()}}</span><br>
        <span>{{fullName}}</span><br>   //计算属性是不需要使用fullName()方式调用
    </div>
    <script>
        let app = new Vue({
            el: '#app',
            data: {
                firstName:'万',
                lastName:'一'
            },
            computed:{		
                //fullName是计算后的属性,所以虽然是函数类型,但命名时变量名应该写成属性方式
                fullName:function () {
                    return this.firstName+" "+this.lastName;
                }
            },
            methods:{
                getFullName(){
                    return this.firstName+" "+this.lastName;
                }
            }
        });
    </script>
    
    /*
    	小提示:对象中的方法书写在es6中可以简化
    	computed:{		
           fullName:function () {
               return this.firstName+" "+this.lastName;
           }  
        }
        可以简化成下面这种,和上面是等效的,是es6方法的增强写法
        computed:{		
           fullName(){
               return this.firstName+" "+this.lastName;
           }  
        }
    */
  • 计算属性setter和getter
    上个案例中,你可能会很好奇,为什么fullName明明是个函数的形式定义,却在使用的时候当做属性的方式使用
    其实计算属性本身是个对象,这个对象有set方法和get方法,而我们定义的方法是简写的get方法,
    set方法被我们省略掉了,一般很少使用完整版的计算属性是下面这种形式:

    <div id="app">
        <span>{{fullName}}</span><br>   <!--计算属性是不需要使用fullName()方式调用-->
    </div>
    <script>
        let app = new Vue({
        	el: '#app',
            data: {
                firstName:'万',
                lastName:'一'
            },
            computed:{
                fullName:{
                    set:function (newValue) {
                        let names = newValue.split(" ");
                        this.firstName=names[0];
                        this.lastName=names[1];
                    },
                    get:function () {
                        return this.firstName+" "+this.lastName;
                    }
                }
            }
        }); 
    </script>

    因为我们大多时候并不使用set方式,所以使用省略的方式写计算属性,以下是省略版本,省略了set方法,并简写get方法

    fullName:function () {
        return this.firstName+" "+this.lastName;
    }
  • 计算属性computed与方法methods有什么区别?
    原因:计算属性会进行缓存,如果多次使用时,计算属性只会调用一次,而函数多次执行,就会多次调用函数
    另外,计算属性在vue内部中确实是一个属性,而我们编写的函数其实是重写了这个属性的get方法。

监视属性

  • 监视属性的基本使用

    <script>
      let app = new Vue({
        el: '#app',
        data: {
          message:'加油!'
        },
        //监视属性第一种写法
        watch: {
          message: {
            //初始化时让handler调用一下,此时oldvalue是undefined
            immediate: true,
            //newValue:message改变后的值,oldValue:改变之前的值
            handler(newValue,oldValue){
              console.log(newValue,oldValue);
            }
          }
        }
      });
    
    //监视属性第二种写法
    app.$watch('message',{
      //immediate: true,
      handler(newValue, oldValue) {
        console.log(newValue,oldValue);
      }
    })
    </script>
  • 深度监视

    <div id="app">
        <span>{{message}}</span>
        <button @click="number.a++">增加a</button>
        <button @click="number.b++">增加b</button>
    </div>
    
    <script>
        let app = new Vue({
            el: '#app',
            data: {
                message:'加油!',
                number: {
                    a: 1,
                    b: 2
                }
            },
            watch:{
                number:{
                    /*
                	开启深度监视配置项,如果没有,则监视整个number对象改变,有了deep则监视number中所有属性的变化
                	监视整个number对象改变指的是,整个对象换了才监听的到,而其中某个属性发生改变,检测不到
                */
                    deep: true, 
                    handler(newValue,oldValue){
                        console.log("number中任一属性发生了变化")
                    }
                }
            }
        });
    </script>
  • 监视的简写形式

    当监视属性时不需要其他配置项,比如deep,immediate等等,则你可以使用简写方式,简写有两种方式:
    <script>
      let app = new Vue({
        el: '#app',
        data: {
          message:'加油!',
    
        },
        watch:{
          //简写的第一种方式
          message(newValue,oldValue){
            console.log("message改变了");
          }
        }
      });
    
    //简写的第二种方式,直接使用handler函数,不需要像之前一样给个对象
    app.$watch('message',function(newValue,oldValue) {
      console.log("message改变了");
    })
    </script>
    
    注意:事件函数,计算属性方法,监视的方法,都不能写成箭头函数,这些函数都属于vue管理,
    如果写成箭头函数则其中的this是window对象,而不是vue实例对象,因为箭头函数的this取自vue的上下文环境
    而vue是window的属性,所以箭头函数中的this是Window对象
  • watch和computed的区别

    1)computed能完成的功能,watch都可以完成
    2)watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
    
    两个重要的小原则:
    1)所被vue管理的函数,最好写成普通函数,这样this指向才是vm(vue实例),或组件实例对象
    2)所有不被vue所管理的函数(定时器的回调函数,ajax的回调函数,Promise的回调函数,这些都是浏览器异步组件调用,不归vue管理)
    最好写成箭头函数,这样this的指向才是vm或组件实例对象。

过滤器的使用

定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑处理)
语法:

  • 注册过滤器:Vue.filter(name,callback)或new Vue({filters:{}})
  • 使用过滤器:{{xxx | 过滤器名}} 或 v-bind属性="xxx | 过滤器名"
  • 备注:
    • 过滤器也可以接收额外参数,多个过滤器也可以串联
    • 过滤器并没有改变原本的数据,而是产生新的对应的数据

下面是过滤器的案例:

/*
	本次案例演示了,全局过滤器(可多个组件使用),局部过滤器,过滤器传递参数,过滤器串联使用
*/
<div id="app">
  <h3>当前时间戳:{{nowTime}}</h3>
  <h3>当前时间,格式1{{nowTime | change()}}</h3>
  <h3>当前时间,格式2{{nowTime | change("YYYY/MM/DD hh:mm:ss")}}</h3>
  <h3>当前时间,格式3{{nowTime | change("YYYY/MM/DD hh:mm:ss") | change2}}</h3>
  <h3>当前时间,格式4{{nowTime | change("YYYY/MM/DD hh:mm:ss") | change2 | change3}}</h3>
</div>

<script>
//全局过滤器
Vue.filter('change3',function(t3) {
  return t3.substring(0,4);
})

let app = new Vue({
  el: '#app',
  data: {
    nowTime: Date.now()
  },
  //局部过滤器
  filters: {
    /*
       value指的是管道符传递过来的参数,str是过滤器调用时传递的参数,如果没传,则使用默认值格式化时间戳
     */
    change(value,str="YYYY-MM-DD HH:mm:ss"){
      return dayjs(value).format(str);
    },
    //多个过滤器串联使用
    change2(t2){
      return t2.substring(0,10)
    }
  }
});
</script>
/*
	本次案例中使用到了第三方库:dayjs,Day.js是一个轻量的处理时间和日期的JavaScript库
	安装:npm install dayjs --save
*/
//值得注意的是:Vue3中过滤器已经被遗弃了,推荐用计算属性或者方法实现。

事件监听

事件监听使用的是v-on指令,为BOM元素绑定事件函数,可以使用语法糖写成@

  • v-on参数
    事件监听函数的参数问题值得我们注意,下面是几种参数情况:

    • 如果事件函数不需要额外的参数,则DOM中@click=”函数名”不需要添加()

      <div id="app">
          <button @click="ceshi">{{num}}</button>	//点击按钮,调用ceshi函数,不需要()
      </div>
      <script>
          let app = new Vue({
              el: '#app',
              data: {
                  num:0
              },
              methods:{
                  ceshi(){
                      this.num++;	  //在data对象外访问data中的变量,都需要使用this,Dom中除外
                  }
              }
          });
      </script>
    • 当然我们也可以给事件函数一个参数,当点击时,传递一个参数到事件函数中

      <button @click="ceshi(50)">{{num}}</button>
      methods:{
         ceshi(param){
            this.num=param;
         }
      }
    • 值得注意的是,如果你的事件函数有参数接收,但是DOM元素中写成@click="ceshi",并没有指定参数,甚至没有括号。
      vue还是会传递一个对象到事件函数参数中,事件函数会接收一个event对象,这个对象是浏览器生成的,
      如果想传入event事件对象进事件函数,则Dom元素中要么只写事件函数名(就是上面所说的),要么使用$event传递参数

    • 最后,如果需要我们的事件函数同时需要某个参数和event对象,那么传递参数的时候应该使用$event传递参数
      如果参数直接使用event传递,则vue会以为该event参数是变量,就去data对象中找对应的属性,最后报错找不到。

      <button @click="ceshi('万①',$event)">{{num}}</button> 
      methods:{
          ceshi(param1,event){
              console.log(param1+" "+event);
          }
      }
  • v-on的修饰符
    修饰符指的是在v-on指令的基础上加上一些命令,使得可以改变事件的触发规则,例如下面这些:

    @click.stop,解决事件冒泡的问题
    @click.prevent,移除默认的事件,例如表单提交,点击submit按钮时会自动提交,
        现在我手动为该按钮绑定事件函数,等我检查完毕,我再提交,不需要这个按钮默认的事件提交了,
        使用v-on.prevent="事件函数名",表示移除该DOM元素默认的事件,转而执行我绑定的事件函数。
    @click.enter,监听键盘的确认键,也可以@click.键盘上键帽对应的编码/别名,来监听某个键
    @click.native,监听组件根元素的原生事件
    @click.once,只触发一次回调,只能点击一次才有用,
    值得说明的是,并不是只有点击事件才有修饰符,所有事件可以使用修饰符,另外,修饰符可以多个连用,@click.stop.prevent="xxx"    

条件判断和循环遍历

  • 条件判断
    条件判断主要使用:v-if,v-else-if,v-else,v-show,v-if=”true”,显示DOM元素,反之不显示,案例如下:

    <div id="app">
        <h3 v-if="score>=90">优秀</h3>
        <h3 v-else-if="score>=70">挺好</h3>
        <h3 v-else-if="score>=60">平庸</h3>
        <h3 v-else="score<60">不行哦</h3>
    </div>
    data: {
        score:95
    }
    //不过这种计算方式不太好,可以使用计算属性代替。

    注意:在使用v-if等条件判断指令时,可能会出现控件复用问题,因为vue底层处于性能考虑,会尽量复用之前存在的控件
    下面是一个关于控件复用的案例:

    <body>
        <div id="app">
            <span v-if="byUsername">
                <label for="username">用户账号</label>
                <input type="text" id="username" placeholder="用户账号">
            </span>
            <span v-else>
                <label for="email">用户邮箱</label>
                <input type="text" id="email" placeholder="用户邮箱">
            </span>
            <button @click="byUsername =! byUsername">切换登陆方式</button>
        </div>
    </body>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                byUsername: true
            }
        })
    </script>
    /*
    	本案例中,当用户在id为username的input控件中输入用户账户,输入到一半,点击按钮切换到邮箱登录时
    	因为vue控件复用问题,用户会看到id为email的input控件上仍然存在之前的账号值,为了解决这个问题
    	就需要在这些控件上加上key属性,并填写不同的key属性值。
    */

    最后还有一个与v-if类似的指令:v-show,它与v-if的区别是,如果判断为false,v-if中根本不会有DOM元素,
    而v-show则只是将DOM元素的display属性设置为none而已,浏览器页面按f12,还是可以看到DOM元素的。

  • 循环遍历
    v-for指令用于遍历数组和对象,遍历数组很简单,不仅可以拿到数组的元素,还可以拿到数组的下标/索引

    <li v-for="(item,index) in books">{{index}} {{item}}</li>
    data: {
        books:[
            '大主宰',
            '斗破苍穹',
            '莽荒纪',
            '星辰变',
            '完美世界'
        ]
    }

    如果遍历的是对象,则也可以获取到对象的key和value,如下所示:

    <li v-for="(value,key) in book">{{key}} {{value}}</li>
    data: {
        book: {
            bookName:'大主宰',price:20
        }
    }

    注意:括号内中第一个参数永远是数组元素或者对象value,第二个参数永远是数组下标或者对象的key,跟参数名没关系
    当然了,如果遍历的是对象,还可以有第三个参数,是对象的下标,第一个属性的下标是0,以此类推,不过对象下标很少使用。

    最后,vue官方建议,在使用v-for时,推荐在DOM元素中绑定一个key元素,绑定key元素可以提高DOM渲染效率,涉及到虚拟DOM
    并且这个key属性值必须是唯一的,并且不能是数组下标或对象下标,最好是数组元素值或者对象value。
    下面来解释一下原因:

    就拿上面遍历数组的例子来看,为什么需要绑定key属性,如果没有绑定key底层是怎么操作的?
    首先如果我们没有绑定key,则输出如下
    	0 大主宰
        1 斗破苍穹
        2 莽荒纪
        3 星辰变
        4 完美世界
    如果此时我们想插入一个DOM元素在"斗破苍穹""莽荒纪"之间,我们在控制台输入app.books.splice(2,0,'武动乾坤'),结果正常显示出来
    	0 大主宰
        1 斗破苍穹
        2 武动乾坤
        3 莽荒纪
        4 星辰变
        5 完美世界
    但是底层是如何操作的呢?
    实际上是将"莽荒纪"DOM元素的值改成"武动乾坤",然后将"星辰变"改成"莽荒纪",依次改下去,最后生成新的DOM元素,它的值为"完美世界"。
    和java数组中间新增一个元素,其后所有元素往后推一位一样,这样做的效率非常的低,
    我们想要的是,直接新增一个DOM元素插入到"斗破苍穹""莽荒纪"之间不需要改变其他DOM元素的位置,
    就和java中链表一样,这个时候就需要为这些DOM元素绑定一个唯一的key,这个key不能是数组的索引。
    因为索引是会变的,如果你的属性key是索引,那插入新增DOM元素后,其后DOM元素的索引值就会发生改变,
    触发vue的diff算法后,也会更新后面的DOM元素。所以属性key值要为数组元素值(元素要唯一),
    使得每一个key于自身DOM元素绑定,才能实现和链表一样的效果。key的作用主要是为了高效更新虚拟BOM

数组响应问题

我们知道,通过修改data中的数据就可以使得界面中元素数据实时发生改变,这被称为响应式,但并不是所以方法都能够使得DOM元素及时更新。也就是说并不是一定修改data数据,DOM元素就能立即响应更改,有些方法是响应式的,有些方法并不是响应式的
数组中响应式的方法:push(),pop(),shift(),unshift(),splice(),sort(),reverse()
如果通过下标直接修改方法,则这种方式并不是响应式的,也就是说DOM元素并不立即发生改变,虽然实际上数据已经被改变了。
例如下面这个案例:

<li v-for="(value,key) in books">{{key}} {{value}}</li>
<li><button @click="update">点击修改</button></li>
methods:{
   update(){
      this.books[0]='武动乾坤';
      console.log(this.books);
   }
}

上面这种方式修改数组元素,数据确实被修改了,但是DOM元素并没有实时更新,如果想实时更新,
可以使用上面的响应式的数组方法去更新数组的元素。
另外vue也提供了方法去实时更新数据:vue.set(要修改的对象,索引值,修改后的值),或者this.$set(要修改的对象,索引值,修改后的值)

Vue.set(this.books,0,'武动乾坤');
this.$set(this.books,0,'武动乾坤');
/*
	其实学到后面才发现,这两个是一样的,如果你需要某个方法,模块在每个组件内都能访问到,
	你只需要,Vue.prototype.$名称=方法或者某个模块,例如Vue.prototype.$set=Vue.set
	因为组件都是Vue构造函数的实例,所以自然继承了Vue原型对象中的属性和方法,
	所以我们在其他组件可以直接使用this.$set方法,组件内容会在下一篇博客中讲解,这里暂做了解。
*/

阶段案例

实现购物车案例

<div id="app">
    <div v-if="books.length">
        <table>
            <thead>
                <th>书籍名称</th>
                <th>出版日期</th>
                <th>价格</th>
                <th>购买数量</th>
                <th>操作</th>
            </thead>
            <tbody>
                <tr v-for="(book,index) in books">
                    <td>{{book.name}}</td>
                    <td>{{book.time}}</td>
                    <td>{{book.price | showPrice}}</td>
                    <td>
                        <button @click="sub(index)" :disabled="book.count<=1">-</button>
                        {{book.count}}
                        <button @click="add(index)">+</button>
                    </td>
                    <td><button @click="remove(index)">移除</button></td>
                </tr>
            </tbody>
        </table>
        <span>总价{{allPrice}}</span>
    </div>
    <div v-else>购物车为空</div>
</div>
 <script>
     let app = new Vue({
         el: '#app',
         data: {
             books:[
                 {name:'大主宰',price:20.00,count:1,time: '1998-8'},
                 {name:'斗破苍穹',price:30.00,count:1,time: '1998-8'},
                 {name:'莽荒纪',price:50.00,count:1,time: '1998-8'},
                 {name:'星辰变',price:20.00,count:1,time: '1998-8'},
                 {name:'完美世界',price:30.00,count:1,time: '1998-8'},
             ],
         },
         computed:{
             allPrice(){
                 //页面解析可能是先解析计算属性,然后再渲染数据
                 let sum=0;
                 this.books.map(function(value) {
                     sum+=(value.price*value.count);
                 });
                 return sum;
             }
         },
         filters:{       //设置过滤器
             showPrice(price){
                 return '$'+price.toFixed(2);    //保留两位小数
             }
         },
         methods:{
             remove(index){
                 //方式1
                 /*let newBooks = this.books.filter(function(book,index2) {
                        return !(index===index2);
                    });
                    this.books=newBooks;
                 */
                 //方式2
                 this.books.splice(index,1);
             },
             add(index){
                 this.books[index].count++;
             },
             sub(index){
                 this.books[index].count--;
             }
         },
     });
</script>

双向绑定

用于绑定控件,被绑定的控件与data中的数据实现双向绑定,准确的说,被绑定控件的value值和data中的数据
实现双向绑定,当我们修改控件的值时,data中的数据也会被修改,这是v-bind实现不了的,v-bind绑定value值
只是将data中的数据取出来显示,当我们修改控件的value值时,data中数据并不会被更改,而v-model却可以。

  • v-model的基本使用

    <input type="text" v-model="message">
    data: {
        message:'加油!',
    }
    //当我们手动修改文本框中的数据时,data中数据也会被更改,而v-bind绑定控件的value可做不到这一点,这就是双向绑定
  • v-model原理
    实际上v-model可以通过v-bind绑定value属性加上事件监听可以实现,当每次修改控件文本框时,
    会触发input事件(input控件有input事件),然后执行我们的事件函数,最后将输入的值写到data数据中。
    下面就是模拟v-model

    <input type="text" :value="message" @input="changeValue">
    data: {
        message:'加油!',
    },
    methods:{
        changeValue(event){
            this.message=event.target.value;
        }
    }
    //或者input事件并不执行事件函数,直接使用表达式即可:
    <input type="text" :value="message" @input="message=$event.target.value">
  • v-model在radio中的使用,单选框

    <div id="app">
        <label>
        	<input type="radio" name="sex" id="male" value="男" v-model="sex"></label>
        <laber>
        	<input type="radio" name="sex" id="female" value="女" v-model="sex"></laber>
    </div>
    data: {
        sex:'男'    //默认男
    }
    /*
    	小提示,当使用v-model在单选框时,name的属性可以去掉,name的本意是将两个选项绑定在一起,
                而v-model的值相同,已经表示为这两个控件被绑定在一起。
    */
  • v-model在checkbox中的使用
    checkbox分为单选框和多选框,单选框表示要么选中,要么不选择,多选框则是同时选中多个

    • 单选框

      <label for="agree">
          <input type="checkbox" id="agree" v-model="isAgree">同意协议
      </label>
      data: {
          isAgree: false	//当用户点击单选框,则isAgree的值为true
      }
    • 多选框

      <input type="checkbox" value="篮球" v-model="hobbies">篮球
      <input type="checkbox" value="足球" v-model="hobbies">足球
      <input type="checkbox" value="兵乓球" v-model="hobbies">兵乓球
      <input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球
      data: {
          hobbies: []	//v-model有多个值时,使用数组进行接收
      }
  • 上面例子使用checkbox出现的问题
    上面v-model使用在checkbox的问题是,我们的input写死在页面中,实际上应该动态的

    <div id="app">
        <label v-for="ball in originHobbies" >
            <input type="checkbox" :value="ball" v-model="hobbies">{{ball}}
    	</label>
    	{{hobbies}}
    </div>
    data: {
        hobbies:[],
        originHobbies:['篮球','足球','羽毛球','兵乓球']
    }
  • v-model在select中的使用
    select也有单选和多选之分

    • 单选

      <select v-model="fruit">
          <option value="芒果">芒果</option>
          <option value="火龙果">火龙果</option>
          <option value="草莓">草莓</option>
          <option value="葡萄">葡萄</option>
      </select>
      data: {
           fruit:'草莓'
      }
    • 多选
      只需要在select上加上multiple属性即可,<select v-model="fruit" multiple>

  • v-model的修饰符
    之前讲过事件有它的修饰符,而v-model也有自己的修饰符,修饰符主要是用来帮助我们处理一些数据的,类似语法糖的概念

    • lazy修饰符
      默认情况下,v-model默认是在input事件中同步输入框的数据的,也就是说,一旦有数据发生改变,
      对应的data数据就会自动发生改变,lazy修饰符可以让数据在失去焦点或者回车时才会更新
      <input type="text" v-model.lazy="message">
    • number修饰符
      默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串进行处理,但是如果我们希望处理的是数字类型,
      那么最好将内容直接当做数字进行处理,number修饰符可以让在输入框输入的内容自动转成数字类型
      <input type="number" v-model.number="message">
      该文本框只能输入数字,并且绑定在data数据中时,message类型也为数字,
      如果没有number修饰符,则message会是字符串类型
    • trim修饰符
      如果输入的内容首尾有很多空格通常我们希望将其去除,trim修饰符可以过滤内容左右两边的空格
      <input type="text" v-model.trim="message">,绑定到data数据时,会去除用户输入的两边的空格

混入mixin

混入mixin是用于复用组件的配置,可以把多个组件共用的配置提取成一个混入对象,下面是一个简单的案例:

//app.vue
<template>
  <div id="app">
    <test1/>
    <hr>
    <test2/>
  </div>
</template>
<script>
import Test1 from './components/Test1';
import Test2 from './components/Test2';
export default {
  name: 'App',
  components: {
    Test1,Test2
  }
}
</script>

//CommonConfig,多个组件共同的配置抽离成一个js文件
export default {
  data(){
    return {
      msg: '测试混入mixin'
    }
  }
}

//Test1.vue
<template>
  <div>
    Test1组件:{{msg}}
  </div>
</template>
<script>
import comConfig from './CommonConfig'
export default {
  name: "Test1",
  mixins: [comConfig]
}
</script>

//Test2.vue
<template>
  <div>
    Test2组件:{{msg}}
  </div>
</template>
<script>
import comConfig from './CommonConfig'
export default {
  name: "Test2",
  mixins: [comConfig]
}
</script>

/*
	如果在复用的文件中,你的data中数据或者方法与复用js文件中的配置冲突
	以你的为主,但是如果是钩子函数冲突了,则复用的钩子函数和你的钩子函数都起作用,并且复用的钩子函数先执行
	上面的混合方式是局部混合,即每个组件中都需要导入复用文件,并配置mixin属性使用
	而全局混合指的是,在main.js中导入复用js文件,然后Vue.mixin(导入的复用对象),则整个应用所有组件都可以使用复用配置
	如果是全局的混合,则不需要配置mixin属性,所有组件都有了复用文件的配置
*

自定义指令

vue中的指令指的是DOM元素标签中标注的v-xxx,只要在标签中标注vue的指令,就能够完成一些事情,本质上
是vue做了一些DOM操作,例如:v-text指令,就是vue拿到data数据,将数据交给标签的innerText属性,
那么自定义指令就是我们自己去做一些DOM操作,自定义指令有两种实现方式,函数式和对象式,下面来看看函数式:

//实现一个指令,只要标注在标签上,就能够显示一些我们想要的内容
<div id="app">
  <span v-wanyi></span>
</div>

<script>
    let app = new Vue({
      el: '#app',
      data: {
      },
      directives: {
        /*
        	指令何时会被调用?
        		1,指令与元素成功绑定时(一上来,第一次时)
          	2,指令所在的模板被重新解析时(data数据发生改变时)  
        */
        wanyi(element,banding){
          element.innerText='万一的秃头时光';
        }
      },
    });
</script>

以上是函数式实现一个指令,下面是对象式实现自定义指令:

//使用对象式定义自定义指令v-waner,使用在input标签上,并可以获取到value,并且一上来就可以获取input的焦点focus
<div id="app">
  <span v-wanyi="n"></span>
  <hr>
  <input v-waner="n"/>
  <hr>
  <button @click="n++">点击n+1</button>
</div>

<script>
  let app = new Vue({
    el: '#app',
    data: {
      n: 1
    },
    directives: {
      wanyi(element,banding){      //函数式实现自定义指令时,这个函数相当于bind+update
        element.innerText=banding.value;
      },
      waner: {
        //指令与元素成功绑定时(一上来,第一次时)
        bind(element,banding){
          element.value=banding.value
        },
        //指令所在元素被插入页面时,mounted的时间差不多
        inserted(element,banding){
          element.focus()    //focus方法必须在DOM被挂载之后才能执行,所以只能用对象方式实现这个功能
        },
        //指令所在的模板被重新解析时(data数据发生改变)
        update(element,banding){      //update逻辑与bind逻辑一样
          element.value=banding.value
        }
      }
    },
  });
</script>
/*
	函数式和对象式实现自定义指令的区别在于:对象式实现自定义指令多了一个钩子函数,可以在页面加载完毕时做一些操作
*/

自定义指令上的一些坑:

  • 因为html模板不支持大小写,所以你的指令名不能有大写,使用-表示驼峰,并且在自定义指令函数名上使用字符串包裹函数名
    比如在上面的例子上,将wanyi,如果写成wanYi,肯定会报错的,你可以写成下面这种形式:

    //使用:
    <span v-wan-yi="n"></span>
    //定义自定义函数,因为有-,所以用''包裹,因为对象的key都是字符串
    'wan-yi'(element,banding){
      element.innerText=banding.value;
    }
  • 指令中函数,里面的this都是window,不是vue,所以想访问vue中的data数据,只能通过banding参数

全局指令:

Vue.directive('指令名',function(){}) //函数式
Vue.directive('指令名',{}) //对象式
全局指令的创建和全局过滤器的创建类似,在多个vue实例中可以使用

下面是关于自定义指令的总结:

插件

之前都是直接导入插件依赖,然后Vue.use(插件对象),就可以直接使用了,原来当我们调用Vue.use()时,
vue会调用插件中的install方法并且install方法可以接收两个参数,第一个参数是Vue,第二个参数及以后,
就是插件使用者传递的数据,交给插件作者使用在插件的install方法,我们可以定义一些全局的东西放到Vue中,
就是一直说的全局注册,注册到Vue中,然后,我们使用插件就可以使用一些功能了

过度与动画

Vue封装的过度和动画,作用是:在插入,更新或移除DOM元素时,在合适的时候给元素添加样式类名

  • 原生css实现动画

    <template>
      <div class="home">
        <button  @click="isShow=!isShow">点击显示或隐藏</button>
    		<h1 v-show="isShow" class="come" >万一</h1>
      </div>
    </template>
    <script>
    	export default {
      name: 'Home',
        data(){
        return {
          isShow: true
        }
      }
    }
    </script>
    
    <style scoped>
    h1{
      background-color: #1da1f2;
    }
    .come{
      animation: wanyi 1s;
    }
    .go{
      animation: wanyi 1s reverse;
    }
    @keyframes wanyi {
      from{
        transform: translateX(-100%);
                              }
      to{
        transform: translateX(0px);
      }
    }
    </style>
    //通过点击事件指定对应的回调函数,在回调函数中改变class属性,从而达到动画的效果
  • 使用vue中对动画的支持,使得我们无需再去手动更改class属性,vue会在合适的时候加上动画
    要求是class样式名不能随意写,不能写成上面come或者go

    <template>
      <div class="home">
        <button  @click="isShow=!isShow">点击显示或隐藏</button>
        <transition name="wan" appear>  <!--appear表示刷新页面立马执行enter样式-->
          <h1 v-show="isShow" class="come" >万一</h1>
        </transition>
      </div>
    </template>
    
    <script>
    	export default {
      name: 'Home',
        data(){
        return {
          isShow: true
        }
      }
    }
    </script>
    
    <style scoped>
    h1{
      background-color: #1da1f2;
    }
    /*默认样式名*/
    /*
    .v-enter-active{
    	animation: wanyi 1s;
    }
    .v-leave-active{
      animation: wanyi 1s reverse;
    }
    */
    
    /*如果给transition指定了name,则样式名改成:name-enter/leace-active*/
    .wan-enter-active{
      animation: wanyi 1s;
    }
    .wan-leave-active{
      animation: wanyi 1s reverse;
    }
    /*动画*/
    @keyframes wanyi {
      from{
        transform: translateX(-100%);
                              }
      to{
        transform: translateX(0px);
      }
    }
    </style>
  • 使用过度效果实现上面的案例

    <style scoped>
        h1{
          background-color: #1da1f2;
        }
    
        /*
          .wan-enter,表示进入的起点,
          .wan-enter-to,表示进入的终点
          .wan-leave,表示离开的起点
          .wan-leave-to,表示离开的终点
          .wan-enter-active,表示进入的过程中
          .wan-leave-active,表示离开的过程中
        */
    
        /*进入的起点,离开的终点*/
        .wan-enter,.wan-leave-to{
          transform: translateX(-100%);
        }
    
        /*进入的终点,离开的起点*/
        .wan-leave,.wan-enter-to{
          transform: translateX(0);
         }
    
        /*进入的过程中,离开的过程中,在其中增加效果*/
        .wan-enter-active ,.wan-leave-active{
          transition: 0.5s linear;
        }
    </style>
    /*
    	提示:如果有多个标签都需要使用相同的动画,则使用<transform-group>标签包裹,
    			并且给其中每个元素加上key值,值为不同的数字即可
    */
  • 第三方库,animate.css,千万不要下载错了,4版本的!下面是简单的使用:

    <template>
      <div class="home">
          <button  @click="isShow=!isShow">点击显示或隐藏</button>
        <transition
          name="animate__animated animate__bounce"
          appear
          enter-active-class="animate__backInDown"
          leave-active-class="animate__backOutDown">
            <h1 v-show="isShow">万一</h1>
        </transition>
      </div>
    </template>
    
    <script>
    	import 'animate.css'
      export default {
        name: 'Home',
        data(){
          return {
            isShow: true
          }
        }
      }
    </script>
    
    <style scoped>
      h1{
        background-color: #1da1f2;
      }
    </style>