手记

Vue2.0超详细且生动具体的基础知识整理

前言

根据慕课网实战课程——Vue2.0实战带你开发去哪儿APP整理而来,里面的内容和源码都是和该课程息息相关,建议结合视频来食用效果更佳

第一个Vue栗子

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>第一个示例</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      {{message}}
    </div>
    <script>
      let vm = new Vue({
        el: "#app",
        data: {
          message: "hello world"
        }
      })
      setTimeout(function() {
        vm.$data.message = "bye world";
      }, 2000)
    </script>
  </body>
</html>

【解析】
数据驱动是Vue的一大亮点,从以上代码就能够得知Vue不需要去操作DOM,只需要操作数据即可。它基本的执行流程是这样的:首先创建一个实例,接着使用el规定Vue实例负责管理或有权限操作的区域,然后在data里面存储所需要的数据。

在定时器那块可以得知,通过 实例.$data.属性名 能够获取相应的数据并对它进行处理。

一个TodoList栗子

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>TodoList</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <input type="text" placeholder="输入一些内容" v-model="message" />
      <button v-on:click="handleBtnClick">提交</button>
      <ul>
        <li v-for="item of list">{{item}}</li>
      </ul>
    </div>

    <script>
      let vm = new Vue({
        el: "#app",
        data: {
          message: "",
          list: []
        },
        methods: {
          handleBtnClick() {
            this.list.push(this.message);
            this.message = "";
          }
        }
      })
    </script>
  </body>
</html>

【解析】

  • 这个栗子引出如下知识点:v-model、v-on、v-for和methods,其中v-model是实现双向数据绑定的一个指令,意思是:视图层(V)的数据变动能够与数据模型层(M)的数据关联在一起,从而实现了改变V层数据则M层数据也跟着改变,反之,M层数据的变动也会让V层的数据变动,有点同生共死的味道。

  • v-on则是定义事件的指令,比如v-on:click代表点击事件,v-on:mouseover鼠标滑过事件等,要想知道更多事件和其他v-on的知识,请点击这里传送门

  • v-for则是遍历数据的指令,一般语法为:alias in expression或者alias of expression,这两种遍历有什么区别以及更多的v-for知识,请点击这里传送门,十分的浅显易懂,需要明白是为什么遍历的时候需要加上:key

  • methods看字面意思就知道它是存储方法的一个集合。

  • 上面的栗子执行过程很容易理解:首先将数据模型层的数据绑定在视图层中,接着改变视图层的数据,触发一个点击事件,然后在点击事件的方法中将从视图层返回到数据模型层的数据push进list数组之中从而遍历到视图层上。

MVVM模式

最开始的热门开发模式为MVP模式,其中M代表数据模型层(Model)、V代表视图层(View)、P代表控制器层(Presenter),下面请看一段基于MVP的代码(M层代码近乎没有)

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>TodoList</title>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
  </head>
  <body>
    <div id="app">
      <input type="text" id="input" />
      <button id="btn">提交</button>
      <ul id="ul"></ul>
    </div>

    <script>
      function Page() {

      }
      $.extend(Page.prototype, {
        init: function() {
          this.bindEvents()
        },
        bindEvents: function() {
          var btn = $("#btn");
          btn.on("click", $.proxy(this.handleBtnClick, this))
        },
        handleBtnClick: function() {
          var inputElem = $("#input");
          var inputValue = inputElem.val();
          var ulElem = $("#ul");
          ulElem.append('<li>' + inputValue + '</li>');
          inputElem.val("");
        }
      })
      var page = new Page();
      page.init();
    </script>
  </body>
</html>

假如用以上模式来开发程序,那么我们大部分都在操作DOM。

MVVM开发模式,其中M也是代表了数据模型层,V代表了视图层,不同的是将P层替换成了VM 层,而这个VM层是Vue框架自带的一个视图模型层,无需我们来进行打理。这样我们开发的时候只要关注视图层和数据模型层即可,非常轻松愉快。更详细的资料请看这传送门

前端组件化

组件,这个词十分容易理解。只要把web应用程序,也就是网站当做成一栋摩天大楼,那么组件就是那一小块的砖头。利用组件化的思想来开发一个网站,对于后期的维护是十分有利的,下面请看使用组件化思想修改的TodoList代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>TodoList</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <input type="text" v-model="message" />
      <button v-on:click="handleBtnClick">提交</button>
      <ul>
        <todo-item v-bind:content="item" v-for="item of list"></todo-item>
      </ul>
    </div>

    <script>
      // 定义一个子组件(全局子组件)
      Vue.component("TodoItem", {
        props: ['content'],
        template: "<li>{{content}}</li>"
      })      
      // 父组件区域
      let vm = new Vue({
        el: "#app",
        data: {
          message: "",
          list: []
        },
        methods: {
          handleBtnClick() {
            this.list.push(this.message);
            this.message = "";
          }
        }
      })
    </script>
  </body>
</html>

上面是是定义了一个全局子组件,下面我们再来定义一个局部子组件:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>TodoList</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <input type="text" v-model="message" />
      <button v-on:click="handleBtnClick">提交</button>
      <ul>
        <todo-item v-bind:content="item" v-for="item of list"></todo-item>
      </ul>
    </div>

    <script>
      // 定义一个子组件(局部子组件)
      var TodoItem = {
        props: ['content'],
        template: "<li>{{content}}</li>"
      }
      // 父组件区域
      let vm = new Vue({
        el: "#app",
        data: {
          message: "",
          list: []
        },
        methods: {
          handleBtnClick() {
            this.list.push(this.message);
            this.message = "";
          }
        },
        components: {
          "todo-item": TodoItem
        }
      })
    </script>
  </body>
</html>

和全局子组件的不同就是,局部子组件需要在父组件的实例中使用components进行注册方能使用。在这两段代码中,还涉及到了父组件向子组件传值这个知识点,其实也是挺好理解的:在视图层调用子组件之中,使用v-bind指令设置一个属性,并传入相关的值;然后子组件里使用props获得传入过来的属性,并将该属性渲染出来即完成了父子组件的传值。

子组件向父组件传值

子组件向父组件传值涉及到了一个专业名词,叫做发布订阅模式。这个名词接下来再解释,先看代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="root">
      <input v-model="inputValue" />
      <button @click="handleSubmit()">提交</button>
      <ul>
        <!--调用子组件-->
        <todo-item v-for="(item,index) of list" :key="index" :content="item" :index="index" @delete="handleDelete(index)">
        </todo-item>
      </ul>
    </div>

    <script>
      //子组件(全局)
      Vue.component('todo-item', {
        props: ['content', 'index'],
        template: '<li @click="handleClick()">{{content}}</li>',
        methods: {
          handleClick() {
            //发布订阅模式
            this.$emit('delete', this.index);
          }
        }
      })
      //父组件
      new Vue({
        el: "#root",
        data: {
          inputValue: '',
          list: []
        },
        methods: {
          handleSubmit() {
            this.list.push(this.inputValue);
            this.inputValue = '';
          },
          handleDelete(index) {
            this.list.splice(index, 1);
          }
        }
      })
    </script>
  </body>
</html>

【解析】
当子组件某个事件被触发时,那么子组件内部就会发布一个自定义事件和相对应的参数;此时父组件监听(也称为订阅)子组件自定义的事件,当该自定义事件被触发的时候,则在父组件里调用一个方法,实现删除的功能。

备注: 如果不在子组件添加对应的参数index,那么结果会是如何呢?如果在模板中这么定义:
@delete="handleDelete()"会发生什么情况呢?为什么?该如何修改才能达到这么定义:
@delete="handleDelete"的效果?

Vue实例生命周期函数

刚开始接触生命周期函数的人可能对这个概念十分迷糊,但是仔细观察和分析却会发现很容易理解。用大白话来说生命周期函数就是:在浏览器解析Vue实例的过程中自动执行的函数。比如你在methods中定义了一个方法,那么这个方法需要在视图层被相应的事件触发的时候才会执行,但是你将该方法放到生命周期函数中,则在Vue实例的某一个阶段该方法会被自动执行。

为了更好的理解生命周期函数在各个阶段发生的事情,请结合官方网站和我下面的大白话进行食用。

  • 第一阶段: beforCreate(),在初始化事件和生命周期(Init Events & LifeCycle)结束后的时候执行;
  • 第二阶段: create(),在初始化Vue内部的关系映射和双向数据绑定(Init Injections & Reactivity)结束后的时候执行;
  • 第三阶段: beforeMount(),在Vue实例准备负责接管视图层某个区域的时候执行(具体流程请看官网)。
  • 第四阶段: mounted(),在Vue实例已经负责接管了视图层某个区域(Create vm.$el and replace “el” with it)结束后的时候执行;

下面一段代码充分说明了beforeMount()和mounted()的区别:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title></title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">{{message}}</div>

    <script>
      let vm = new Vue({
        el: "#app",
        data: {
          message: "hello vue.js"
        },
        beforeMount() {
          console.log(this.$el);
          console.log("beforeMount");
        },
        mounted() {
          console.log(this.$el);
          console.log("mount");
        }
      })
    </script>
  </body>
</html>

剩下的几个生命周期函数大家上官网看看就好啦,在这不一一说明,能看懂那些英语单词,自然也就能理解。其中触发beforeDestroy()和destroyed()函数很简单,只要在控制台输入vm.$destroy()即可。对于beforeUpdate()和updated()只要在控制台更新了某些数据即可被触发。

Vue模板语法

  • 第一种:插值表达式,使用{{}}将数据渲染出来,无法解析html元素;
  • 第二种:v-text,值为JavaScript表达式,无法解析html元素;
  • 第三种:v-html,值为JavaScript表达式,能够解析html元素;
  • 第N种。。。。。不说先,太多了,先看上面三种模板语法区别的代码实现:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title></title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      {{message}}
      <div v-text="message"></div>
      <div v-html="message"></div>
    </div>

    <script>
      let vm = new Vue({
        el: "#app",
        data: {
          message: "<h1>hello Vue!</h1>",
        }
      })
    </script>
  </body>
</html>



计算属性、方法和侦听器

  • 计算属性:计算属性,首先得了解下“计算”这个词,“计算”这个词出现在vue中的含义我觉得它的首要任务是完成相关数据的计算。然后和methods最大的区别也是亮点所在,就是它具有缓存的这个特点,当依赖的数据没有发生变化的话,那么数据就不会被重新渲染,很大程度上提升了性能。下面请看代码实现:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>计算属性、方法和侦听器</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      {{fullName}}
      {{age}}
    </div>

    <script>
      let vm = new Vue({
        el: "#app",
        data: {
          firstName: "张",
          lastName: "三丰",
          age: 22
        },
        //计算属性
        computed: {
        	fullName(){
        		console.log("计算了一次");
        		return this.firstName + " " + this.lastName
        	}
        }
      })
    </script>
  </body>
</html>

为了测试computed的缓存特点,在data里新增了一个age数据进行对比,当在控制台更新age值的时候,fullName()并没有执行;当更新firstName和lastName值的时候,fullName()被执行,即证明了计算属性具有:当依赖的数据没有发生变化的话,那么数据就不会被重新渲染的缓存特点。

下面再看一个计算属性的栗子:

<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="Content-Type" Content="text/html; charset=utf-8" />
    <title>javascript</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
      .delete {
        text-decoration: line-through;
        color: #ddd;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <input type="text" v-model="inputValue" />
      <button @click="addContent">添加内容</button>
      <button @click="cleanContent">删除完成内容</button>
      <ul>
        <li v-for="(item,index) of list" @click="toggle(index)" :class="{delete:item.state}">
          {{index}}:{{item.mes}}
        </li>
      </ul>
      <p>完成条数:{{Complete}}/{{this.list.length}}</p>
    </div>

    <script>
      new Vue({
        el: '#app',
        data: {
          inputValue: '',
          list: [
            { mes: '王者农药', state: false },
            { mes: '荒野行动', state: false },
            { mes: '地下城', state: false }
          ]
        },
        computed: {
          Complete() {
            return this.list.filter(function(v) {
              return v.state
            }).length
          }
        },
        methods: {
          addContent() {
            this.list.push({
              mes: this.inputValue,
              state: false
            })
            this.inputValue = ''
          },
          toggle(index) {
            this.list[index].state = !this.list[index].state
          },
          cleanContent() {
            this.list = this.list.filter(function(v) {
              return !v.state
            })
          }
        }
      })
    </script>
  </body>
</html>
  • 方法:当页面的任何数据发生改变时,methods里面定义的函数都会被重新执行一次,因此导致了性能上的消耗,下面请看代码:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>计算属性、方法和侦听器</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      {{fullName()}} {{age}}
    </div>

    <script>
      let vm = new Vue({
        el: "#app",
        data: {
          firstName: "张",
          lastName: "三丰",
          age: 22
        },
        // 方法
        methods: {
          fullName() {
            console.log("计算了一次");
            return this.firstName + " " + this.lastName;
          }
        }
      })
    </script>
  </body>
</html>
  • 侦听器:特点和计算属性(computed)一样,有所不同的是侦听器需要分别侦听会变动的数据,在某些方面来说实在是有些不够简便,请看下面的代码:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>计算属性、方法和侦听器</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      {{fullName}} {{age}}
    </div>

    <script>
      let vm = new Vue({
        el: "#app",
        data: {
          firstName: "张",
          lastName: "三丰",
          fullName: "张三丰",
          age: 22
        },
        // 侦听器
        watch: {
          firstName() {
            console.log("计算了一次");
            this.fullName = this.firstName + this.lastName;
          },
          lastName() {
            console.log("计算了一次");
            this.fullName = this.firstName + this.lastName;
          }
        }
      })
    </script>
  </body>
</html>

可以在控制台输入命令vm.$data.firstName = 'jack'看看最后结果如何,还有就是firstName()和lastName()的命名方式可是固定的,不是随意的。

计算属性(computed)的setter和getter

所有的计算属性都以函数的形式写在vue实例内的computed选项内,最终返回计算的结果 。每一个计算属性都包含一个getter和一个setter,我们的上个实例都是计算属性的默认用法,只是利用了getter来获取,在你需要时,也可以提供一个setter函数,当手动修改计算属性的值就像修改一个普通数据那样时,就会触发setter函数,执行一些自定义操作 ,请看如下代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>计算属性getter和setter</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      {{fullName}}
    </div>

    <script>
      var vm = new Vue({
        el: '#app',
        data: {
          firstName: '张',
          lastName: '三丰'
        },
        computed: {
          fullName: {
            get() {
              return this.firstName + ' ' + this.lastName;
            },
            set(newValue) {
              var names = newValue.split(' ');
              this.firstName = names[0];
              this.lastName = names[names.length - 1];
            }
          }
        }
      })
    </script>
  </body>
</html>

绝大多数情况下,我们只会用默认的getter方法来读取一个计算属性,在业务开发中很少用到setter,所以在声明一个计算属性时,可以直接使用默认的写法,不必将getter和setter声明。

Vue中的样式绑定

下面是几种常见的Vue样式绑定方法,看源码进行测试就可以了:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue样式绑定</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
      .activated {
        color: green;
      }      
      .red {
        color: red;
      }      
      .fontSize {
        font-size: 20px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <!--class的对象绑定-->
      <div @click="handleDivClick" :class="{activated:isActivated}">
        hello vue.js
      </div>
      <!--class的数组绑定-->
      <div @click="handleDivClickOne" :class="[activatedOne,activatedTwo]">
        hello vue.js
      </div>
      <!--class的style绑定1-->
      <div :style="styleObj" @click="handleDivClickTwo">
        hello vue.js
      </div>
      <!--class的style绑定2-->
      <div :style="[styleObjOne,{fontSize:'30px'}]">
        hello vue.js
      </div>
    </div>

    <script>
      var vm = new Vue({
        el: '#app',
        data: {
          isActivated: true,
          activatedOne: "",
          activatedTwo: "fontSize",
          styleObj: {
            color: "yellow"
          },
          styleObjOne: {
            color: "black"
          }
        },
        methods: {
          handleDivClick() {
            this.isActivated = !this.isActivated;
          },
          handleDivClickOne() {
            // 可以简化成三元运算符
            if(this.activatedOne === "red") {
              this.activatedOne = ""
            } else {
              this.activatedOne = "red"
            }
          },
          handleDivClickTwo() {
            this.styleObj.color = this.styleObj.color === "yellow" ? "black" : "yellow"
          }
        }
      })
    </script>
  </body>
</html>



Vue条件渲染

  • v-if:根据布尔值,动态插入或者删除一个元素;
  • v-show:根据布尔值,动态将元素的display的值设置为block或者none;
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue条件渲染</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <div v-if="lookMeOne">{{messageOne}}</div>
      <div v-show="lookMeTwo">{{messageTwo}}</div>
    </div>

    <script>
      var vm = new Vue({
        el: '#app',
        data: {
          lookMeOne: true,
          lookMeTwo: false,
          messageOne: "hello vue.js",
          messageTwo: "hello vue.js"
        }
      })
    </script>
  </body>
</html>

运行以上代码,在浏览器的控制台上通过改变lookMeOne和lookMeTwo的值观察并分析v-if和v-show的区别。

根据以上分析可以得知,如果要经常切换某个元素显示或者隐藏,那么使用v-show无疑是最好的选择,能让网站性能提高。

  • v-else:需要接在v-if后面使用,否则会报错,作用就和普通的if…else类似,请运行下面代码进行查看:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue条件渲染</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <div v-if="lookMe">{{messageOne}}</div>
      <div v-else>{{messageTwo}}</div>
    </div>

    <script>
      var vm = new Vue({
        el: '#app',
        data: {
          lookMe: false,
          messageOne: "hello vue.js",
          messageTwo: "hello world"
        }
      })
    </script>
  </body>
</html>

更多的条件渲染内容,比如key值的作用请查看官方网站,我有些说不清楚额。

Vue列表渲染

列表渲染,无非是v-for指令的作用,在使用时我们需要记住的是不要漏了书写独一无二的key值来提高渲染效率,并且应该使用of替换v-for里面的in,因为for in会遍历原型上的属性。还有就是假如要修改遍历数组里的数据,记得使用变异的方法来修改,否则修改后的数据Vue是没有办法正确检测的。下面请看一段代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue条件渲染</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <div v-for="(item,index) of list" :key="item.id">
        {{item.text}}--{{index}}
      </div>
    </div>

    <script>
      var vm = new Vue({
        el: '#app',
        data: {
          list: [
            { id: "001", text: "这是第一项数据" },
            { id: "002", text: "这是第二项数据" },
            { id: "003", text: "这是第三项数据" }
          ]
        }
      })
    </script>
  </body>
</html>

添加数据时在控制台输入这样的指令:vm.list.push({id:“004”,text:“这是第四项数据”}),能正确被Vue实例检测到,并在页面显示;当在控制台输入这样的指令:vm.list[4] = {id:“004”,text:“这是第四项数据”},不能被Vue实例检测到,并且页面也没有任何的显示。

修改数据时在控制台输入这样的指令:vm.list[1] = {id:“002”,text:“这是被修改的第二项数据”},不能被Vue实例检测到,并且页面也没有任何的显示;当在控制台输入这样的指令:vm.list.splice(1,1,{id:“002”,text:“这是被修改的第二项数据”}),那么就能正确被Vue实例检测到,并在页面显示被修改的数据。

当然,直接修改数组的引用,那么数据也会被Vue正确检测并显示在页面,比如在控制台输入这样的指令:

vm.list = [
            { id: "001", text: "这是第一项数据" },
            { id: "002", text: "这是被修改的第二项数据" },
            { id: "003", text: "这是第三项数据" }
          ]

在下面那段代码中,template是没有被渲染出来的,假如template替换成div元素,那结果就会不同。

<div id="app">
  <template v-for="(item,index) of list" :key="item.id">
    <div>
      {{item.text}}--{{index}}
    </div>
    <span>{{item.text}}</span>
  </template>
</div>

对于使用v-for遍历数组和遍历对象,其中是有些区别的,我认认真真的正常速度听了老师的课程之后,再去看官方网站的教程,真的是跟发现新大陆一样,希望你们也能如此,不脱离官网,反复阅读和研究。

Vue中的set方法

之前改变对象里的数据时只有这么一种办法,先看看代码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue条件渲染</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <div v-for="(item,key,index) of userInfo">
        {{item}}--{{key}}--{{index}}
      </div>
    </div>

    <script>
      var vm = new Vue({
        el: '#app',
        data: {
          userInfo: {
            name: "jack",
            age: 28,
            gender: "male",
            salary: "secret"
          }
        }
      })
    </script>
  </body>
</html>

之前添加数据的方法就是在控制台输入指令:

vm.userInfo = {
            name: "jack",
            age: 28,
            gender: "male",
            salary: "secret",
            address: "北京"
          }

其实Vue提供了一个set()用来添加数据,在控制台输入指令:Vue.set(vm.userInfo,“address”,“北京”)即可以往userInfo对象里添加一个属性数据。

通过实例也能够往对象里添加数据,在控制台输入指令:vm.$set(vm.userInfo,“address”,“北京”)。

我们还能够通过set()来修改数组内的数据,请先看下面的代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中的set()</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <div v-for="(item,index) of list">
        {{item}}
      </div>
    </div>

    <script>
      var vm = new Vue({
        el: '#app',
        data: {
          list: [1, 2, 3, 4, 5]
        }
      })
    </script>
  </body>
</html>

在控制台输入指令:Vue.set(vm.list,1,66)就能够把数组第二项的内容变成"66";当然在控制台输入这样的指令也是可以的:vm.$set(vm.list,1,66)。

Vue组件使用的细节点

当组件为一个和父标签有特殊关联的时,直接导入子组件会让浏览器解析异常,比如下面的代码,会把row组件渲染在table标签之外:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue组件使用中的细节点</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <table>
        <tbody>
          <row></row>
          <row></row>
          <row></row>
        </tbody>
      </table>
    </div>

    <script>
      Vue.component("row", {
        template: "<tr><td>this is a row</td></tr>"
      })

      var vm = new Vue({
        el: "#app"
      })
    </script>
  </body>
</html>

造成这种问题的原因是由于父子标签之间的联系导致,只要使用is关键字将组件以另外一种方式导入即可:

<table>
  <tbody>
    <tr is="row"></tr>
    <tr is="row"></tr>
    <tr is="row"></tr>
  </tbody>
</table>

会有这种问题出现的还有ul和li标签,dl和dt标签,select和option标签等,解决的办法都是通过is关键字。

还有一个小细节就是在根组件中,data可以返回一个对象,但是在子组件中data却必须以函数的形式在内部返回一些数据,这么设计的目的是为了避免N个子组件彼此之间数据受到污染、互相影响,请看核心代码:

Vue.component("row", {
  data(){
    return {
  	content: "this is a row"
    }
  },
   template: "<tr><td>{{content}}</td></tr>"
 })



Vue操作DOM

虽然Vue不建议我们通过DOM来操作页面,但是当业务复杂的时候,只能通过关键字ref来对DOM进行操作实现相应的功能,以下代码是获得html元素的DOM:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue组件使用中的细节点</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <div ref="hello" @click="handleClick">
        hello world
      </div>
    </div>

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

        },
        methods: {
          handleClick() {
            // this.$refs指的是页面所有的ref引用
            // 在控制台查看输出结果是什么
            console.log(this.$refs.hello);
            console.log(this.$refs.hello.innerHTML);
          }
        }
      })
    </script>
  </body>
</html>

但是当我们需要获得一个组件的DOM时,也可说是组件的引用时,应该怎么做呢?下面看代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue组件使用中的细节点</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <counter ref="one" @change="handleChange"></counter>
      <counter ref="two" @change="handleChange"></counter>
      <div>{{total}}</div>
    </div>

    <script>
      Vue.component("counter", {
        template: "<div @click='handleClick'>{{number}}</div>",
        data() {
          return {
            number: 0
          }
        },
        methods: {
          handleClick() {
            this.number++;
            this.$emit("change")
          }
        }
      })

      var vm = new Vue({
        el: "#app",
        data: {
          total: 0
        },
        methods: {
          handleChange() {
            // 控制台看看下面代码输出的结果并加以分析
            console.log(this.$refs.one.number);
            console.log(this.$refs.two.number);
            // 对total进行累加
            this.total = this.$refs.one.number + this.$refs.two.number
            //this.total++ 这个也可以
          }
        }
      })
    </script>
  </body>
</html>

下面我们来看一个开发需求:在一个input输入框中,输入金额并且保留两位小数。这个需求看似简单,然而牵涉的东西却很多,比如数据需要使用v-model双向数据绑定,那么可以像下面这么写吗:

<li class="border-1px">
  贷款金额(元):
  <input 
      type="text" 
      placeholder="请填写贷款金额" 
      :value="MiddleInfo.loanAmount | Tofixed" 
      v-model="MiddleInfo.loanAmount"
   />
</li>

事实上这是有问题的,因为v-model和v-bind指令在这里面会起错误冲突。但是不这么写的话那么过滤器的方法就无法被调用了,具体可以查看官网,很明确的说明了过滤器的使用地方是在“插值中或者v-bind指令中”。

那么我们该如何解决上面的问题呢?此时我们需要的只是将v-model指令去掉,使用@chang事件来进行代替,然后我们用关键字ref来获取DOM的数据。具体实现过程请看下面代码:

//html结构层
<li class="border-1px">
  贷款金额(元):
  <input 
      type="text" 
      placeholder="请填写贷款金额" 
      :value="MiddleInfo.loanAmount | Tofixed" 
     @change="onChangeAmount"
     ref="AmountValue" 
   />
</li>

// 金额过滤器
<script>
filters: {
  Tofixed(value) {
    value = Number(value);
    return value.toFixed(2);
  }
}

// 金额改变触发事件
onChangeAmount() {
  this.MiddleInfo.loanAmount = this.$refs.AmountValue.value;
}
</script>



父组件向子组件的数据传递

这一块曾经专门开了个仓库来介绍,先到这里熟悉熟悉传送门,然后再回来。课程中老师讲到几个新的知识点,记录一下。

  • 父子组件的数据传递默认是单向的,意思是数据只能从父组件流向子组件,而子组件不能操作父组件的数据,这是因为父组件里可能会调用多个子组件,当其中一个子组件改变了父组件的内容,那不是要坏事了吗?

解决数据流单向问题:将父组件传递过来的数据复制一份出来重新使用,下面请看源码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>父子组件传值</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <counter :count="0"></counter>
      <counter :count="1"></counter>
    </div>

    <script>
      var counter = {
        props: ["count"],
        data() {
          return {
            number: this.count
          }
        },
        template: "<div @click='handleClick'>{{number}}</div>",
        methods: {
          handleClick() {
            // 下面为错误示范
            // this.count ++
            // 下面为正确示范
            this.number++
          }
        }
      }

      var vm = new Vue({
        el: "#app",
        data: {
          total: 0
        },
        components: {
          counter
        }
      })
    </script>
  </body>
</html>



子组件向父组件的数据传递

默认已经熟悉了前面传送们的内容,直接源码走起:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>父子组件传值</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <counter :count="0" @inc="handleIncrease"></counter>
      <counter :count="0" @inc="handleIncrease"></counter>
      <div>{{total}}</div>
    </div>

    <script>
      // 子组件区域
      var counter = {
        props: ["count"],
        data() {
          return {
            number: this.count
          }
        },
        template: "<div @click='handleClick'>{{number}}</div>",
        methods: {
          handleClick() {
            this.number = this.number + 2;
            this.$emit("inc", 2)
          }
        }
      }
	  // 父组件区域
      var vm = new Vue({
        el: "#app",
        data: {
          total: 0
        },
        methods: {
          handleIncrease(val) {
            this.total = this.total + val
          }
        },
        components: {
          counter
        }
      })
    </script>
  </body>
</html>



组件参数校验与非props特性

当父组件向子组件传递参数(属性)的时候,子组件有权利对传递过来的参数进行校验,下面请看一些常用的组件参数校验方式:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>组件参数校验与非props特性</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <child content="12345678"></child>
    </div>

    <script>
      // 子组件区域
      Vue.component("child", {
        props: {
          // 参数值为字符串型
          content: String,
          // 参数值为数字型
          content: Number,
          // 传数字和字符串都行
          content: [Number, String],
          // 复杂的参数检验
          content: {
            // 参数值为一个字符串型
            type: String,
            // required值为true表示content这个参数为必须传递
            required: false,
            // 当没有传入content参数时,默认content参数值为"default value"
            default: 'default value',
            // 校验传入参数的长度
            validator(value) {
              return(value.length > 5)
            }
          }
        },
        template: "<div>{{content}}</div>"
      })
      // 父组件区域
      var vm = new Vue({
        el: "#app"
      })
    </script>
  </body>
</html>

非props特性与props是相反的,主要有两点不同之处:1、不使用关键字props接收父组件的值;2、父组件调用子组件中定义的属性会被浏览器渲染出来。下面请看源码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>组件参数校验与非props特性</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <child content="12345678"></child>
    </div>

    <script>
      // 子组件区域
      Vue.component("child", {
        template: "<div>hello vue.js</div>"
      })
      // 父组件区域
      var vm = new Vue({
        el: "#app"
      })
    </script>
  </body>
</html>



给子组件绑定原生事件

在父组件调用子组件的时候,如果直接在调用子组件的地方使用@click="handleClick"其实是不会触发handleClick方法的,原因是此时的click是一个自定义的事件,结合父子组件传值就能大概明白什么意思。

<div id="app">
  <child @click="handelClick"></child>
</div>

在子组件绑定原生事件一共有两种方式,一种是直接在子组件内部定义,如下面代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>给子组件绑定原生事件</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <child></child>
    </div>

    <script>
      // 子组件区域
      Vue.component("child", {
        template: "<div @click='handleChildClick'>hello vue.js</div>",
        methods: {
          handleChildClick() {
            console.log("子组件内部事件被触发")
          }
        }
      })
      // 父组件区域
      var vm = new Vue({
        el: "#app"
      })
    </script>
  </body>
</html>

另外一种方式就是直接在调用子组件的地方使用native关键字,如下面代码所示:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>给子组件绑定原生事件</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <child @click.native="handleClick"></child>
    </div>

    <script>
      // 子组件区域
      Vue.component("child", {
        template: "<div>hello vue.js</div>"
      })
      // 父组件区域
      var vm = new Vue({
        el: "#app",
        methods: {
          handleClick() {
            console.log("点击事件被触发")
          }
        }
      })
    </script>
  </body>
</html>



非父子组件传值

还是之前的那篇总结,大家先来这里看看哈传送门,课程里面的内容这次看得还是有些晕乎,暂时不做讲解,看传送门的内容和结合下面的源码就好了:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>非父子组件间传值(Bus/总线/发布订阅模式/观察者模式)</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <child content="One"></child>
      <child content="Two"></child>
    </div>

    <script>
      // 在Vue实例的原型中新增一个属性bus作为中转站
      Vue.prototype.bus = new Vue()

      // 子组件区域
      Vue.component("child", {
        data() {
          return {
            selfContent: this.content
          }
        },
        props: {
          content: String
        },
        template: "<div @click='handleClick'>{{selfContent}}</div>",
        methods: {
          handleClick() {
            this.bus.$emit("change", this.selfContent)
          }
        },
        mounted() {
          var self = this;
          this.bus.$on("change", function(msg) {
            self.selfContent = msg
          })
        }
      })
      // 父组件区域
      var vm = new Vue({
        el: "#app",
        methods: {
          handleClick() {
            console.log("点击事件被触发")
          }
        }
      })
    </script>
  </body>
</html>



在Vue中使用插槽

为什么要在Vue里面使用插槽?插槽到底是什么来的?带着这两个问题,先来看看插槽的使用场景:在父组件调用子组件的时候,想要从父组件传递一些元素和内容与子组件合并,此时当然可以利用父子组件传值的方式实现这个功能,比如<child content="<p>123</p>"></child>,但是这种方式十分不容易管理,并且当要传递的内容过多时,该怎么办?此时我们的插槽就隆重登场啦!下面请看源码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中使用插槽(slot)</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <child>
        <h2>hello world</h2>
      </child>
    </div>

    <script>
      // 子组件区域
      Vue.component("child", {
        template: `<div>
                      <span>hello vue.js</span>
                      <slot>默认内容</slot>
                  </div>`
      })
      // 父组件区域
      var vm = new Vue({
        el: "#app"
      })
    </script>
  </body>
</html>

【分析】
在子组件中使用插槽slot引入父组件传递过来的<h2>hello world</h2>,那么就能够将传入过来的元素和内容进行渲染,并且slot还有个特点,就是:当没有<h2>hello world</h2>或其他传入元素的时候,slot会调用内部设置好的默认值。

具名插槽
能够分别识别多个从父组件传入进来的元素,将其当做组件一般在子组件里面进行调用,请看下面的代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中使用插槽(slot)</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <child>
        <div slot="header">这是头部区域</div>
        <div slot="footer">这是底部区域</div>
      </child>
    </div>

    <script>
      // 子组件区域
      Vue.component("child", {
        template: `<div>
        	      <slot name="header"><h1>默认值</h1></slot>
                      <span>hello vue.js</span>
                      <slot name="footer"><h1>默认值</h1></slot>
                  </div>`
      })
      // 父组件区域
      var vm = new Vue({
        el: "#app"
      })
    </script>
  </body>
</html>

运行上面的代码,即可明白具名插槽的作用是啥。

Vue中的作用域插槽

一谈到作用域,首先让我想到的就是各个组件的相互独立、互不干扰问题。果不然奇然,作用域插槽就是为了让不同父组件调用子组件时显示不同的形态,意思就是子组件的相关形态并不是它自己来决定的,而是由调用它的父组件来决定,下面请看代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中的作用域插槽</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <child>
        <template slot-scope="props">
          <h5>{{props.content}}</h5>
        </template>
      </child>
    </div>

    <script>
      // 子组件区域
      Vue.component("child", {
        data() {
          return {
            list: [1, 2, 3, 4, 5, 6]
          }
        },
        template: `<div>
        		     <slot v-for="item of list" :content="item"></slot>
                  </div>`
      })
      // 父组件区域
      var vm = new Vue({
        el: "#app"
      })
    </script>
  </body>
</html>

当然还有很多关于作用域插槽的不同操作,虽然手法可能有些不同,但是它的执行本质却是一毛一样的。

动态组件与v-once指令

动态组件在Vue中的意思是:一个元素(component)将所有要被调用的组件包裹在其中,然后根据关键字is绑定的数据的变化分别将集合中的组件渲染出来,下面请看代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中的动态组件</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <component :is="type"></component>
      <!--<child-one v-if="type==='child-one'"></child-one>
      <child-two v-if="type==='child-two'"></child-two>-->
      <button @click="handleBtnClick">change</button>
    </div>

    <script>
      Vue.component("child-one", {
        template: "<div>child-one</div>"
      })

      Vue.component("child-two", {
        template: "<div>child-two</div>"
      })

      var vm = new Vue({
        el: "#app",
        data: {
          type: 'child-one'
        },
        methods: {
          handleBtnClick() {
            this.type = (this.type === 'child-one' ? 'child-two' : 'child-one')
          }
        }
      })
    </script>
  </body>
</html>

v-once指令
这个指令的作用就是将初次渲染的静态文件存储在内存中,第N次调用该文件的时候就直接从内存中拿出来即可,而不用重新渲染这个文件,从而提高了渲染效率和性能。

Vue中CSS动画原理

在解析动画原理之前,先看看下面一副图

通过这个图我们能够发现Vue动画的执行过程总共分为三个阶段(页面从隐藏到显示状态)

  • 第一阶段:监测到页面即将显示,添加fade-enterfade-enter-active这两个类;
  • 第二阶段:监测到页面已经显示,添加fade-enter-to这个类同时删除fade-enter这个类;
  • 第三阶段:监测到页面已经显示完全,删除fade-enter-activefade-enter-to这两个类;
  • 注意:fade这个关键字是transition元素对应的name值,默认的name值是v。

下面来看一段代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中CSS动画原理</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
      .fade-enter {
        opacity: 0;
      }
      .fade-enter-active {
        transition: opacity 2s;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <transition name="fade">
        <div v-if="show">hello vue.js</div>
      </transition>
      <button @click="handleClick">点我</button>
    </div>

    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          show: true
        },
        methods: {
          handleClick() {
            this.show = !this.show
          }
        }
      })
    </script>
  </body>
</html>

【解析】
上面的代码执行过程:首先,在动画执行之前,系统自动添加了fade-enter这个类,此时div元素的opcity的值为0,也就是在页面完全透明,接着动画执行之后,fade-enter这个类被删除, 此时div元素的opcity值恢复默认值为1,fade-enter-active这个类从始至终都在监视着opacity这个属性值的变化,当这个值发生变化时,动画效果则执行。

得知了元素从隐藏到显示的动画执行过程,那么现在来看看元素从显示到隐藏执行过程是如何的,先看一看下方一副图

通过这幅图,我们同样能够得知页面从显示到隐藏动画的执行过程也分为三个阶段

  • 第一阶段:监测到页面即将隐藏,添加fade-leavefade-leave-active这两个类;
  • 第二阶段:监测到页面已经从显示到隐藏,添加fade-leave-to这个类同时删除fade-leave这个类;
  • 第三阶段:监测到页面已经完全隐藏,删除fade-leave-activefade-leave-to这两个类。

下面结合一段代码进行分析

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中CSS动画原理</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
      .fade-enter {
        opacity: 0;
      }
      .fade-enter-active {
        transition: opacity 2s;
      }
      .fade-leave-to {
        opacity: 0;
      }
      .fade-leave-active {
        transition: opacity 2s;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <transition name="fade">
        <div v-if="show">hello vue.js</div>
      </transition>
      <button @click="handleClick">点我</button>
    </div>

    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          show: true
        },
        methods: {
          handleClick() {
            this.show = !this.show
          }
        }
      })
    </script>
  </body>
</html>

【解析】
很明显,在显示到隐藏阶段中,并没有定义fade-leave这个类,原因很简单:因为此时fade-leave这个类里面的属性和值等于页面显示的属性和值,即opacity为1,所以无需去定义fade-leave这个类的属性与值,而只需在隐藏页面的时候添加fade-leave-to这个类并且将opacity的值设为0即可。之后fade-leave-active这个类的内部transition属性监测到了属性opacity的变化,因此延迟动画执行。

Vue中使用animate.css库

和CSS3一样,除了能在Vue中使用transition动画,animation动画也能够使用,使用方式如下

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中使用animate.css库</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
      @keyframes bounce-in {
        0% {
          transform: scale(0);
        }
        50% {
          transform: scale(1.5);
        }
        100% {
          transform: scale(1);
        }
      }
      .fade-enter-active {
        /*定义视图被置于 X和Y轴的何处*/
        transform-origin: left center;
        animation: bounce-in 2s;
      }
      .fade-leave-active {
        /*定义视图被置于 X和Y轴的何处*/
        transform-origin: left center;
        animation: bounce-in 2s reverse;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <transition name="fade">
        <div v-if="show">hello vue.js</div>
      </transition>
      <button @click="handleClick">点我</button>
    </div>

    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          show: true
        },
        methods: {
          handleClick() {
            this.show = !this.show
          }
        }
      })
    </script>
  </body>
</html>

【解析】
上面代码的执行过程是这样的:页面从显示到隐藏是先扩大(scale)1倍,接着扩大1.5倍,最后扩大0倍;而页面从隐藏到显示则动画效果完全反过来,即先扩大0倍,接着扩大1.5倍,最后扩大为1倍。

由于在transition标签中能够自定义class类,这是各种为Vue服务的css动画框架出现的基础,先看看如何自定义class类

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中使用animate.css库</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
      @keyframes bounce-in {
        0% {
          transform: scale(0);
        }
        50% {
          transform: scale(1.5);
        }
        100% {
          transform: scale(1);
        }
      }
      .active {
        /*定义视图被置于 X和Y轴的何处*/
        transform-origin: left center;
        animation: bounce-in 2s;
      }
      .leave {
        /*定义视图被置于 X和Y轴的何处*/
        transform-origin: left center;
        animation: bounce-in 2s reverse;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <transition 
      	name="fade"
      	enter-active-class="active"
      	leave-active-class="leave"
      >
        <div v-if="show">hello vue.js</div>
      </transition>
      <button @click="handleClick">点我</button>
    </div>

    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          show: true
        },
        methods: {
          handleClick() {
            this.show = !this.show
          }
        }
      })
    </script>
  </body>
</html>

前面使我们自定义的动画,为了节省开发时间与精力,我们可以使用一个CSS动画框架,叫做Animate.css。我们先来官方网站进行下载和查看效果,接着我们就可以进行使用了,下面请看代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中使用animate.css库</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <link rel="stylesheet" href="animate.css" />
  </head>
  <body>
    <div id="app">
      <transition 
      	name="fade"
      	enter-active-class="animated swing"
      	leave-active-class="animated shake"
      >
        <div v-if="show">hello vue.js</div>
      </transition>
      <button @click="handleClick">点我</button>
    </div>

    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          show: true
        },
        methods: {
          handleClick() {
            this.show = !this.show
          }
        }
      })
    </script>
  </body>
</html>

在Vue中使用animate.css是不是十分简单?只要引入框架源文件,定义如下的语法即可运行成功:

<transition name="fade" enter-active-class="animated swing" leave-active-class="animated shake">



在Vue中同时使用过渡和动画

假如想要页面在刚开始加载的时候就有动画效果,那么该如何实现呢?下面请看代码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中同时使用过渡和动画</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <link rel="stylesheet" href="animate.css" />
  </head>
  <body>
    <div id="app">
    	<!--appear意思是这个属性刚出现的时候会有动画效果-->
      <transition 
      	name="fade"
      	appear
      	enter-active-class="animated swing"
      	leave-active-class="animated shake"
      	appear-active-class="animated shake"
      >
        <div v-if="show">hello vue.js</div>
      </transition>
      <button @click="handleClick">点我</button>
    </div>

    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          show: true
        },
        methods: {
          handleClick() {
            this.show = !this.show
          }
        }
      })
    </script>
  </body>
</html>

假如在执行animation动画的同时也执行transition动画(也成为过渡),那么该如何实现呢?下面请看代码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中同时使用过渡和动画</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <link rel="stylesheet" href="animate.css" />
    <style>
    	.fade-enter {
    		opacity: 0;
    	}
    	.fade-enter-active {
    		transition: opacity 3s;
    	}
    	.fade-leave-to {
    		opacity: 0;
    	}
    	.fade-leave-active {
    		transition: opacity 3s;
    	}
    </style>
  </head>
  <body>
    <div id="app">
    	<!--appear意思是这个属性刚出现的会有动画效果-->
      <transition 
      	name="fade"
      	appear
      	enter-active-class="animated swing fade-enter-active"
      	leave-active-class="animated shake fade-leave-active"
      	appear-active-class="animated shake"
      >
        <div v-if="show">hello vue.js</div>
      </transition>
      <button @click="handleClick">点我</button>
    </div>

    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          show: true
        },
        methods: {
          handleClick() {
            this.show = !this.show
          }
        }
      })
    </script>
  </body>
</html>

上面代码成功实现了既有animation动画效果,也有transition过渡效果。然而却有一个执行时间的问题,在animate.css框架中,这个时间默认是1s,然而在transition中定义的却是3s,那么该如何让transition的执行时间为统一值,只需这么做即可:

<transition
      	type="transition"
      	name="fade"
      	appear
      	enter-active-class="animated swing fade-enter-active"
      	leave-active-class="animated shake fade-leave-active"
      	appear-active-class="animated shake"
>

当然也能自己定义动画执行的统一时间,代码如下

<transition
      	:duration="10000"
      	name="fade"
      	appear
      	enter-active-class="animated swing fade-enter-active"
      	leave-active-class="animated shake fade-leave-active"
      	appear-active-class="animated shake"
>

在代码执行的时候,我们可以打开控制台查看相应类的变化和效果的变化。并且我们可以把duration设置的更复杂一点点,如

<transition
      	:duration="{enter:5000,leave:10000}"
      	name="fade"
      	appear
      	enter-active-class="animated swing fade-enter-active"
      	leave-active-class="animated shake fade-leave-active"
      	appear-active-class="animated shake"
>



Vue中的JS动画与Velocity.js的结合

在Vue中使用JavaScript动画时,首先得了解动画钩子函数,这一块自行上网搜索了解哈,和Vue的生命周期本质可以说是一毛一样了。下面我们来看一个简单的JS动画效果案例

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中的JS动画与Velocity.js</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <transition 
      	name="fade" 
      	@before-enter="handleBeforEnter"
      	@enter="handleEnter"
      >
        <div v-if="show">hello vue.js</div>
      </transition>
      <button @click="handleClick">点我</button>
    </div>

    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          show: true
        },
        methods: {
          handleClick() {
            this.show = !this.show
          },
          handleBeforEnter(el){
	      el.style.color = "red"
	  },
	  handleEnter(el,done){
	      setTimeout( () => {
	         el.style.color = "green"
	      },2000)
	   }
        }        
      })
    </script>
  </body>
</html>

细心的你们应该发现了在handleEnter这个方法中,done并没有被使用到。其实这个关键字很重要,调用它的时候,才能告诉程序这个动画执行完了,那么after-enter这个动画钩子函数才会开始生效,下面请看代码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中的JS动画与Velocity.js</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <transition 
      	name="fade" 
      	@before-enter="handleBeforEnter" 
      	@enter="handleEnter" 
      	@after-enter="handleAfterEnter"
      >
        <div v-if="show">hello vue.js</div>
      </transition>
      <button @click="handleClick">点我</button>
    </div>

    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          show: true
        },
        methods: {
          handleClick() {
            this.show = !this.show
          },
          handleBeforEnter(el) {
            el.style.color = "red"
          },
          handleEnter(el, done) {
            setTimeout(() => {
              el.style.color = "green";
            }, 2000)

            setTimeout(() => {
              done()
            }, 4000)
          },
          handleAfterEnter(el) {
            el.style.color = "black"
          }
        }
      })
    </script>
  </body>
</html>

以上动画效果都是原生的js动画,并且都是页面从隐藏到显示的动画效果,假如我们需要从显示到隐藏的动画效果该怎么做,很简单,只要把enter替换成leave即可。为了实现更复杂的js动画,我们可以使用js动画库——Velocity.js

使用该库还是老方法,先到官方网站进行下载,然后导入到项目之中,接下来就看看简单的一个案例实现吧

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中的JS动画与Velocity.js</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="velocity.js"></script>
  </head>
  <body>
    <div id="app">
      <transition 
      	name="fade" 
      	@before-enter="handleBeforEnter" 
      	@enter="handleEnter" 
      	@after-enter="handleAfterEnter"
      >
        <div v-if="show">hello vue.js</div>
      </transition>
      <button @click="handleClick">点我</button>
    </div>

    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          show: true
        },
        methods: {
          handleClick() {
            this.show = !this.show
          },
          handleBeforEnter(el) {
            el.style.opacity = 0;
          },
          handleEnter(el, done) {
            Velocity(el, {
            	opacity: 1
            }, {
            	duration: 2000,
            	complete: done
            })
          },
          handleAfterEnter(el) {
            el.style.color = "red"
          }
        }
      })
    </script>
  </body>
</html>

想要了解更多的velocity.js知识,请看它的中文文档

Vue中多个元素或组件的过渡

在Vue中有个内部机制:就是当一个元素被调用了,会被储存起来,那么下次重复调用的就能够直接拿过来用了,这样虽然提升了性能,但是也是由于这样导致动画效果在N个元素件切换的时候会失效,解决办法只要增加一个key值即可,代码如下

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中多个元素或组件的过渡</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
    	.v-enter {
    		opacity: 0;
    	}
    	.v-enter-active {
    		transition: opacity 1s;
    	}
    	.v-leave-active {
    		transition: opacity 1s;
    	}
    	.v-leave-to {
    		opacity: 0;
    	}
    </style>
  </head>
  <body>
    <div id="app">
      <transition>
        <div v-if="show" key="hello">hello vue.js</div>
        <div v-else key="world">hello world</div>
      </transition>
      <button @click="handleClick">点我</button>
    </div>

    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          show: true
        },
        methods: {
          handleClick() {
            this.show = !this.show
          }
        }
      })
    </script>
  </body>
</html>

以上代码执行之后虽然动画效果有了,然而却是十分的丑陋,为了优化效果,我们可以在transition内添加mode属性,该属性有两个值,代入进去你会发现神奇的效果,分别是

  • in-out:表示元素是先显示一个,然后另一个再进行隐藏
  • out-in:表示元素是先隐藏一个,然后另一个再进行显示

下面来看组件间的过渡动画效果,需要借助之前学到的“动态组件”知识,当然不借助也是可以的,下面来看代码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中多个元素或组件的过渡</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
      .v-enter {
        opacity: 0;
      }
      .v-enter-active {
        transition: opacity 1s;
      }
      .v-leave-active {
        transition: opacity 1s;
      }
      .v-leave-to {
        opacity: 0;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <transition mode="out-in">
        <child-one v-if="show"></child-one>
        <child-two v-else></child-two>
      </transition>
      <button @click="handleClick">点我</button>
    </div>

    <script>
      Vue.component("child-one", {
        template: "<div>child-one</div>"
      })

      Vue.component("child-two", {
        template: "<div>child-two</div>"
      })

      var vm = new Vue({
        el: "#app",
        data: {
          show: true
        },
        methods: {
          handleClick() {
            this.show = !this.show
          }
        }
      })
    </script>
  </body>
</html>

使用动态组件实现该动画效果会简洁很多,代码如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中多个元素或组件的过渡</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
      .v-enter {
        opacity: 0;
      }
      .v-enter-active {
        transition: opacity 1s;
      }
      .v-leave-active {
        transition: opacity 1s;
      }
      .v-leave-to {
        opacity: 0;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <transition mode="out-in">
      	<component :is="type"></component>
      </transition>
      <button @click="handleClick">点我</button>
    </div>

    <script>
      Vue.component("child-one", {
        template: "<div>child-one</div>"
      })

      Vue.component("child-two", {
        template: "<div>child-two</div>"
      })

      var vm = new Vue({
        el: "#app",
        data: {
          type: "child-one"
        },
        methods: {
          handleClick() {
            this.type = (this.type==="child-one"?"child-two":"child-one")
          }
        }
      })
    </script>
  </body>
</html>



Vue中的列表过渡

在列表遍历循环中,其实不建议让:key=“index”,因为这会导致性能上的下降同时功能方面的问题也有影响,所以能不用index作为key的值就不要用。好了,说正事,为了让列表在遍历的时候有过渡效果,只要使用关键字transition-group即可,下面我们来看代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中的列表过渡</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
      .v-enter {
        opacity: 0;
      }
      .v-enter-active {
        transition: opacity 2s;
      }
      .v-leave-to {
        opacity: 0;
      }
      .v-leave-active {
        transition: opacity 2s;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <transition-group>
        <div v-for="(item,index) of list" :key="item.id">
          {{item.title}}
        </div>
      </transition-group>
      <button @click="handleClick">点我</button>
    </div>

    <script>
      var count = 0;
      var vm = new Vue({
        el: "#app",
        data: {
          list: []
        },
        methods: {
          handleClick() {
            this.list.push({
              id: count++,
              title: "hello vue.js"
            })
          }
        }
      })
    </script>
  </body>
</html>



Vue中的动画封装

为了提高动画的复用性,我们可以为一个动画进行封装。封装的原理主要是使用到插槽和js动画,下面我们一起来看源码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Vue中封装动画</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <fade :show="show">
        <div>hello vue.js</div>
      </fade>
      <fade :show="show">
        <h1>hello vue.js</h1>
      </fade>
      <button @click="handleClick">点我</button>
    </div>

    <script>
      Vue.component("fade", {
        props: ["show"],
        template: `<transition @before-enter="handleBeforEnter" @enter="handleEnter">
    				<slot v-if="show"></slot>
    			</transition>`,
        methods: {
          handleBeforEnter(el) {
            el.style.color = "red"
          },
          handleEnter(el, done) {
            setTimeout(() => {
              el.style.color = "green";
              done()
            }, 2000)
          }
        }
      })

      var vm = new Vue({
        el: "#app",
        data: {
          show: true
        },
        methods: {
          handleClick() {
            this.show = !this.show
          }
        }
      })
    </script>
  </body>
</html>
6人推荐
随时随地看视频
慕课网APP

热门评论

我来给自己点个赞

查看全部评论