【Web前端】Vue核心基础

1. Vue简介

Vue的概念:Vue是一套用于构建用户界面的渐进式JavaScript框架。

渐进式:表示Vue可以自底向上逐层的应用,从简单应用逐渐递进到复杂应用。

Vue的特点:

  • 采用组件化模式,提高代码复用率,且让代码更好维护。
  • 声明式编码,让编码人员无需直接操作DOM,提高开发效率。
  • 使用虚拟DOM和Diff算法,尽量复用DOM节点。

学习Vue之前要掌握的js基础知识:

  • ES6语法规范
  • ES6模块化
  • 包管理器
  • 原型和原型链
  • 数组常用方法
  • axios
  • promise

2. Vue官网使用指南

Vue官网:

以Vue2文档为例,对该文档顶部的导航栏做详细的介绍。

在这里插入图片描述

1.学习:其中最重要的是教程和API,贯穿vue学习的始终。

  • 教程:入门vue的教程
  • API:vue的属性、方法和指令等
  • 风格指南:官方推荐的vue代码风格指南
  • 示例:官方展示的一些vue案例
  • Cookbook:展示一些vue案例,教你如何优化vue代码
    在这里插入图片描述

2.生态系统:其中工具和插件很重要,用于搭建Vue工程。

在这里插入图片描述

3.资源列表:其中Awesome Vue、浏览和Vue相关的包,这两个包含了官方整理的一些优秀的vue周边库。
在这里插入图片描述

3. 初识Vue

3.1 搭建Vue开发环境

1.首先需要下载Vue

进入Vue官网的导航列表:学习 -> 教程 -> 安装,也可点击如下地址直接跳转:https://v2.cn.vuejs.org/v2/guide/installation.html

有开发和生产两种版本,这里我们使用开发版本,其中包含了完整的警告和调试模式,适用于学习阶段。

  • 开发环境版本:安装包名称为 vue.js,包含了有帮助的命令行警告,体积较大。
  • 生产环境版本:安装包名称为 vue.min.js,优化了尺寸和速度,体积较小。
    在这里插入图片描述

2.新建一个js文件夹,将下载好的vue.js放入js文件夹中,然后使用 <script>标签引入 vue.js,就可以使用Vue了

目录结构如下:

在这里插入图片描述

index.html内容如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- 引入Vue -->
    <script src="../js/vue.js"></script>
  </head>
  <body></body>
</html>

打开页面后,再打开控制台,发现有两个提示:

在这里插入图片描述

3.消除开发环境的提示

添加vue开发者工具,消除第一个提示,步骤如下:打开Chrome扩展程序 -> 打开开发者模式 ->拖入vue_dev_tools.crx 文件到浏览器并松开,点击添加程序即可

在这里插入图片描述
script标签中写入以下vue配置,消除第二个提示:

Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

3.2 HelloWorld案例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- 引入Vue -->
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <!-- 准备好一个容器 -->
    <div id="app">
      <h1>{{message}}</h1>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      // 创建Vue实例
      new Vue({
      	// 配置
        el: "#app", // el用于指定当前Vue实例为哪个容器服务
        data: { // data中用于存储数据,数据供el所指定的容器去使用
          message: "Hello World!"
        }
      });
    </script>
  </body>
</html>

代码解释:
1.el表示元素(element),值通常为css选择器,此处#app为id选择器,对应<div id="app">,此时当前vue实例挂载到了div容器上,为该容器服务。
2.data表示数据,数据供挂载的容器去使用,而容器中使用了模板语法 {{message}},引用了vue实例中的data数据。
3.vue实例中的message属性值Hello World!替换了{{message}},因此<h1>{{message}}</h1>最终解析成了<h1>Hello World!</h1>

运行结果如下:

在这里插入图片描述

注:
1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
2.容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
3.容器里的代码被称为Vue模板
4.Vue实例和容器是一一对应的
5.真实开发中只有一个Vue实例,并且会配合着组件一起使用
6.{{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性
7.一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新

3.3 el与data的两种写法

1.eldata的第一种写法是对象式写法,就是上述HelloWorld案例中的写法,即通过配置对象属性和属性值。

// 创建Vue实例
new Vue({
  // 配置
  el: "#app", // 将Vue实例挂载到 id=app的容器上
  data: {  // data中用于存储数据,数据供挂载的容器去使用
    message: "Hello World!"
  }
});

2.eldata的第二种写法是函数式写法

// 创建Vue实例
const vm = new Vue({
  // data的函数式写法,组件中必须使用该写法
  data() {
    return {
      message: "Hello World!"
    };
  }
});

vm.$mount("#app"); // 等价于 el: "#app"

注意:vue实例中的函数不能使用箭头函数的写法,原本函数中的this指向的是vue实例对象,使用箭头函数后,函数中的this指向的就是window对象。

3.4 MVVM模型

Vue采用了MVVM架构模型,其对应关系如下:

  • M:模型(Model) ,对应 data 中的数据
  • V:视图(View) ,对应模板
  • VM:视图模型(ViewModel) , 对应 Vue 实例对象

在这里插入图片描述

在这里插入图片描述

3.5 模板语法

模板语法:Vue实例挂载的容器里中的代码被称为模板,模板语法也就是这个容器中代码的语法规范,模板语法分为两大类(插值语法和指令语法)。

插值语法:

  • 功能:用于解析标签体内容
  • 写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性

指令语法:

  • 功能:用于解析标签(包括标签属性、标签体内容、绑定事件等)
  • 写法:v-xxx,如v-bindv-modelv-on
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- 引入Vue -->
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- 插值语法,{{js表达式}} -->
      <h3>{{message.toUpperCase()}}</h3>
      <!-- 指令语法,v-xxx -->
      <a v-bind:href="url">百度链接</a>
    </div>

    <script>
      new Vue({
        el: "#app",
        data: {
          message: "hello world",
          url: "https://www.baidu.com/"
        }
      });
    </script>
  </body>
</html>

运行结果:
在这里插入图片描述

4. 数据绑定

Vue中有两种数据绑定的方式:

  • 单向绑定(v-bind):数据只能从data流向页面
  • 双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data

4.1 v-bind单向数据绑定

v-bind:用于单向数据绑定,该指令也可以简写为 :

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- 普通写法 -->
      单向数据绑定:<input type="text" v-bind:value="name" /> <br />
      <!-- 简写 -->
      单向数据绑定:<input type="text" :value="name" />
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          name: "hello"
        }
      });
    </script>
  </body>
</html>

验证数据能从data流向页面:打开控制台,输入 vm.name="hello123"并回车,可以发现页面中的数据也显示成了hello123

在这里插入图片描述

验证页面的数据无法流向data:在文本框中输入hello1234,随后在控制台输入vm.name,得到的结果依然是hello123,说明vue实例中的data数据未发生改变

在这里插入图片描述

4.2 v-model双向数据绑定

v-model:用于双向数据绑定,双向绑定只能应用在表单类元素(输入类元素)上,如:input、select等

注:v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- 普通写法 -->
      双向数据绑定:<input type="text" v-model:value="name" /> <br />
      <!-- 简写 -->
      双向数据绑定:<input type="text" v-model="name" />
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          name: "hello"
        }
      });
    </script>
  </body>
</html>

验证数据能从data流向页面:打开控制台,输入 vm.name="hello123"并回车,可以发现页面中的数据也显示成了hello123

在这里插入图片描述

验证页面的数据也能流向data:在文本框中输入hello1234,随后在控制台输入vm.name,得到的结果是hello1234,说明vue实例中的data数据随页面数据的改变而改变

在这里插入图片描述

5. 事件处理

5.1 v-on绑定事件

事件的基本使用:

  • 使用v-on:xxx="yyy" 指令用于绑定事件,其中v-on:可以简写为@xxx是事件名,yyy是回调函数名(也可以是简单的语句,如isShow=!isShow
  • methods对象用于配置vm的方法,事件的回调函数需要配置在methods对象中
  • methods中配置的函数,this指向的是vm或组件实例对象,且配置的函数不能使用箭头函数,否则this指向的是window对象
  • @click="demo"@click="demo($event)" 效果一致,但后者可以传参

1.绑定事件,且回调函数不传参

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- 普通写法 -->
      <button v-on:click="showInfo">按钮1</button>
      <!-- 简写 -->
      <button @click="showInfo">按钮2</button>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        // 配置方法
        methods: {
          // 事件回调函数
          showInfo() {
            alert("Hello World!");
          }
        }
      });
    </script>
  </body>
</html>

2.绑定事件,且给回调函数传参

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <button @click="showInfo">按钮1</button>
      <!-- $event为占位符,表示此时第一个参数为事件对象 -->
      <button @click="showInfo1($event,'Jack')">按钮2</button>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        // 配置方法
        methods: {
          // 不传参时,函数的第一个参数代表事件对象
          showInfo(e) {
            console.log(e.target); // 触发事件的对象
            console.log(e.type); // 事件的类型
            console.log(this); // this指向vm
          },
          // 传参时,根据$event占位符的位置确定哪个参数代表事件对象
          showInfo1(e, name) {
            console.log(e.target);
            console.log(e.type);
            console.log(`Hello ${name}`);
          }
        }
      });
    </script>
  </body>
</html>

运行结果:

在这里插入图片描述

5.2 事件修饰符

Vue中的事件修饰符:

  • prevent:阻止默认事件(常用)
  • stop:阻止事件冒泡(常用)
  • once:事件只触发一次(常用)
  • capture:使用事件的捕获模式
  • self:只有event.target是当前操作的元素时才触发事件
  • passive:事件的默认行为立即执行,无需等待事件回调执行完毕

注:修饰符可以连续写,如@click.prevent.stop="showInfo"

1.prevent:阻止事件的默认行为(常用)

同js事件对象中的e.preventDefault()方法,用于阻止事件的默认行为

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- 阻止链接跳转 -->
      <a href="https://www.baidu.com/" @click.prevent="showInfo">百度链接</a>
      <!-- 阻止表单提交 -->
      <form action="https://www.baidu.com/">
        <input type="submit" value="提交" @click.prevent="showInfo" />
      </form>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        methods: {
          showInfo() {
            console.log("已阻止事件的默认行为");
          }
        }
      });
    </script>
  </body>
</html>

运行结果:点击百度链接和表单提交按钮,都不发生页面跳转,因为跳转的默认行为被阻止了

2.stop:阻止事件冒泡(常用)

同js事件对象中的e.stopPropagation()方法,用于阻止事件冒泡

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
    <style>
      .father {
        position: relative;
        width: 200px;
        height: 200px;
        background-color: #666;
        margin: 30px;
      }

      .son {
        position: absolute;
        width: 100px;
        height: 100px;
        background-color: orange;
        margin: auto;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
      }
    </style>
  </head>
  <body>
    <div id="app" class="father" @click="showInfo1">
      <!-- 阻止事件冒泡 -->
      <div class="son" @click.stop="showInfo2"></div>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        methods: {
          showInfo1() {
            alert("father");
          },
          showInfo2() {
            alert("son");
          }
        }
      });
    </script>
  </body>
</html>

运行结果:点击子元素 ,只弹出警示框“son”,没有后续弹出“father”
(释义:点击子元素son以后,阻止冒泡,因此没有冒泡到父元素,父元素的事件行为不执行)

在这里插入图片描述

3.once:事件只触发一次(常用)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- 此按钮只在第一次点击时,触发事件 -->
      <button @click.once="showInfo">点击按钮</button>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        methods: {
          showInfo() {
            alert("Hello World!");
          }
        }
      });
    </script>
  </body>
</html>

运行结果:第一次点击按钮时,弹出警示框,后续再点击无法弹出

4.capture:使用事件的捕获模式

注:js中addEventListener(type, listener, useCapture)useCapturefalse或者忽略时,事件处于冒泡阶段;为true时,事件处于捕获阶段

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
    <style>
      .father {
        position: relative;
        width: 200px;
        height: 200px;
        background-color: #666;
        margin: 30px;
      }

      .son {
        position: absolute;
        width: 100px;
        height: 100px;
        background-color: orange;
        margin: auto;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
      }
    </style>
  </head>
  <body>
    <!-- 绑定事件到父元素father,为捕获阶段 -->
    <div id="app" class="father" @click.capture="showInfo1">
      <div class="son" @click="showInfo2"></div>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        methods: {
          showInfo1() {
            alert("father");
          },
          showInfo2() {
            alert("son");
          }
        }
      });
    </script>
  </body>
</html>

运行结果:点击橘色的子元素div,先弹出警示框 “father”,后弹出警示框 “son”
(释义:捕获从上往下传递,点击子元素son以后,会先找到父元素并执行它的事件,再往下找到子元素后,执行子元素的事件)

在这里插入图片描述

5.self:只有event.target是当前操作的元素时才触发事件

注:通过这种方式,也能阻止事件冒泡

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
    <style>
      .father {
        position: relative;
        width: 200px;
        height: 200px;
        background-color: #666;
        margin: 30px;
      }

      .son {
        position: absolute;
        width: 100px;
        height: 100px;
        background-color: orange;
        margin: auto;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
      }
    </style>
  </head>
  <body>
    <!-- 只有当e.target是当前操作的元素时才触发事件,即只有点击该父元素才触发父元素的事件 -->
    <div id="app" class="father" @click.self="showInfo1">
      <!-- <div id="app" class="father" @click="showInfo1"> -->
      <div class="son" @click="showInfo2"></div>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        methods: {
          showInfo1() {
            alert("father");
          },
          showInfo2() {
            alert("son");
          }
        }
      });
    </script>
  </body>
</html>

运行结果:只有点击灰色的父元素div时,才弹出警示框 “father”

在这里插入图片描述

5.3 键盘事件

Vue中常用的按键别名:

  • 回车 => enter
  • 删除 => delete (捕获“删除”和“退格”键)
  • 退出 => esc
  • 空格 => space
  • 换行 => tab (特殊,必须配合keydown去使用)
  • 上 => up
  • 下 => down
  • 左 => left
  • 右 => right

注:
1.Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名,如caps-lock)
2.可以使用keyCode去指定具体的按键(不推荐),如e.keyCode===13代表回车键
3.Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名,如Vue.config.keyCodes.huiche=13,此时别名huiche代表回车键
4.系统修饰键(用法特殊):ctrl、alt、shift、meta,

  • 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
  • 配合keydown使用:正常触发事件。
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- 按下回车键松开时触发事件 -->
      <input type="text" placeholder="输入完成后请按回车" @keyup.enter="showInfo" />
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        methods: {
          showInfo(e) {
            alert(e.target.value);
          }
        }
      });
    </script>
  </body>
</html>

运行结果:输入“123456”,再按下回车后松开

在这里插入图片描述

6. 计算属性

6.1 姓名案例

要求:一个input框输入“姓”,另一个input框输入“名”,生成“姓-名”

解题方法有三种:

  • 使用插值语法{{}}实现
  • 使用methods方法实现
  • 使用computed计算属性实现(推荐)

1.使用插值语法{{}}实现姓名案例,通过直接在模板里进行字符串拼接

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      姓:<input type="text" v-model="firstName" /> <br />
      名:<input type="text" v-model="lastName" /> <br />
      全名:<span>{{firstName}}-{{lastName}}</span>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          firstName: "张",
          lastName: "三"
        }
      });
    </script>
  </body>
</html>

运行结果如下:在两个输入框中分别输入“姓”和“名”,得到全名为“姓-名”

在这里插入图片描述

2.使用methods方法实现姓名案例,通过调用方法,将方法的返回值渲染到模板中

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      姓:<input type="text" v-model="firstName" /> <br />
      名:<input type="text" v-model="lastName" /> <br />
      全名:<span>{{fullName()}}</span>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          firstName: "张",
          lastName: "三"
        },
        methods: {
          fullName() {
            return this.firstName + "-" + this.lastName;
          }
        }
      });
    </script>
  </body>
</html>

3.使用computed计算属性实现,通过计算原有的属性,得到新的属性,并将其渲染到模板中

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      姓:<input type="text" v-model="firstName" /> <br />
      名:<input type="text" v-model="lastName" /> <br />
      全名:<span>{{fullName}}</span>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          firstName: "张",
          lastName: "三"
        },
        computed: {
          fullName: {
            get() {
              return this.firstName + "-" + this.lastName;
            }
          }
        }
      });
    </script>
  </body>
</html>

6.2 computed计算属性

计算属性:

  • 定义:要用的属性不存在,要通过已有属性计算得来,计算属性最终会出现在vm上,直接读取使用即可
  • 原理:底层借助了Objcet.defineproperty方法提供的gettersetter
  • 优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
  • 计算属性中的getset函数:
    • 使用计算属性时,初次读取会执行一次get函数,当依赖的数据发生改变时也会再次调用get函数
    • 修改计算属性时,会调用set函数
  • 当计算属性中,只有get函数时,可以简写计算属性

1.计算属性中的getset函数的使用

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      姓:<input type="text" v-model="firstName" /> <br />
      名:<input type="text" v-model="lastName" /> <br />
      全名:<span>{{fullName}}</span>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          firstName: "张",
          lastName: "三"
        },
        computed: {
          fullName: {
            // 使用fullName时,返回全名
            get() {
              console.log("get被调用了");
              return this.firstName + "-" + this.lastName;
            },
            // 修改fullName时,修改firstName和lastName属性
            set(value) {
              console.log("set被调用了");
              arr = value.split("-");
              this.firstName = arr[0];
              this.lastName = arr[1];
            }
          }
        }
      });
    </script>
  </body>
</html>

运行结果:
由于模板中使用了fullName属性,先调用了一次get,然后在输入修改名后,fullName依赖的lastName属性发生了改变,再次调用了get;

在这里插入图片描述
控制台中输入vm.fullName='张-三丰',fullName属性被修改,调用了set,又因为set函数修改了fullName所依赖的firstName 和lastName,所以再次触发调用了get

在这里插入图片描述

2.简写计算属性

当计算属性中,只有get函数时,代码可以进行如下简写:

// 普通写法
computed: {
  fullName: {
    get() {
      return this.firstName + "-" + this.lastName;
    }
  }
}
// 简写
computed: {
  fullName() {
    return this.firstName + "-" + this.lastName;
  }
}

7. 监视属性

7.1 天气案例

要求:点击按钮时,天气在炎热和凉爽之间切换,初始值默认为炎热。

实现如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <h1>今天天气很{{info}}</h1>
      <button @click="changeWeather">点击切换天气</button>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          isHot: true // 默认天气为炎热
        },
        computed: {
          info() {
            return this.isHot ? "炎热" : "凉爽";
          }
        },
        methods: {
          changeWeather() {
            this.isHot = !this.isHot; // 每次调用方法时,isHot取反
          }
        }
      });
    </script>
  </body>
</html>

注:由于methods中的changeWeather方法逻辑简单,因此也可以将methods去掉,并把@click="changeWeather"改为@click="isHot = !isHot",这样就简化了代码。

运行结果:点击按钮,天气在炎热和凉爽之间切换

在这里插入图片描述

7.2 watch监视属性

监视属性watch

  • 监视的属性必须存在,才能进行监视
  • 当被监视的属性变化时, handler回调函数自动调用
  • 监视属性有两种写法,一是通过watch属性配置,二是通过vm.$watch()方法
  • 深度监视属性:
    • 当被监视的属性有多层结构时,默认不监测对象内部值的改变(一层)
    • 通过配置deep:true可以监测对象内部值改变(多层)
  • 当监视属性中,只有handler函数时,可以监视计算属性

1.监视属性的基本用法(通过watch属性配置)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <h1>今天天气很{{info}}</h1>
      <button @click="isHot = !isHot">点击切换天气</button>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          isHot: true // 默认天气为炎热
        },
        computed: {
          info() {
            return this.isHot ? "炎热" : "凉爽";
          }
        },
        methods: {
          changeWeather() {
            this.isHot = !this.isHot; // 每次调用方法时,isHot取反
          }
        },
        watch: {
          // 当isHot属性发生变化时,会调用handler函数
          isHot: {
            immediate: true, //初始化时会调用一下handler函数
            handler(newValue, oldValue) {
              console.log(`isHot被修改,原来的值是${oldValue},现在是${newValue}`);
            }
          }
        }
      });
    </script>
  </body>
</html>

点击按钮,运行结果如下:

在这里插入图片描述

2.可以通过vm.$watch()方法,进行属性监视

// watch属性配置的写法
watch: {
  isHot: {
    immediate: true, 
    handler(newValue, oldValue) {
      console.log(`isHot被修改,原来的值是${oldValue},现在是${newValue}`);
    }
  }
}
// vm.$watch()的写法
vm.$watch("isHot", {
  immediate: true, 
  handler(newValue, oldValue) {
    console.log(`isHot被修改,原来的值是${oldValue},现在是${newValue}`);
  }
});

3.deep:true 用于深度监视属性

  • 深度监视单个属性:监视的属性写为"numbers.a",表示监视numbers属性中的a属性
  • 深度监视所有属性: 监视的属性写为numbers,再配置deep:true,表示监视numbers属性中的所有属性
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <h3>a的值是:{{numbers.a}}</h3>
      <button @click="numbers.a++">点我让a+1</button>
      <h3>b的值是:{{numbers.b}}</h3>
      <button @click="numbers.b++">点我让b+1</button>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          // numbers属性有多层结构
          numbers: {
            a: 1,
            b: 1,
            c: {
              d: {
                e: 100
              }
            }
          }
        },
        watch: {
          //监视多级结构中某个属性的变化
          "numbers.a": {
            handler() {
              console.log("a发生了改变");
            }
          },
          //监视多级结构中所有属性的变化
          numbers: {
            deep: true,
            handler() {
              console.log("numbers发生了改变");
            }
          }
        }
      });
    </script>
  </body>
</html>

分别点击上下两个按钮,使a、b值增加,运行结果如下:

在这里插入图片描述

4.简写监视属性

当监视属性中,只有handler函数时,代码可以进行如下简写:

// 普通写法
watch: {
  isHot: {
    handler(newValue, oldValue) {
      console.log(`isHot被修改,原来的值是${oldValue},现在是${newValue}`);
    }
  }
}
// watch属性配置简写
watch: {
  isHot(newValue, oldValue) {
    console.log(`isHot被修改,原来的值是${oldValue},现在是${newValue}`);
  }
}

// vm.$watch()方法简写
vm.$watch("isHot", function (newValue, oldValue) {
  console.log(`isHot被修改,原来的值是${oldValue},现在是${newValue}`);
});

5.计算属性与监视属性的区别

computedwatch之间的区别:

  • computed能完成的功能,watch都可以完成
  • watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
  • computed的写法更加的简洁,因此能用 computed完成的功能,不要用watch完成

注:Vue中有关函数的两个重要的小原则:
1.所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象
2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。

8. 绑定样式

8.1 绑定class样式

绑定class样式有三种方式,分别适用于不同的场景:写法为:class="xxx"

  • 字符串写法:适用于样式的类名不确定,需要动态指定
  • 对象写法:适用于要绑定的样式个数确定、名字也确定,但要动态决定用不用
  • 数组写法:适用于要绑定的样式个数不确定、名字也不确定

1.绑定class样式,字符串写法:适用于样式的类名不确定,需要动态指定

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
    <style>
      /* basic为固定样式,happy和sad为需要动态绑定的样式 */
      .basic {
        width: 200px;
        height: 100px;
        border: 1px solid black;
      }

      /* 快乐情绪 */
      .happy {
        border: 4px solid red;
        background-color: orangered;
        background: linear-gradient(30deg, yellow, pink, orange, yellow);
      }

      /* 悲伤情绪 */
      .sad {
        border: 4px dashed rgb(2, 197, 2);
        background-color: gray;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <!-- 字符串写法,适用于:样式的类名不确定,需要动态指定 -->
      <div class="basic" :class="mood"></div>
      <br />
      <button @click="changeMood">点击切换情绪</button>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          mood: "happy" // 属性值为字符串
        },
        methods: {
          // 改变情绪
          changeMood() {
            if (this.mood == "happy") {
              this.mood = "sad";
            } else {
              this.mood = "happy";
            }
          }
        }
      });
    </script>
  </body>
</html>

运行结果:点击按钮,盒子样式发生改变(盒子的样式是动态的)

在这里插入图片描述在这里插入图片描述

2.绑定class样式,对象写法:适用于要绑定的样式个数确定、名字也确定,但要动态决定用不用

动态切换单个 class样式:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
    <style>
      .basic {
        width: 200px;
        height: 100px;
        border: 1px solid black;
      }

      .a1 {
        background-color: yellowgreen;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <!-- 对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
      <div class="basic" :class="{a1:isActive}">归海一刀</div>
      <button @click="isActive=!isActive">添加/删除样式</button>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          isActive: true
        }
      });
    </script>
  </body>
</html>

运行结果:点击按钮,选择添加或者删除样式.

在这里插入图片描述

动态切换多个 class样式:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
    <style>
      .basic {
        width: 200px;
        height: 100px;
        border: 1px solid black;
      }

      .a1 {
        background-color: yellowgreen;
      }

      .a2 {
        font-size: 30px;
        text-shadow: 2px 2px 10px red;
      }

      .a3 {
        border-radius: 20px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <!-- 对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
      <div class="basic" :class="classObj">归海一刀</div>
      <button @click="classObj.a1=!classObj.a1">添加/删除样式a1</button>
      <button @click="classObj.a2=!classObj.a2">添加/删除样式a2</button>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          classObj: {
            a1: false,
            a2: false
          }
        }
      });
    </script>
  </body>
</html>

运行结果:点击按钮,选择添加或者删除样式

在这里插入图片描述

3.绑定class样式,数组写法:适用于要绑定的样式个数不确定、名字也不确定

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
    <style>
      .basic {
        width: 200px;
        height: 100px;
        border: 1px solid black;
      }

      .a1 {
        background-color: yellowgreen;
      }

      .a2 {
        font-size: 30px;
        text-shadow: 2px 2px 10px red;
      }

      .a3 {
        border-radius: 20px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <!-- 数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
      <div class="basic" :class="classArr">归海一刀</div>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          classArr: ["a1", "a2"] // 属性值为数组
        }
      });
    </script>
  </body>
</html>

运行结果:可以通过对数组进行操作,从而对样式进行添加和删除

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

8.2 绑定style样式

绑定style样式有两种方式:

  • 对象写法::style="{fontSize: xxx}"其中xxx是动态值
  • 数组写法::style="[a,b]"其中a、b是样式对象

1.绑定style样式,对象写法

注:在style样式绑定中,CSS中的样式名要改为小驼峰写法,如:font-size 要改为 fontSize

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
    <style>
      .basic {
        width: 200px;
        height: 100px;
        border: 1px solid black;
        margin-bottom: 20px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <!-- 绑定style样式,对象写法 -->
      <div class="basic" :style="{fontSize: `${fsize}px`}">归海一刀</div>
      <div class="basic" :style="styleObj">归海一刀</div>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          fsize: 30,
          styleObj: {
            // 小驼峰写法
            fontSize: "30px",
            color: "red",
            backgroundColor: "orange"
          }
        }
      });
    </script>
  </body>
</html>

运行结果:

在这里插入图片描述

2.绑定style样式,数组写法

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
    <style>
      .basic {
        width: 200px;
        height: 100px;
        border: 1px solid black;
        margin-bottom: 20px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <!-- 绑定style样式,数组写法 -->
      <div class="basic" :style="styleArr">归海一刀</div>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          styleArr: [
            {
              fontSize: "30px",
              color: "red"
            },
            {
              backgroundColor: "orange"
            }
          ]
        }
      });
    </script>
  </body>
</html>

运行结果:

在这里插入图片描述

9. 条件渲染

9.1 v-show

v-show

  • 写法:v-show="表达式"
  • 适用于:切换频率较高的场景
  • 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉(设置为display:none
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <h1 v-show="isActive">Hello World</h1>
      <button @click="isActive=!isActive">显示/隐藏元素</button>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          isActive: true
        }
      });
    </script>
  </body>
</html>

运行结果:点击按钮之后,元素被隐藏,再查看DOM结构,发现元素样式上多了个style="display: none;"

在这里插入图片描述
在这里插入图片描述

9.2 v-if、v-else与v-else-if

v-if

  • 写法:v-if="表达式"v-else="表达式"v-else-if="表达式"
  • 适用于:切换频率较低的场景
  • 特点:不展示的DOM元素直接被移除

1.使用v-if会把DOM元素直接被移除

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <h1 v-if="isActive">Hello World</h1>
      <button @click="isActive=!isActive">显示/隐藏元素</button>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          isActive: true
        }
      });
    </script>
  </body>
</html>

运行结果:点击按钮之后,元素被隐藏,再查看DOM结构,发现元素直接被移除了

在这里插入图片描述

2.v-ifv-elsev-else-if配合使用

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <h2>当前n的值是{{n}}</h2>
      <button @click="n++">点击n+1</button>

      <h3 v-if="n===1">Angular</h3>
      <h3 v-else-if="n===2">React</h3>
      <h3 v-else-if="n===3">Vue</h3>
      <h3 v-else>不是前端三大框架</h3>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          n: 0
        }
      });
    </script>
  </body>
</html>

运行结果:点击按钮增加n的值,当n达到对应值时,分别展示对应的元素
在这里插入图片描述

注:v-if可以和v-else-ifv-else一起使用,但要求结构不能被“打断”

打断的结构示例如下:

<h3 v-if="n===1">Angular</h3>
<h3 v-else-if="n===2">React</h3>
<h3>打断结构</h3> 
<h3 v-else-if="n===3">Vue</h3>
<h3 v-else>不是前端三大框架</h3>

3.v-iftemplate的配合使用:当几个v-if的条件一样时,可以配合template,将多个条件简写为一个

注:使用template标签,不会影响结构,而且只能和v-if配合使用,不能和v-show配合

<!-- 简写前 -->
<h3 v-if="n===1">Angular</h3>
<h3 v-if="n===1">React</h3>
<h3 v-if="n===1">Vue</h3>
<!-- 简写后 -->
<template v-if="n===1">
  <h3>Angular</h3>
  <h3>React</h3>
  <h3>Vue</h3>
</template>

10. 列表渲染

10.1 v-for渲染列表数据

v-for指令:用于展示列表数据

语法:v-for="(item, index) in xxx" :key="yyy"

  • xxx为可遍历对象,包括数组、对象、字符串等
  • item为可遍历对象的每一项数据,index为每一项数据的索引值
  • key属性为每项数据的唯一标识

1.使用v-for遍历数组(常用)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- 遍历数组 -->
      <ul>
        <!-- p为数组成员,index为数组下标 -->
        <li v-for="(p,index) in persons" :key="index">{{index}}:{{p.name}}-{{p.age}}</li>
      </ul>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          // id为每项数据的唯一标识
          persons: [
            { id: "001", name: "张三", age: 18 },
            { id: "002", name: "李四", age: 19 },
            { id: "003", name: "王五", age: 20 }
          ]
        }
      });
    </script>
  </body>
</html>

注:由于数组下标indexid都是唯一标识,而key只要作为唯一标识就可以,因此以上代码中的:key="index"也可以改为:key="p.id"

运行结果:

在这里插入图片描述

2.使用v-for遍历对象(常用)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- 遍历对象 -->
      <ul>
        <!-- v为对象的属性值(value),k为对象的属性名(key) -->
        <li v-for="(v,k) in car" :key="k">{{k}}-{{v}}</li>
      </ul>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          // 对象是一个键值对,属性名就是键(key),可以作为唯一标识
          car: {
            name: "奥迪A6L",
            price: "70万",
            color: "黑色"
          }
        }
      });
    </script>
  </body>
</html>

运行结果:

在这里插入图片描述

3.使用v-for遍历字符串(用的少)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- 遍历字符串 -->
      <ul>
        <!-- item为每个字符,index为字符串下标 -->
        <li v-for="(item,index) in str" :key="index">{{index}}-{{item}}</li>
      </ul>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          str: "Hello"
        }
      });
    </script>
  </body>
</html>

运行结果:

在这里插入图片描述

4.使用v-for遍历指定的次数(用的少)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- 遍历指定的次数 -->
      <ul>
        <!-- 遍历5次 -->
        <li v-for="(number,index) in 5" :key="index">{{number}}-{{str}}</li>
      </ul>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          str: "Hello World"
        }
      });
    </script>
  </body>
</html>

运行结果:

在这里插入图片描述

10.2 key的作用与原理

1.key作为数据的唯一标志,选择原则遵循如下:

  • 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
  • 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。

2.用index作为key可能会引发的问题:

  • 若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
  • 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- 遍历数组 -->
      <button @click.once="add">添加一个老刘</button>
      <ul>
        <!-- p为数组成员,index为数组下标 -->
        <li v-for="(p,index) in persons" :key="index">
          {{p.name}}-{{p.age}} <input type="text" />
        </li>
      </ul>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          // id为每项数据的唯一标识
          persons: [
            { id: "001", name: "张三", age: 18 },
            { id: "002", name: "李四", age: 19 },
            { id: "003", name: "王五", age: 20 }
          ]
        },
        methods: {
          // 对数据进行逆序添加,破坏原有顺序
          add() {
            const p = { id: "004", name: "老刘", age: 40 };
            this.persons.unshift(p);
          }
        }
      });
    </script>
  </body>
</html>

运行结果如下:在输入框中输入内容,然后再点击按钮,发现输入框中的数据显示有问题

在这里插入图片描述

在这里插入图片描述

当把以上代码的:key="index"改为:key="p.id",数据则显示正常了

在这里插入图片描述

原理如下:

  • 虚拟DOM中key的作用:key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】
  • 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
    • 旧虚拟DOM中找到了与新虚拟DOM相同的key:若虚拟DOM中内容没变, 直接使用之前的真实DOM;若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
    • 旧虚拟DOM中未找到与新虚拟DOM相同的key:创建新的真实DOM,随后渲染到到页面

在这里插入图片描述

在这里插入图片描述

10.3 列表过滤

列表过滤:对列表的数据进行筛选,筛选后的数据再渲染到页面中。

1.使用监视属性,实现列表过滤

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      人员列表:<input type="text" placeholder="请输入名字" v-model="keyWord" />
      <ul>
        <!-- 展示过滤后的数组 -->
        <li v-for="(p,index) of filPerons" :key="index">
          {{p.name}}-{{p.age}}-{{p.sex}}
        </li>
      </ul>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          keyWord: "", // 关键词,用于模糊搜索
          persons: [
            { id: "001", name: "马冬梅", age: 19, sex: "女" },
            { id: "002", name: "周冬雨", age: 20, sex: "女" },
            { id: "003", name: "周杰伦", age: 21, sex: "男" },
            { id: "004", name: "温兆伦", age: 22, sex: "男" }
          ],
          filPerons: [] // 用于存放过滤后的数组,避免改动原数组
        },
        watch: {
          // 监视keyWord属性,当keyWord属性值发生变化时,调用handler函数进行模糊搜索
          keyWord: {
            immediate: true, // 初始化时调用handler,空字符串会匹配所有数据,首次展示为完整数据
            handler(val) {
              this.filPerons = this.persons.filter(p => {
                return p.name.includes(val); // 判断输入框中的值是否出现在姓名中
              });
            }
          }
        }
      });
    </script>
  </body>
</html>

运行结果:

在这里插入图片描述

在这里插入图片描述

2.使用计算属性,实现列表过滤

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      人员列表:<input type="text" placeholder="请输入名字" v-model="keyWord" />
      <ul>
        <!-- 展示过滤后的数组 -->
        <li v-for="(p,index) of filPerons" :key="index">
          {{p.name}}-{{p.age}}-{{p.sex}}
        </li>
      </ul>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          keyWord: "", // 关键词,用于模糊搜索
          persons: [
            { id: "001", name: "马冬梅", age: 19, sex: "女" },
            { id: "002", name: "周冬雨", age: 20, sex: "女" },
            { id: "003", name: "周杰伦", age: 21, sex: "男" },
            { id: "004", name: "温兆伦", age: 22, sex: "男" }
          ]
        },
        computed: {
          // 计算属性filPerons依赖于keyWord属性,当keyWord发生改变时,会重新计算属性
          filPerons() {
            return this.persons.filter(p => {
              return p.name.includes(this.keyWord);
            });
          }
        }
      });
    </script>
  </body>
</html>

注:使用computed还是watch的原则
1.computed 的写法更加的简洁(推荐使用),能用 computed 完成的功能,不要用 watch 完成
2.computed 无法完成的功能才用 watch 实现,例如:watch 可以进行异步操作

10.4 列表排序

列表排序:对列表数据进行排序,排序后的数据再渲染到页面中

  • 可以对完整的列表数据进行排序
  • 也可以配合列表过滤,对筛选后的列表数据进行排序。
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      人员列表:<input type="text" placeholder="请输入名字" v-model="keyWord" />
      <button @click="sortType = 2">年龄升序</button>
      <button @click="sortType = 1">年龄降序</button>
      <button @click="sortType = 0">原顺序</button>
      <ul>
        <!-- 展示过滤后的数组 -->
        <li v-for="(p,index) of filPerons" :key="index">
          {{p.name}}-{{p.age}}-{{p.sex}}
        </li>
      </ul>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          keyWord: "",
          sortType: 0, // 0原顺序、1降序、2升序
          persons: [
            { id: "001", name: "马冬梅", age: 30, sex: "女" },
            { id: "002", name: "周冬雨", age: 31, sex: "女" },
            { id: "003", name: "周杰伦", age: 18, sex: "男" },
            { id: "004", name: "温兆伦", age: 19, sex: "男" }
          ]
        },
        computed: {
          // 计算属性filPerons依赖于keyWord和sortType属性,依赖的这两个属性任何一个发生变化,都会引起重新计算属性
          filPerons() {
            // 对列表数据进行筛选
            const arr = this.persons.filter(p => {
              return p.name.includes(this.keyWord);
            });
            // 判断一下是否需要排序
            if (this.sortType) {
              arr.sort((p1, p2) => {
                return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age;
              });
            }
            return arr;
          }
        }
      });
    </script>
  </body>
</html>

运行结果:在输入框中输入关键词,可以对数据进行筛选,再点击按钮可以对筛选后的数据再进行排序

在这里插入图片描述

11. 收集表单数据

v-model用于收集表单数据,且在收集表单数据时,根据表单控件类型的不同,默认收集的值也不同。

v-model需要注意的点如下:

  • 若表单控件为文本框或密码框,即 <input type="text"/><input type="password"/>,则v-model收集的是value值,用户输入的就是value值。
  • 若表单控件为单选框,即<input type="radio"/>,则v-model收集的是value值,需要给标签配置value值。
  • 若表单控件为复选框,即<input type="checkbox"/>,则有以下两种情况:
    • 没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
    • 配置了input的value属性,当v-model的初始值是非数组时,那么收集的就是checked;是数组时,那么收集的的就是value组成的数组

注:v-model的三个修饰符
1.lazy:失去焦点时,再收集数据
2.number:输入字符串转为有效的数字
3.trim:清除文本框首尾空格

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- 阻止表单默认跳转行为 -->
      <form @submit.prevent="demo">
        账号:<input type="text" v-model.trim="userInfo.account" /> <br /><br />
        密码:<input type="password" v-model="userInfo.password" /> <br /><br />
        年龄:<input type="number" v-model.number="userInfo.age" /> <br /><br />
        性别: 男<input type="radio" name="sex" v-model="userInfo.sex" value="male" /><input type="radio" name="sex" v-model="userInfo.sex" value="female" /> <br /><br />
        爱好: 学习<input type="checkbox" v-model="userInfo.hobby" value="study" /> 打游戏<input type="checkbox" v-model="userInfo.hobby" value="game" /> 吃饭<input
          type="checkbox"
          v-model="userInfo.hobby"
          value="eat"
        />
        <br /><br />
        所属校区
        <select v-model="userInfo.city">
          <option value="">请选择校区</option>
          <option value="beijing">北京</option>
          <option value="shanghai">上海</option>
          <option value="shenzhen">深圳</option>
          <option value="wuhan">武汉</option>
        </select>
        <br /><br />
        其他信息:
        <textarea v-model.lazy="userInfo.other"></textarea> <br /><br />
        <input type="checkbox" v-model="userInfo.agree" />阅读并接受<a href="http://www.baidu.com">《用户协议》</a>
        <button>提交</button>
      </form>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          userInfo: {
            account: "",
            password: "",
            age: 18,
            sex: "female",
            hobby: [],
            city: "beijing",
            other: "",
            agree: ""
          }
        },
        methods: {
          demo() {
            console.log(JSON.stringify(this.userInfo));
          }
        }
      });
    </script>
  </body>
</html>

运行结果如下:输入内容,勾选单选和复选框等控件,再点击提交按钮,可以看到控制台打印了表单提交的信息。

在这里插入图片描述

12. 内置指令

目前已经学过的内置指令有:v-bindv-modelv-onv-showv-ifv-elsev-for

12.1 v-text 解析普通文本

v-text指令:用于解析普通文本,向其所在的节点中渲染文本内容。

v-text与插值语法的区别:v-text会替换掉节点中的内容,而{{xxx}}则不会,因此{{xxx}}更加灵活,用的更多。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- 插值语法相对v-text更加灵活 -->
      <div>Hello, {{name}}</div>
      <!-- v-text会替换掉节点中的内容 -->
      <div v-text="name">Hello</div>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          name: "Jack"
        }
      });
    </script>
  </body>
</html>

运行结果:

在这里插入图片描述

12.2 v-html 解析html标签

v-html指令:用于解析html标签,向其所在的节点中渲染包含html结构的内容。

v-html与插值语法的区别:v-html会替换掉节点中的内容,而{{xxx}}则不会。

注:v-html有安全性问题
1.在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击
2.一定要在可信的内容上使用v-html,永不要用在用户提交的内容上(如表单的输入框等)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- v-text只能解析普通文本,无法解析html标签 -->
      <div v-text="str"></div>
      <!-- v-html可以解析html标签 -->
      <div v-html="str"></div>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          str: `<h3>Hello World</h3>`
        }
      });
    </script>
  </body>
</html>

运行结果:

在这里插入图片描述

12.3 v-cloak 隐藏模板

v-cloak指令:

  • 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性
  • 该指令可以配合css属性选择器,解决网速慢时页面展示出{{xxx}}的问题
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
    <style>
      /* 使用该属性的元素被隐藏 */
      [v-cloak] {
        display: none;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <!-- 
        初始时,v-cloak属性存在,但由于属性选择器的作用,会将h2标签隐藏;
        等到vue接管容器后,会移除掉h2上的v-cloak属性,此时会显示h2标签
       -->
      <h2 v-cloak>{{name}}</h2>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          name: "Jack"
        }
      });
    </script>
  </body>
</html>

12.4 v-once 仅渲染一次

v-once指令:

  • v-once所在节点在初次动态渲染后,就视为静态内容
  • 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <h2 v-once>初始化的n值是:{{n}}</h2>
      <h2>当前的n值是:{{n}}</h2>
      <button @click="n++">点我n+1</button>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          n: 1
        }
      });
    </script>
  </body>
</html>

运行结果:点击按钮,只有下面的n值发生变化,而上面的n值不变化

在这里插入图片描述

12.5 v-pre 跳过元素编译

v-pre指令:

  • 跳过其所在节点的编译过程
  • 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- 该标签未用到指令语法和插值语法,可以使用v-pre加快编译 -->
      <h2 v-pre>Vue其实很简单</h2>
      <h2>当前的n值是:{{n}}</h2>
      <button @click="n++">点我n+1</button>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      const vm = new Vue({
        el: "#app",
        data: {
          n: 1
        }
      });
    </script>
  </body>
</html>

13. 自定义指令

13.1 函数式和对象式

自定义指令有两种定义方式:

  • 函数式定义
  • 对象式定义

1.函数式定义

例:定义一个 v-big 指令,和 v-text 功能类似,但会把绑定的数值放大10倍

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <h2>当前的n值是:<span v-text="n"></span></h2>
      <h2>放大10倍后的n值是:<span v-big="n"></span></h2>
      <button @click="n++">点我n+1</button>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      new Vue({
        el: "#app",
        data: {
          n: 1,
        },
        // 自定义指令
        directives: {
          // element表示真实的dom元素, binding表示绑定的对象
          big(element, binding) {
            element.innerText = binding.value * 10;
          },
        },
      });
    </script>
  </body>
</html>

big函数被调用的条件:
1.指令与元素成功绑定时(一上来)
2.指令所在的模板被重新解析时

运行结果:点击按钮,上面的n值+1,下面的n值+1后乘以10倍

在这里插入图片描述

2.对象式定义

配置对象中常用的三个回调:

  • bind:指令与元素成功绑定时调用
  • inserted:指令所在元素被插入页面时调用
  • update:指令所在模板结构被重新解析时调用

对象式定义与函数式定义的区别:函数式定义写法等于对象式定义中的 bind + update 回调函数,而对象式定义还有inserted回调函数,可以实现函数式定义无法实现的细节操控。

例:定义一个 v-fbind 指令,和 v-bind 功能类似,但可以让其所绑定的input元素默认获取焦点

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <h2>当前的n值是:<span v-text="n"></span></h2>
      <button @click="n++">点我n+1</button>
      <br />
      <input type="text" v-fbind:value="n" />
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      new Vue({
        el: "#app",
        data: {
          n: 1,
        },
        // 自定义指令
        directives: {
          fbind: {
            //指令与元素成功绑定时(一上来)
            bind(element, binding) {
              element.value = binding.value;
            },
            //指令所在元素被插入页面时
            inserted(element, binding) {
              element.focus();
            },
            //指令所在的模板被重新解析时
            update(element, binding) {
              element.value = binding.value;
            },
          },
        },
      });
    </script>
  </body>
</html>

运行结果:刷新页面后,文本框默认获取焦点,点击按钮后失去焦点

在这里插入图片描述

13.2 局部指令和全局指令

自定义指令分为两类:

  • 局部指令
  • 全局指令

1.局部指令写法

// 函数式定义
 new Vue({															
 	directives:{指令名:回调函数}   
 }) 	
 
// 对象式定义	
 new Vue({															
 	directives:{指令名:配置对象}   
 }) 		
new Vue({
  // 局部自定义指令
  directives: {
    // 函数式定义
    big(element, binding) {
      element.innerText = binding.value * 10;
    },
    // 对象式定义
    fbind: {
      bind(element, binding) {
        element.value = binding.value;
      },
      inserted(element, binding) {
        element.focus();
      },
      update(element, binding) {
        element.value = binding.value;
      },
    },
  },
});

2.全局指令写法

// 函数式定义
Vue.directive(指令名, 回调函数)
// 对象式定义
Vue.directive(指令名, 配置对象)
 // 函数式定义
 Vue.directive("big", function (element, binding) {
   element.value = binding.value;
 });

 // 对象式定义
 Vue.directive("fbind", {
   bind(element, binding) {
     element.value = binding.value;
   },
   inserted(element, binding) {
     element.focus();
   },
   update(element, binding) {
     element.value = binding.value;
   },
 });

自定义指令注意事项:
1.指令定义时不加v-,但使用时要加v-;
2.指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。

new Vue({
  directives: {
    "big-number"(element, binding) {
      element.innerText = binding.value * 10;
    },
  },
});

14. Vue的生命周期

14.1 生命周期概念

Vue的生命周期:又称生命周期回调函数、生命周期函数、生命周期钩子,是Vue在关键时刻帮我们调用的一些特殊名称的函数。

生命周期函数中的this指向是vm 或 组件实例对象

vue的生命周期分为以下四个流程:每个流程对应两个函数,其中最重要的是mountedbeforeDestroy

  • 创建流程:
    • beforeCreate:此时无法通过vm访问到data中的数据和methods中的方法。
    • created:此时可以通过vm访问到data中的数据和methods中配置的方法。
  • 挂载流程:
    • beforeMount:此时页面呈现的是未经Vue编译的DOM结构,所有对DOM的操作,最终都不奏效。
    • mounted:此时页面呈现的是经过Vue编译的DOM,对DOM的操作均有效(尽可能避免)。至此初始化过程结束,一般在此阶段:开启定时器、发送网络请求、订阅消息以及绑定自定义事件等初始化操作
  • 更新流程:
    • beforeUpdate:此时数据是新的,但页面是旧的,即页面尚未和数据保持同步。
    • updated:此时数据是新的,页面也是新的,即页面尚未和数据保持同步。
  • 销毁流程
    • beforeDestroy:此时vm中所有的data、methods和指令等等,都处于可用状态,马上要执行销毁过程。一般在此阶段:关闭定时器、取消订阅消息以及解绑自定义事件等收尾操作
    • destroyed:此时vm已完成销毁。

Vue的生命周期流程图:
在这里插入图片描述

14.2 生命周期演示

1.生命周期钩子函数的调用顺序

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <!-- 准备好一个容器-->
    <div id="app">
      <h2>当前的n值是:{{n}}</h2>
      <button @click="add">点我n+1</button>
      <button @click="bye">点我销毁vm</button>
    </div>

    <script>
      Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示

      new Vue({
        el: "#app",
        data: {
          n: 1,
        },
        methods: {
          add() {
            this.n++;
          },
          bye() {
            this.$destroy();
          },
        },
        beforeCreate() {
          console.log("beforeCreate");
        },
        created() {
          console.log("created");
        },
        beforeMount() {
          console.log("beforeMount");
        },
        mounted() {
          console.log("mounted");
        },
        beforeUpdate() {
          console.log("beforeUpdate");
        },
        updated() {
          console.log("updated");
        },
        beforeDestroy() {
          console.log("beforeDestroy");
        },
        destroyed() {
          console.log("destroyed");
        },
      });
    </script>
  </body>
</html>

在这里插入图片描述

2.生命周期的常用应用场景

常用的生命周期钩子:

  • mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等(初始化操作)
  • beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等(收尾操作)

关于销毁Vue实例:

  • 销毁后借助Vue开发者工具看不到任何信息
  • 销毁后自定义事件会失效,但原生DOM事件依然有效
  • 一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <!-- 准备好一个容器-->
    <div id="app">
      <h2 :style="{opacity}">欢迎学习Vue</h2>
      <button @click="opacity = 1">透明度设置为1</button>
      <button @click="stop">点我停止变换</button>
    </div>

    <script>
      Vue.config.productionTip = false; //阻止 vue 在启动时生成生产提示。

      new Vue({
        el: "#app",
        data: {
          opacity: 1,
        },
        methods: {
          stop() {
            this.$destroy(); // 销毁vm
          },
        },
        //Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
        mounted() {
          // 初始化操作:启动定时器
          console.log("mounted", this);
          this.timer = setInterval(() => {
            console.log("setInterval");
            this.opacity -= 0.01;
            if (this.opacity <= 0) this.opacity = 1;
          }, 16);
        },
        beforeDestroy() {
          // 收尾操作:清除定时器
          clearInterval(this.timer);
        },
      });
    </script>
  </body>
</html>

运行结果:
未点击按钮停止变换前,点击设置透明度有效;点击按钮停止变换后,再点击设置透明度则无效,因为vm被销毁了。
在这里插入图片描述

15. Vue的组件化编程

15.1 模块与组件

1.模块与模块化:

  • 模块:一个模块就是一个js文件,向外提供特定功能的 js 程序,作用是可以复用 js、简化 js 的编写、提高 js 运行效率。
  • 模块化:当应用中的 js 都以模块来编写的, 那这个应用就是一个模块化的应用。

2.组件与组件化:

  • 组件:用来实现局部(特定)功能效果的代码集合(html/css/js/image……),作用是复用编码、简化项目编码、提高运行效率。
  • 组件化:当应用中的功能都是多组件的方式来编写的, 那这个应用就是一个组件化的应用。

传统方式编写应用:

在这里插入图片描述

组件化方式编写应用:

在这里插入图片描述

15.2 非单文件组件

组件的分类:

  • 非单文件组件:模板编写没有提示, 没有构建过程,无法将 ES6 转换,不支持组件的 CSS,真正开发中几乎不用。
  • 单文件组件:单文件组件为 .vue 文件,由<template> 模板页面、<script> JS模块对象和 <style> 样式组成。

虽然非单文件组件在开发中几乎不用,但是要想学会单文件组件( .vue) ,必须先从非单文件组件开始入手。

15.2.1 组件的基本使用

Vue中使用组件的三大步骤:

  • 创建组件
  • 注册组件
  • 使用组件

1.创建组件:

  • 使用 Vue.extend(options) 创建组件
  • Vue.extend(options)new Vue(options) 时传入的 options 几乎一样,但有区别如下:
    • el 不能写,因为最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
    • data 必须写成函数,为了避免组件被复用时,数据存在引用关系

注:使用template可以配置组件结构

2.注册组件

  • 局部注册:靠 new Vue 的时候传入 components 配置项
  • 全局注册:靠 Vue.component('组件名',组件)

3.使用组件:编写组件标签,使用组件

组件示例如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <!-- 准备好一个容器-->
    <div id="app">
      <!-- 第三步:使用student组件 -->
      <student></student>
    </div>

    <script>
      Vue.config.productionTip = false; //阻止 vue 在启动时生成生产提示。

      // 第一步:创建student组件
      const student = Vue.extend({
        template: `
          <div>
            <h2>学生姓名:{{name}}</h2>
            <h2>学生年龄:{{age}}</h2>
            <button @click="showName">点我提示姓名</button>	
          </div>
        `,
        // 组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器
        data() {
          return {
            name: "张三",
            age: 18,
          };
        },
        methods: {
          showName() {
            alert(this.name);
          },
        },
      });

      // 第二步:注册student组件(局部注册)
      new Vue({
        el: "#app",
        components: {
          student,
        },
      });
    </script>
  </body>
</html>

以上局部注册也可改为全局注册:

// 第二步:注册student组件
Vue.component("student", student);

new Vue({
  el: "#app",
});

15.2.2 组件的注意事项

1.组件名的取名:

  • 组件名由一个单词组成:
    • 第一种写法(首字母小写):school
    • 第二种写法(首字母大写):School(推荐)
  • 组件名由多个单词组成:
    • 第一种写法(kebab-case命名):my-school
    • 第二种写法(CamelCase命名):MySchool (推荐,且该写法需要Vue脚手架支持)

2.组件标签的两种写法

  • 双标签:<school></school>
  • 单标签:school/>

备注:不用使用脚手架时,<school/> 会导致后续组件不能渲染

3.Vue.extend()可以简写:const school = Vue.extend(options) ,可简写为 const school = options

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <!-- 准备好一个容器-->
    <div id="app">
      <!-- 第三步:使用student组件 -->
      <Student />
    </div>

    <script>
      Vue.config.productionTip = false; //阻止 vue 在启动时生成生产提示。

      // 第一步:创建student组件,简写省略了Vue.extend()
      const Student = {
        template: `
        <div>
          <h2>学生姓名:{{name}}</h2>
          <h2>学生年龄:{{age}}</h2>
          <button @click="showName">点我提示姓名</button>	
        </div>
        `,
        // 组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器
        data() {
          return {
            name: "张三",
            age: 18,
          };
        },
        methods: {
          showName() {
            alert(this.name);
          },
        },
      };

      // 第二步:注册student组件
      new Vue({
        el: "#app",
        components: {
          Student,
        },
      });
    </script>
  </body>
</html>

15.2.3 组件的嵌套

组件的嵌套:
在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <!-- 准备好一个容器-->
    <div id="root"></div>

    <script>
      Vue.config.productionTip = false; //阻止 vue 在启动时生成生产提示。

      //定义student组件
      const student = Vue.extend({
        name: "student",
        template: `
        <div>
          <h2>学生姓名:{{name}}</h2>	
          <h2>学生年龄:{{age}}</h2>	
        </div>
        `,
        data() {
          return {
            name: "张三",
            age: 18,
          };
        },
      });

      //定义school组件
      const school = Vue.extend({
        name: "school",
        template: `
		<div>
			<h2>学校名称:{{name}}</h2>	
			<h2>学校地址:{{address}}</h2>	
			<student></student>
		</div>
		`,
        data() {
          return {
            name: "北京大学",
            address: "北京",
          };
        },
        //注册组件(局部),school中嵌套student
        components: {
          student,
        },
      });

      //定义hello组件
      const hello = Vue.extend({
        template: `<h1>{{msg}}</h1>`,
        data() {
          return {
            msg: "欢迎来到北京!",
          };
        },
      });

      //创建vm
      new Vue({
        template: `
        <div>
            <hello></hello>	
            <school></school>	
        </div>
        `,
        el: "#root",
        //注册组件(局部)
        components: {
          hello,
          school,
        },
      });
    </script>
  </body>
</html>

组件之间结构如下:

在这里插入图片描述

15.2.4 VueComponent

关于VueComponent:

1.school 组件本质是一个名为 VueComponent 的构造函数,且不是程序员定义的,是 Vue.extend 生成的

2.我们只需要编写 <school/><school></school>,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行了 new VueComponent(options)

3.特别注意,每次调用 Vue.extend,返回的都是一个全新的 VueComponent

4.关于this指向:

  • 组件配置中的this指向:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是 VueComponent 实例对象
  • new Vue(options)配置中的this指向:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是 Vue 实例对象

15.2.5 一个重要的内置关系

一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype,这个关系可以让VueComponent组件实例对象访问到 Vue原型上的属性、方法。

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="root"></div>

    <script>
      Vue.config.productionTip = false; //阻止 vue 在启动时生成生产提示。
      Vue.prototype.x = 99;

      //定义school组件
      const school = Vue.extend({
        name: "school",
        template: `
		<div>
			<h2>学校名称:{{name}}</h2>	
			<h2>学校地址:{{address}}</h2>	
			<button @click="showX">点我输出x</button>
		</div>
		`,
        data() {
          return {
            name: "北京大学",
            address: "北京",
          };
        },
        methods: {
          showX() {
            alert(this.x);
          },
        },
      });

      new Vue({
        template: `<school></school>`,
        el: "#root",
        components: { school },
      });
    </script>
  </body>
</html>

15.3 单文件组件

15.3.1 单文件组件的组成

单文件组件:即一个 .vue 文件,由以下三部分组成

  • <template> : 模板页面
  • <script> :JS模块对象
  • <style> : 样式

1.模板页面

<template>
页面模板
</template>

2.JS模块对象

<script>
  export default {
    data() {
      return {};
    },
    methods: {},
    computed: {},
    components: {},
  };
</script>

3.样式

<style>
样式定义
</style>

15.3.2 单文件组件的示例

本节仅展示代码和文件之间的关联逻辑,此处的代码无法直接运行,需要搭建脚手架环境以后才能运行。

示例:有以下文件结构,作为单文件组件的实际开发使用

在这里插入图片描述

文件之间关系如下:

  • index.html :主页面
  • main.js :程序入口
  • App.vue :汇总所有组件,此处包括School和Student组件
  • School.vue :学校组件
  • Student.vue :学生组件

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
  </head>
  <body>
    <!-- 准备一个容器 -->
    <div id="root"></div>
    <script type="text/javascript" src="../js/vue.js"></script>
    <script type="text/javascript" src="./main.js"></script>
  </body>
</html>

main.js

import App from './App.vue'

new Vue({
	el:'#root',
	template:`<App></App>`,
	components:{App},
})

App.vue

<template>
  <div>
    <School></School>
    <Student></Student>
  </div>
</template>

<script>
//引入组件
import School from "./School.vue";
import Student from "./Student.vue";

export default {
  name: "App",
  components: {
    School,
    Student,
  },
};
</script>

School.vue

<template>
	<div class="demo">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
		<button @click="showName">点我提示学校名</button>	
	</div>
</template>

<script>
	 export default {
		name:'School',
		data(){
			return {
				name:'北京大学',
				address:'北京'
			}
		},
		methods: {
			showName(){
				alert(this.name)
			}
		},
	}
</script>

<style>
	.demo{
		background-color: orange;
	}
</style>

Student.vue

<template>
	<div>
		<h2>学生姓名:{{name}}</h2>
		<h2>学生年龄:{{age}}</h2>
	</div>
</template>

<script>
	 export default {
		name:'Student',
		data(){
			return {
				name:'张三',
				age:18
			}
		}
	}
</script>

  • 23
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值