Vue.js 框架基础笔记

Vue.js 框架基础笔记

1. Vue.js 基本概念

1.1 遇见 Vue.js
1.1.1 为什么要学习 Vue.js
  • 组件化
    • 其中以 React 的组件化最为彻底
    • 甚至可以到函数级别的原子组件
    • 高度的组件化可以是我们的工程易于维护、易于组合拓展
  • 天然分层
    • jQuery 时代的代码大部分情况下是面条代码,耦合严重
    • 现代框架不管是 MVCMVP还是MVVM 模式都能帮助我们进行分层,代码解耦更易于读写
  • 生态
    • 现在主流前端框架都自带生态
    • 不管是数据流管理架构还是 UI 库都有成熟的解决方案
1.1.2 前端开发的复杂化
  • Vue (读音 /vjuː/,类似于 view),不要读错
  • Vue是一个渐进式的框架
    • 渐进式意味着你可以将Vue作为你应用的一部分嵌入其中,带来更丰富的交互体验
    • 如果你希望将更多的业务逻辑使用Vue实现,那么Vue的核心库以及其生态系统,比如Core+Vue-router+Vuex,也可以满足你各种各样的需求
1.1.3 Vue.js 特点
  • Vue有很多特点和Web开发中常见的高级功能
    • 解耦视图和数据
    • 可复用的组件
    • 前端路由技术
    • 状态管理
    • 虚拟DOM
1.1.4 Vue.js 核心概念
  • Vue 的核心概念之一:
    • 通过数据驱动界面更新, 无需操作DOM来更新界面
    • 使用Vue我们只需要关心如何获取数据, 如何处理数据, 如何编写业务逻辑代码
    • 我们只需要将处理好的数据交给Vue, Vue就会自动将数据渲染到模板中(界面上)
  • Vue的核心概念之二:
    • 组件化开发,我们可以将网页拆分成一个个独立的组件来编写
    • 将来再通过封装好的组件拼接成一个完整的网页
1.2 安装 Vue.js
1.2.1 直接 CDN 引入
  • 可以选择引入开发环境版本还是生产环境版本
<!-- 开发环境版本,包含了有帮助的命令行警告 --> 
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
1.2.2 下载和引入
1.2.3 NPM 安装
  • 在用 Vue 构建大型应用程序时,推荐使用 NPM 安装方式
  • npm install vue
1.2.4 CLI 和 NPM 结合使用
  • npm install --global vue-cli
1.3 体验 Vue.js
1.3.1 Hello Vue.js
  • 创建Vue对象的时候,传入了一些选项options:{}
    • {}中包含了el属性:
      • 该属性决定了这个Vue对象挂载到哪一个元素上
      • 我们这里是挂载到了idapp的元素上
    • {}中包含了data属性:该属性中通常会存储一些数据
      • 这些数据可以是我们直接定义出来的,比如像message这样
      • 也可能是来自网络,从服务器加载的
  • 浏览器执行代码的流程
    • 执行显示出对应的HTML
    • 执行创建Vue实例,并且对原HTML进行解析和修改
  • 并且,目前我们的代码是可以做到数据响应式
<div id="app">{{message}}</div>
<script src="../vue.js" charset="utf-8"></script>
<script type="text/javascript">
  // 编程范式:声明式编程
  const app = new Vue({
    el:'#app', // 用于挂载要管理的元素
    data:{ // 定义数据
      message:'hello Vue.js'
    }
  })
</script>
1.3.2 Vue 显示列表
  • 展示一个更加复杂的数据:数据列表
    • 比如我们现在从服务器请求过来一个数据列表
    • 希望展示到HTML
  • HTML代码中,使用v-for指令
  • 不需要在JavaScript代码中完成DOM的拼接相关操作
  • 更重要的是,它还是数据响应式
    • 也就是说,当我们数组中的数据发生改变时,界面会自动改变
<div id="app">
    <ul>
        <li v-for="item in movies">{{item}}</li>
    </ul>
</div>

<script src="../vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            message: '你好啊',
            movies: ['星际穿越', '大话西游', '少年派', '盗梦空间']
        }
    })
</script>
1.3.3 实现计数器
  • 新的属性:methods
    • 该属性用于在Vue对象中定义方法
  • 新的指令:@click
    • 该指令用于监听某个元素的点击事件
    • 并且需要指定当发生点击时,执行的方法(方法通常是methods中定义的方法)
<div id="app">
  <h1>计数器:{{count}}</h1>
  <button type="button" name="button" v-on:click='jia'>+</button>
  <button type="button" name="button" @click='jian'>-</button>
</div>
<script src="../vue.js" charset="utf-8"></script>
<script type="text/javascript">
  // 编程范式: 声明式编程
  const app = new Vue({
    el:'#app', // 用于挂载要管理的元素
    data:{ // 定义数据
      count:0
    },
    methods:{
      jia:function(){
        this.count++;
      },
      jian:function(){
        this.count--;
      }
    }
  })
</script>
1.4 MVVM 架构

在这里插入图片描述

1.4.1 data 和 Vue 对象的分离
  • 计数器的MVVM
    • 我们的计数器中就有严格的MVVM思想
      • View依然是我们的DOM
      • Model就是我们我们抽离出来的obj
      • ViewModel就是我们创建的Vue对象实例
    • 它们之间如何工作呢?
      • 首先ViewModel通过Data Bindingobj中的数据实时的在DOM中显示
      • 其次ViewModel通过DOM Listener来监听DOM事件,并且通过methods中的操作,来改变obj中的数据
1.4.2 Vue 中的 MVVM
  • View层:
    • 视图层
    • 在我们前端开发中,通常就是DOM
    • 主要的作用是给用户展示各种信息
    • 展示数据, 与用户交互
  • Model层:
    • 数据层
    • 数据可能是我们固定的死数据,更多的是来自我们服务器,从网络上请求下来的数据
    • 保存数据, 处理数据业务逻辑
  • ViewModel层:
    • 视图模型层
    • 视图模型层是ViewModel沟通的桥梁
    • 通过Data Binding,也就是数据绑定,将Model的数据改变状态实时的反应到View
    • 通过DOM Listener,也就是DOM监听,当DOM发生一些事件(点击、滚动、touch等)时,可以监听到,并在需要的情况下改变对应的Data
1.4.3 MVVM 设计模式特点
  • 支持数据的双向传递
    • 数据可以从 M -> VM -> V
    • 也可以从 V -> VM -> M
1.4.4 Vue 中 MVVM 的划分
  • Vue 其实是基于 MVVM 设计模式的
    • 被控制的区域: View
    • Vue实例对象 : View Model
    • 实例对象中的data: Model
1.4.5 创建 Vue 实例传入的 options
  • 我们在创建Vue实例的时候,传入了一个对象options
  • 这个options中可以包含哪些选项呢?
  • 目前掌握这些选项:
    • el:
      • 类型:string | HTMLElement
      • 作用
        • 决定之后Vue实例会管理哪一个DOM
        • 告诉Vue的实例对象, 将来需要控制界面上的哪个区域
    • data:
      • 类型:Object | Function (组件当中data必须是一个函数)
      • 作用
        • Vue实例对应的数据对象
        • 告诉Vue的实例对象, 被控制区域的数据是什么
    • methods:
      • 类型:{ [key: string]: Function }
      • 作用:定义属于Vue的一些方法,可以在其他地方调用,也可以在指令中使用
1.4.6 Vue 的生命周期

2. Vue 基础语法

2.1 插值语法
2.1.1 Mustache 语法
  • 通过插值表达式,使用{{ }}将js代码包裹,使js中的数据属性值进行展示,也称为Mustache语法
  • 插值表达式中的内容类型
    • data中的属性名
    • 数字
    • 字符串
    • 布尔值
    • 对js的数据属性进行运算
<div id="app">
  <h2>{{message}}</h2>
  <h2>{{message}}, 李银河!</h2>

  <!--mustache语法中,不仅仅可以直接写变量,也可以写简单的表达式-->
  <h2>{{firstName + lastName}}</h2>
  <h2>{{firstName + ' ' + lastName}}</h2>
  <h2>{{firstName}} {{lastName}}</h2>
  <h2>{{counter * 2}}</h2>
</div>

<script src="../../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      firstName: 'kobe',
      lastName: 'bryant',
      counter: 100
    }
  })
</script>
2.1.2 v-once 语法
  • 在某些情况下,我们可能不希望界面随意的跟随改变
    • 这个时候,我们就可以使用一个Vuev-once指令
  • 该指令后面不需要跟任何表达式(比如之前的v-for后面是由跟表达式的)
  • 该指令表示元素和组件只渲染一次,不会随着数据的改变而改变
<div id="app">
  <h2>{{message}}</h2>
  <h2 v-once>{{message}}</h2>
</div>

<script src="../../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    }
  })
</script>

在这里插入图片描述

2.1.3 v-html 语法
  • 某些情况下,我们从服务器请求到的数据本身就是一个HTML代码
    • 如果我们直接通过{{}}来输出,会将HTML代码也一起输出
    • 但是我们可能希望的是按照HTML格式进行解析,并且显示对应的内容
  • 如果我们希望解析出HTML展示
    可以使用v-html指令
    - 该指令后面往往会跟上一个string类型
    - 会将stringhtml解析出来并且进行渲染
<div id="app">
  <h2>{{url}}</h2>
  <h2 v-html="url"></h2>
</div>

<script src="../../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      url: '<a href="http://www.baidu.com">百度一下</a>'
    }
  })
</script>

在这里插入图片描述

2.1.4 v-text 语法
  • v-text作用和Mustache比较相似:都是用于将数据显示在界面中
  • v-text通常情况下,接受一个string类型
  • v-text不够灵活,会将标签中的文本内容覆盖
<div id="app">
  <h2>{{message}}, Vue.js!</h2>
  <h2 v-text="message">, Vue.js!</h2>
</div>

<script src="../../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    }
  })
</script>

在这里插入图片描述

2.1.4 v-pre 语法
  • v-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法
  • 比如下面的代码:
    • 第一个h2元素中的内容会被编译解析出来对应的内容
    • 第二个h2元素中会直接显示{{message}}
<div id="app">
  <h2>{{message}}</h2>
  <h2 v-pre>{{message}}</h2>
</div>

<script src="../../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    }
  })
</script>

在这里插入图片描述

2.1.5 v-cloak 语法
  • Vue 数据绑定过程
    • 先将未绑定数据的界面展示给用户
    • 然后再根据模型中的数据和控制的区域生成绑定数据之后的HTML代码
    • 最后再将绑定数据之后的HTML渲染到界面上
  • 问题
    • 正是在最终的HTML被生成渲染之前会先显示模板内容
    • 所以如果用户网络比较慢或者网页性能比较差, 那么用户会看到模板内容
  • 如何解决这个问题
    • 利用v-cloak配合 [v-cloak]:{display: none}默认先隐藏未渲染的界面
    • 等到生成HTML渲染之后再重新显示
  • v-cloak 指令作用
    • 数据渲染之后自动显示元素
<style>
  [v-cloak] {
    display: none;
  }
</style>
<div id="app" v-cloak>
  <h2>{{message}}</h2>
</div>

<script src="../../vue.js"></script>
<script>
  // 在vue解析之前, div中有一个属性v-cloak
  // 在vue解析之后, div中没有一个属性v-cloak
  setTimeout(function () {
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好啊'
      }
    })
  }, 1000)
</script>
2.2 绑定属性
2.2.1 v-bind 介绍
  • 什么是 v-bind 指令
    • 在企业开发中想要给"元素"绑定数据, 我们可以使用{{}}, v-text, v-html
    • 但是如果想给"元素的属性"绑定数据, 就必须使用v-bind
    • 所以 v-bind 的作用是专门用于给"元素的属性"绑定数据的
      • 比如动态绑定a元素的href属性
      • 比如动态绑定img元素的src属性
  • v-bind 格式
    • v-bind:属性名称="绑定的数据"
    • :属性名称="绑定的数据"
  • v-bind 特点
    • 赋值的数据可以是任意一个合法的JS表达式
    • 例如 :属性名称="age + 1"
2.2.2 v-bind 基础
  • v-bind用于绑定一个或多个属性值,或者向另一个组件传递props
  • 在开发中,有哪些属性需要动态进行绑定呢?
    • 还是有很多的,比如图片的链接src、网站的链接href、动态绑定一些样式等等
  • 比如通过Vue实例中的data绑定元素的srchref
<div id="app">
  <!-- 错误的做法: 这里不可以使用mustache语法-->
  <!--<img src="{{imgURL}}" alt="">-->
  <!-- 正确的做法: 使用v-bind指令 -->
  <img v-bind:src="imgURL" alt="">
  <a v-bind:href="aHref">百度一下</a>
  <!--<h2>{{}}</h2>-->

  <!--语法糖的写法-->
  <img :src="imgURL" alt="">
  <a :href="aHref">百度一下</a>
</div>

<script src="../../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      imgURL: 'https://img11.360buyimg.com/mobilecms/s350x250_jfs/t1/20559/1/1424/73138/5c125595E3cbaa3c8/74fc2f84e53a9c23.jpg!q90!cc_350x250.webp',
      aHref: 'http://www.baidu.com'
    }
  })
</script>
2.2.3 动态绑定 class 类名
  • 对象语法
    • 对象语法的含义是:class后面跟的是一个对象
## 用法一:直接通过{}绑定一个类
<h2 :class="{'active': isActive}">Hello World</h2>

## 用法二:也可以通过判断,传入多个值
<h2 :class="{'active': isActive, 'line': isLine}">Hello World</h2>

## 用法三:和普通的类同时存在,并不冲突
注:如果isActive和isLine都为true,那么会有title/active/line三个类
<h2 class="title" :class="{'active': isActive, 'line': isLine}">Hello World</h2>

## 用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
<h2 class="title" :class="classes">Hello World</h2>
  • 举例
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>

  <style>
    .active {
      color: red;
    }

    .line{
      border: 1px solid red;
    }
  </style>
</head>
<body>

<div id="app">
  <!--<h2 class="active">{{message}}</h2>-->
  <!--<h2 :class="active">{{message}}</h2>-->

  <!--<h2 v-bind:class="{key1: value1, key2: value2}">{{message}}</h2>-->
  <!--<h2 v-bind:class="{类名1: true, 类名2: boolean}">{{message}}</h2>-->
  <h2 class="title" v-bind:class="{active: isActive, line: isLine}" @click="btnClick">{{message}}</h2>
  <h2 class="title" v-bind:class="getClasses()" @click="btnClick1">{{message}}</h2>

</div>

<script src="../../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      isActive: true,
      isLine: true
    },
    methods: {
      btnClick: function () {
        this.isActive = true,
        this.isLine = true
      },
      btnClick1: function () {
        this.isActive = true,
        this.isLine = false
      },
      getClasses: function () {
        return {active: this.isActive, line: !this.isLine}
      }
    }
  })
</script>

</body>
</html>
  • 数组语法
    • 数组语法的含义是:class后面跟的是一个数组
## 用法一:直接通过[]绑定一个类
<h2 :class="['active']">Hello World</h2>

## 用法二:也可以传入多个值
<h2 :class="['active', 'line']">Hello World</h2>

## 用法三:和普通的类同时存在,并不冲突
注:会有title/active/line三个类
<h2 class="title" :class="['active', 'line']">Hello World</h2>

## 用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
<h2 class="title" :class="classes">Hello World</h2>
  • 举例
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    .active {
      color: red;
    }

    .line{
      border: 1px solid red;
    }
  </style>
</head>
<body>

<div id="app">
  <h2 class="title" :class="['active', 'line']">{{message}}</h2>
  <h2 class="title" :class="getClasses()">{{message}}</h2>
</div>

<script src="../../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      active: true,
      line: false
    },
    methods: {
      getClasses: function () {
        return ['active',!'line']
      }
    }
  })
</script>

</body>
</html>
2.2.4 动态绑定 class 类名练习
  • 点击列表中的哪一项, 那么该项的文字变成红色
<style media="screen">
.active{
  background-color: red;
}
</style>

<body>
	<div id="app">
	  <ul>
	    <!--<li v-for="m in movies">{{m}}</li>-->
	    <li v-on:click="change(index)" v-for="(m, index) in movies" v-bind:class="getClass(index)">{{m}}</li>
	  </ul>
	</div>

	<script src="../../vue.js"></script>
	<script>
	  const app = new Vue({
	    el: '#app',
	    data: {
	      count: 0,
	      movies: ['海王', '海尔兄弟', '火影忍者', '进击的巨人']
	    },
	    methods: {
	      change: function(index){
	        this.count = index;
	      },
	      getClass: function(index){
	        return this.count == index ? 'active' : ''
	      }
	    }
	  })
	</script>
</body>
2.2.5 动态绑定样式 style
  • 如何通过 v-bindstyle 属性绑定CSS内联样式数据
    • 将数据放到对象
      • :style="{color:'red','font-size':'50px'}"
    • 将数据放到Model对象
obj: {
    color: 'red',
    'font-size': '80px',
}
  • 注意点
    • 可以使用驼峰式 (camelCase) fontSize
    • 如果属性名称包含-, 那么必须用引号括起来
    • 如果需要绑定Model中的多个对象, 可以放到一个数组中赋值
  • 对象语法
    • style后面跟的是一个对象类型
      • 对象的keyCSS属性名称
      • 对象的value是具体赋的值,值可以来自于data中的属性
<h2 :style="{color: currentColor, fontSize: fontSize + 'px'}">{{messages}}</h2>
  • 数组语法
    • style后面跟的是一个数组类型
      • 多个值以,分割即可
<div v-bind:style="[baseStyles, overridingStyles]"></div>
2.3 条件和循环
2.3.1 条件渲染
  • v-ifv-else-ifv-else
    • 这三个指令与JavaScript的条件语句ifelseelse if类似
    • Vue的条件指令可以根据表达式的值在DOM中渲染或销毁元素或组件
  • 什么是 v-if 指令
    • 条件渲染
    • 如果 v-if 取值是 true 就渲染元素, 如果不是就不渲染元素
  • v-if 特点
    • 如果条件不满足根本就不会创建这个元素(重点)
  • v-if 注意点
    • v-if 可以从模型中获取数据
    • v-if 也可以直接赋值一个表达式
  • v-if 的原理
    • v-if后面的条件为false时,对应的元素以及其子元素不会渲染
    • 也就是根本没有不会有对应的标签出现在DOM
  • v-else 指令
    • v-else 指令可以和 v-if 指令配合使用, 当 v-if 不满足条件时就执行 v-else 就显示 v-else 中的内容
  • v-else 注意点
    • v-else 不能单独出现
    • v-if 和 v-else 中间不能出现其它内容
  • v-else-if 指令
    • v-else-if 可以和 v-if 指令配合使用
    • 当v-if 不满足条件时就依次执行后续 v-else-if, 哪个满足就显示哪个
  • v-else-if 注意点
    • 和 v-else一样
  • 案例
<div id="app">
  <h2 v-if="score>=90">优秀</h2>
  <h2 v-else-if="score>=80">良好</h2>
  <h2 v-else-if="score>=60">及格</h2>
  <h2 v-else>不及格</h2>
</div>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      score: 99
    }
  })
</script>
2.3.2 v-show 指令
  • 什么是 v-show 指令
    • v-showv-if 的能够一样都是条件渲染
    • 取值为 true 就显示, 取值为 false 就不显示
  • v-if 和 v-show 区别
    • v-if: 只要取值为false就不会创建元素
    • v-show: 哪怕取值为false也会创建元素, 只是如果取值是false会设置元素的display为none
  • v-if 和 v-show 应用场景
    • 如果需要切换元素的显示和隐藏, 每次 v-if 都会创建和删除元素
    • 如果需要切换元素的显示和隐藏,不会反复创建和删除, 只是修改display的值
    • 所以: 如果企业开发中需要频繁切换元素显示隐藏, 那么推荐使用 v-show, 否则使用 v-if
<div id="app">
  <button @click="toggle">切换显示</button>
  <h2 v-show="isShow">我要不要显示呢</h2>
</div>

<script src="../../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      isShow: true
    },
    methods: {
    	toggle() {
    		this.isShow = !this.isShow
    	}
    }
  })
</script>
2.3.3 v-for 指令
  • 什么是v-for指令
    • 相当于JS中的for in循环, 可以根据数据多次渲染元素
    • 格式如下
      • item in items
  • v-for 特点
    • 可以遍历 数组, 字符, 数字, 对象
  • 案例
    • 不需要使用索引值
      • v-for="movie in movies"
      • 依次从movies中取出movie,并且在元素的内容中,我们可以使用Mustache语法,来使用movie
  • 需要使用数组中的索引值
    • 语法格式
      • v-for=(item, index) in items
    • 其中的index就代表了取出的item在原数组的索引值
<div id="app">
	<ul>
		<li v-for="item in movies">{{item}}</li>
		<li v-for="(movie,index) in movies">{{index+1}}.{{movie}}</li>
	</ul>
</div>
<script>
	const app = new Vue({
		el: '#app',
		data: {
			movies: [
				'星际穿越','盗墓笔记','大话西游','少年派'
			]
		}
	})
</script>
  • v-for可以遍历对象
    • 比如某个对象中存储着你的个人信息,我们希望以列表的形式显示出来
<div id="app">
	<ul>
		<li v-for="(value,key,index) in info">{{value}}-{{key}}-{{index}}</li>
	</ul>
</div>
<script>
	const app = new Vue({
		el: '#app',
		data: {
			info: [
				name: 'xxx',
				age: 18,
				height: 1.80
			]
		}
	})
</script>
  • v-for 注意点
    • v-for 为了提升性能, 在更新已渲染过的元素列表时,会采用“就地复用”策略
    • 也正是因为这个策略, 在某些时刻会导致我们的数据混乱
  • 举例
    • 在列表前面新增了内容
    • 为了解决这个问题, 可以在渲染列表的时候给每一个元素加上一个独一无二的key
    • v-for 在更新已经渲染过的元素列表时, 会先判断 key 是否相同
      • 如果相同则复用
      • 如果不同则重新创建
  • key 属性注意点
    • 不能使用index的作为key,因为当列表的内容新增或者删除时index都会发生变化
<!--这里就是MVVM中的View-->
<div id="app">
    <form>
        <input type="text" v-model="name">
        <input type="submit" value="添加" @click.prevent="add">
    </form>
    <ul>
<!--        <li v-for="(person,index) in persons" :key="person.id">-->
        <li v-for="(person,index) in persons" :key="index">
            <input type="checkbox">
            <span>{{index}} --- {{person.name}}</span>
        </li>
    </ul>
</div>
<script>
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            persons: [
                {name: "zs", id: 1},
                {name: "ls", id: 2},
                {name: "ww", id: 3}
                ],
            name: ""
        },
        // 专门用于存储监听事件回调函数
        methods: {
            add(){
                let lastPerson = this.persons[this.persons.length - 1];
                let newPerson = {name: this.name, id: lastPerson.id + 1};
                // this.persons.push(newPerson);
                this.persons.unshift(newPerson);
                this.name = "";
            }
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
2.3.4 组件的 key 属性
  • 官方推荐我们在使用v-for时,给对应的元素或组件添加上一个:key属性
  • 为什么需要这个key属性呢?
    • 这个其实和Vue的虚拟DOMDiff算法有关系
    • 这里我们借用React’s diff algorithm中的一张图来简单说明一下:
      • 当某一层有很多相同的节点时,也就是列表节点时,我们希望插入一个新的节点
        • 我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的
        • 即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?
      • 所以我们需要使用key来给每个节点做一个唯一标识
        • Diff算法就可以正确的识别此节点
        • 找到正确的位置区插入新的节点
      • 所以一句话,key的作用主要是为了高效的更新虚拟DOM
        在这里插入图片描述
2.3.5 检测数组更新
  • 因为Vue数据响应式
  • 所以当数据发生变化时,Vue自动检测数据变化视图会发生对应的更新
  • Vue中包含了一组观察数组编译的方法,使用它们改变数组也会触发视图的更新
methods: {
	updateData: function(){
		// 响应式数组方法
		// 1. push方法
		this.names.push('xxx','zs')
		// 2. pop方法
		this.names.pop()
		// 3. unshift方法
		this.names.unshift('xxx','zs')
		// 4. shift方法
		this.names.shift()
		// 5. splice方法
		// 传递一个参数(index):将对应index,以及后面的所有数据删除掉
		this.names.splice(2)
		// 6. sort排序数据
		this.names.sort()
		// 7. reverse反转数据
		this.names.reverse()

		// 注意: 通过索引值修改数组中的元素(不可响应式)
        this.letters[0] = 'bbbbbb';
        // 通过以下方法
        this.letters.splice(0, 1, 'bbbbbb')
        // set(要修改的对象, 索引值, 修改后的值)
        Vue.set(this.letters, 0, 'bbbbbb')		
2.4 事件监听
2.4.1 v-on 介绍
  • 什么是 v-on 指令?
    • v-on 指令专门用于给元素绑定监听事件
  • v-on 指令格式
    • v-on:事件名称="回调函数名称"
    • @事件名称="回调函数名称"
  • v-on 注意点:
    • v-on 绑定的事件被触发之后, 会去 Vue 实例对象的 methods 中查找对应的回调函数
    • 如果是通过v-on来绑定监听事件, 那么在指定事件名称的时候不需要写on
    • 如果是通过v-on来绑定监听事件, 那么在赋值的时候必须赋值一个回调函数的名称
2.4.2 v-on 基础
  • 这里,我们用一个监听按钮的点击事件,来简单看看v-on的使用
    • 下面的代码中,我们使用了v-on:click="counter++”
    • 另外,我们可以将事件指向一个在methods中定义的函数
<div id="app">
  <h2>点击次数: {{counter}}</h2>
  <button v-on:click="counter++">按钮点击</button>
  <button v-on:click="btnClick">按钮点击</button>
  <!-- <button @click="counter++">按钮点击</button> -->
  <!-- <button @click="btnClick">按钮点击</button> -->
</div>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      counter: 0
    },
    methods: {
      btnClick(){
      	this.counter++
      }
    }
  })
</script>
2.4.3 v-on 修饰符
  • v-on 修饰符
    • 在事件中有很多东西需要我们处理, 例如事件冒泡,事件捕获, 阻止默认行为
    • 那么在Vue中如何处理以上内容呢, 我们可以通过v-on修饰符来处理
  • 常见修饰符
    • .stop - 调用 event.stopPropagation()
    • .prevent - 调用 event.preventDefault()
    • .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调
    • .self - 只当事件是从侦听器绑定的元素本身触发时才触发回调
    • .capture - 添加事件侦听器时使用 capture 模式
    • .native - 监听组件根元素的原生事件
    • .once - 只触发一次回调
<body>
	<!--这里就是MVVM中的View-->
	<div id="app">
	    <!--注意点: 默认情况下事件的回调函数可以反复的执行, 只要事件被触发就会执行-->
		<button v-on:click="myFn">我是按钮</button>
	    <!--如果想让事件监听的回调函数只执行一次, 那么就可以使用.once修饰符-->
		<button v-on:click.once ="myFn">我是按钮</button>
	    <!--如果想阻止元素的默认行为, 那么可以使用.prevent修饰符-->
		<a href="http://www.it666.com" v-on:click.prevent="myFn">我是A标签</a>
	    <!--
	    默认情况下载嵌套的元素中, 如果都监听了相同的事件, 那么会触发事件冒泡
	    如果想阻止事件冒泡, 那么可以使用.stop修饰符
	    -->
		<div class="a" @click="myFn1">
	        <div class="b" @click.stop="myFn2">
	            <div class="c" @click="myFn3"></div>
	        </div>
	    </div>
	    <!--
	    如果想让回调只有当前元素触发事件的时候才执行, 那么就可以使用.self修饰符
	    -->
		<div class="a" @click="myFn1">
	        <div class="b" @click.self="myFn2">
	            <div class="c" @click="myFn3"></div>
	        </div>
	    </div>
	    <!--
	    默认情况下是事件冒泡, 如果想变成事件捕获, 那么就需要使用.capture修饰符
	    -->
	    <div class="a" @click.capture="myFn1">
	        <div class="b" @click.capture="myFn2">
	            <div class="c" @click.capture="myFn3"></div>
	        </div>
	    </div>
	</div>
	<script>
	    // 这里就是MVVM中的View Model
	    let vue = new Vue({
	        el: '#app',
	        // 这里就是MVVM中的Model
	        data: {
	        },
	        // 专门用于存储监听事件回调函数
	        methods: {
	            myFn(){
	                alert('lnj');
	            },
	            myFn1(){
	                console.log("爷爷");
	            },
	            myFn2(){
	                console.log("爸爸");
	            },
	            myFn3(){
	                console.log("儿子");
	            }
	        }
	    });
	</script>
</body>
2.4.4 v-on 按键修饰符
  • 什么是按键修饰符
    • 通过按键修饰符监听特定按键触发的事件
    • 例如
      • 可以监听当前事件是否是回车触发的
      • 可以监听当前事件是否是ESC触发的等
  • 按键修饰符分类
    • 系统预定义修饰符
    • 自定义修饰符
<!--这里就是MVVM中的View-->
<div id="app">
<!--    <input type="text" @keyup.enter="myFn">-->
    <input type="text" @keyup.f2="myFn">
</div>
<script>
    Vue.config.keyCodes.f2 = 113;
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
            myFn(){
                alert("lnj");
            }
        }
    });
</script>
2.4.5 v-on 参数
  • 当通过methods中定义方法,以供@click调用时,需要注意参数问题
  • 情况一
    • 如果该方法不需要额外参数,那么方法后的()可以不添加
    • 注意:如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去
  • 情况二
    • 如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件
<div id="app">
  <!--1.事件调用的方法没有参数-->
  <button @click="btn1Click()">按钮1</button>
  <button @click="btn1Click">按钮1</button>

  <!--2.在事件定义时, 写方法时省略了小括号, 但是方法本身是需要一个参数的, 这个时候, Vue会默认将浏览器生产的event事件对象作为参数传入到方法-->
  <!--<button @click="btn2Click(123)">按钮2</button>-->
  <!--<button @click="btn2Click()">按钮2</button>-->
  <button @click="btn2Click">按钮2</button>

  <!--3.方法定义时, 我们需要event对象, 同时又需要其他参数-->
  <!-- 在调用方式, 如何手动的获取到浏览器产生的event对象: $event-->
  <button @click="btn3Click(abc, $event)">按钮3</button>
</div>

<script src="../../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      abc: 123
    },
    methods: {
      btn1Click() {
        console.log("btn1Click");
      },
      btn2Click(event) {
        console.log('--------', event);
      },
      btn3Click(abc, event) {
        console.log('++++++++', abc, event);
      }
    }
  })

  // 如果函数需要参数,但是没有传入, 那么函数的形参为undefined
  // function abc(name) {
  //   console.log(name);
  // }
  //
  // abc()
</script>
2.5 自定义指令
2.5.1 自定义全局指令
  • 自定义全局指令
    • 在Vue中除了可以使用Vue内置的一些指令以外, 我们还可以自定义指令
  • 自定义全局指令语法
vue.directive('自定义指令名称', {
    生命周期名称: function (el) {
        指令业务逻辑代码
    }
});
  • 指令生命周期方法
    • 自定义指令时一定要明确指令的业务逻辑代码更适合在哪个阶段执行
      • 例如: 指令业务逻辑代码中没有用到元素事件, 那么可以在bind阶段执行
      • 例如: 指令业务逻辑代码中用到了元素事件, 那么就需要在inserted阶段执行
  • 自定义指令注意点
    • 使用时需要加上v-, 而在自定义时不需要加上v-
<!--这里就是MVVM中的View-->
<div id="app">
   <p v-color>我是段落</p>
    <input type="text" v-focus>
</div>
<script>
    /*
    directive方法接收两个参数
    第一个参数: 指令的名称
    第二个参数: 对象
    注意点: 在自定义指令的时候, 在指定指令名称的时候, 不需要写v-
    注意点: 指令可以在不同的生命周期阶段执行
    bind: 指令被绑定到元素上的时候执行
    inserted: 绑定指令的元素被添加到父元素上的时候执行
    * */
    Vue.directive("color", {
        // 这里的el就是被绑定指令的那个元素
        bind: function (el) {
            el.style.color = "red";
        }
    });
    Vue.directive("focus", {
        // 这里的el就是被绑定指令的那个元素
        inserted: function (el) {
            el.focus();
        }
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
        }
    });
</script>
2.5.2 自定义指令参数
  • 自定义指令参数
    • 在使用官方指令的时候我们可以给指令传参
      • 例如: v-model="name"
      • 在我们自定义的指令中我们也可以传递传递
  • 获取自定义指令传递的参数
    • 在执行自定义指令对应的方法的时候, 除了会传递el给我们, 还会传递一个对象给我们
    • 这个对象中就保存了指令传递过来的参数
<!--这里就是MVVM中的View-->
<div id="app">
<!--    <p v-color="'blue'">我是段落</p>-->
    <p v-color="curColor">我是段落</p>
</div>
<script>
    Vue.directive("color", {
        // 这里的el就是被绑定指令的那个元素
        bind: function (el, obj) {
            // el.style.color = "red";
            el.style.color = obj.value;
        }
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            curColor: 'green'
        },
        // 专门用于存储监听事件回调函数
        methods: {
        }
    });
</script>
2.5.3 自定义局部指令
  • 自定义全局指令的特点
    • 在任何一个Vue实例控制的区域中都可以使用
  • 自定义局部指令的特点
    • 只能在自定义的那个Vue实例中使用
  • 如何自定义一个局部指令
    • 给创建Vue实例时传递的对象添加
directives: {
    // key: 指令名称
    // value: 对象
    'color': {
        bind: function (el, obj) {
            el.style.color = obj.value;
        }
    }
}
  • 举例
<div id="app1">
    <p v-color="'blue'">我是段落</p>
</div>
<div id="app2">
    <p v-color="'red'">我是段落</p>
</div>
<script>
    /*
    Vue.directive("color", {
        // 这里的el就是被绑定指令的那个元素
        bind: function (el, obj) {
            el.style.color = obj.value;
        }
    });
     */
    // 这里就是MVVM中的View Model
    let vue1 = new Vue({
        el: '#app1',
        // 这里就是MVVM中的Model
        data: {},
        // 专门用于存储监听事件回调函数
        methods: {}
    });
    // 这里就是MVVM中的View Model
    let vue2 = new Vue({
        el: '#app2',
        // 这里就是MVVM中的Model
        data: {},
        // 专门用于存储监听事件回调函数
        methods: {},
        // 专门用于定义局部指令的
        directives: {
            "color": {
                // 这里的el就是被绑定指令的那个元素
                bind: function (el, obj) {
                    el.style.color = obj.value;
                }
            }
        }
    });
</script>
2.6. 计算属性
2.6.1 计算属性的介绍
  • 插值语法特点

    • 可以在{{}}中编写合法的JavaScript表达式
  • 在插值语法中编写JavaScript表达式缺点

    • 没有代码提示
    • 语句过于复杂不利于我们维护
  • 如何解决?

    • 对于任何复杂逻辑,你都应当使用计算属性
  • 注意点

    • 虽然在定义计算属性的时候是通过一个函数返回的数据
    • 但是在使用计算属性的时候不能在计算属性名称后面加上()
    • 因为它是一个属性不是一个函数(方法)
<div id="app">
    <p>{{name}}</p>
    <p>{{age + 1}}</p>
    <p>{{msg.split("").reverse().join("")}}</p>
    <p>{{msg2}}</p>
</div>
<script>
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            name: "lnj",
            age: 18,
            msg: "abcdef"
        },
        // 专门用于存储监听事件回调函数
        methods: {},
        // 专门用于定义计算属性的
        computed: {
            msg2: function () {
                let res = "abcdef".split("").reverse().join("");
                return res;
            }
        }
    });
</script>
2.6.2 计算属性的复杂操作
  • 计算属性和函数
    • 通过计算属性我们能拿到处理后的数据, 但是通过函数我们也能拿到处理后的数据
    • 那么计算属性和函数有什么区别呢?
      • 函数"不会"将计算的结果缓存起来, 每一次访问都会重新求值
      • 计算属性"会"将计算的结果缓存起来, 只要数据没有发生变化, 就不会重新求值
  • 计算属性的特点
    • 只要返回的结果没有发生变化, 那么计算属性就只会被执行一次
  • 应用场景
    • 计算属性:比较适合用于计算不会频繁发生变化的的数据
<div id="app">
  <h2>总价格: {{totalPrice}}</h2>
</div>

<script src="../../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      books: [
        {id: 110, name: 'Unix编程艺术', price: 119},
        {id: 111, name: '代码大全', price: 105},
        {id: 112, name: '深入理解计算机原理', price: 98},
        {id: 113, name: '现代操作系统', price: 87},
      ]
    },
    computed: {
      totalPrice: function () {
        let result = 0
        for (let i=0; i < this.books.length; i++) {
          result += this.books[i].price
        }
        return result
      }
    }
  })
</script>
<div id="app">
    <!--<p>{{msg1()}}</p>
    <p>{{msg1()}}</p>
    <p>{{msg1()}}</p>-->
    <p>{{msg2}}</p>
    <p>{{msg2}}</p>
    <p>{{msg2}}</p>
</div>
<script>
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
            /*
            函数的特点: 每次调用都会执行
            * */
            msg1(){
                console.log("msg1函数被执行了");
                let res = "abcdef".split("").reverse().join("");
                return res;
            }
        },
        // 专门用于定义计算属性的
        computed: {
            /*
            计算属性的特点: 只要返回的结果没有发生变化, 那么计算属性就只会被执行一次
            计算属性的应用场景: 由于计算属性会将返回的结果缓存起来
                                所以如果返回的数据不经常发生变化,
                                那么使用计算属性的性能会比使用函数的性能高
            * */
            msg2: function () {
                console.log("msg2计算属性被执行了");
                let res = "abcdef".split("").reverse().join("");
                return res;
            }
        }
    });
</script>
2.6.3 计算属性的 setter 和 getter
  • 每个计算属性都包含一个getter和一个setter
    在上面的例子中,我们只是使用getter来读取
    在某些情况下,你也可以提供一个setter方法(不常用)
    在需要写setter的时候,代码如下:
computed: {
  fullName: {
    set: function(newValue) {
      // console.log('-----', newValue);
      const names = newValue.split(' ');
      this.firstName = names[0];
      this.lastName = names[1];
    },
    get: function () {
      return this.firstName + ' ' + this.lastName
    }
  },
}
2.6.4 计算属性的缓存
  • methodscomputed区别
    • 计算属性computed会进行缓存,如果多次使用时,计算属性只会调用一次
    • 而方法属性methods会多次调用
<div id="app">
	<div>{{fullName}}</div>
	<div>{{fullName}}</div>
	<div>{{fullName}}</div>
	<div>{{getFullName()}}</div>
	<div>{{getFullName()}}</div>
	<div>{{getFullName()}}</div>
</div>
computed: {
	fullName(){
		console.log('执行fullName的计算属性');
		return this.firstName + ' ' + this.lastName;
	}
},
methods: {
	getFullName(){
		console.log('执行getFullName的方法');
		return this.firstName + ' ' + this.lastName;
	}
}
2.7 表单绑定
2.7.1 基本使用
  • Vue中使用v-model指令来实现表单元素和数据的双向绑定
  • 案例解析:
    • 当我们在输入框输入内容时
    • 因为input中的v-model绑定了message,所以会实时将输入的内容传递给messagemessage发生改变
    • message发生改变时,因为上面我们使用Mustache语法,将message的值插入到DOM中,所以DOM会发生响应的改变
    • 所以,通过v-model实现了双向的绑定
  • 当然,我们也可以将v-model用于textarea元素
<div id="app">
  <input type="text" v-model="message">
  {{message}}
</div>

<script src="../../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    }
  })
</script>

在这里插入图片描述

2.7.2 v-model 原理
  • v-model其实是一个语法糖,它的背后本质上是包含两个操作:
    • v-bind绑定一个value属性
    • v-on指令给当前元素绑定input事件
<input type="text" v-model="message">
## 等于下面代码
<input type="text" :value="message" @input="message = $event.target.value">
2.7.3 v-model 绑定其他属性
  • v-model: radio
    • 当存在多个单选框时
<div id="app">
  <label for="male">
    <input type="radio" id="male" value="" v-model="sex"></label>
  <label for="female">
    <input type="radio" id="female" value="" v-model="sex"></label>
  <h2>您选择的性别是: {{sex}}</h2>
</div>

<script src="../../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      sex: '女'
    }
  })
</script>
  • v-model: checkbox
    • 复选框分为两种情况:单个勾选框多个勾选框
      • 单个勾选框
        • v-model即为布尔值
        • 此时inputvalue并不影响v-model的值
      • 多个复选框
        • 当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组
        • 当选中某一个时,就会将inputvalue添加到数组中
<div id="app">
	<!--1.checkbox单选框-->
	<label for="check">
		<input type="checkbox" id="check" v-model="checked">同意协议
	</label>
  	<p>是否选中: {{checked}}</p>

	<!--2.checkbox多选框-->
	<label><input type="checkbox" v-model="hobbies" value="篮球">篮球</label>
	<label><input type="checkbox" v-model="hobbies" value="足球">足球</label>
	<label><input type="checkbox" v-model="hobbies" value="台球">台球</label>
	<p>您选中的爱好: {{hobbies}}</p>
</div>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      checked: false, // 单选框
      hobbies: [], // 多选框
  })
</script>
  • v-model:select
    • checkbox一样,select也分单选和多选两种情况
    • 单选:只能选中一个值
      • v-model绑定的是一个值
      • 当我们选中option中的一个时,会将它对应的value赋值到mySelect
    • 多选:可以选中多个值
      • v-model绑定的是一个数组
      • 当选中多个值时,就会将选中的option对应的value添加到数组mySelects
<!--1.选择一个-->
<select name="abc" v-model="mySelect">
  <option value="苹果">苹果</option>
  <option value="香蕉">香蕉</option>
  <option value="榴莲">榴莲</option>
  <option value="葡萄">葡萄</option>
</select>
<h2>您选择的水果是: {{mySelect}}</h2>

<!--2.选择多个-->
<select name="abc" v-model="mySelects" multiple>
  <option value="苹果">苹果</option>
  <option value="香蕉">香蕉</option>
  <option value="榴莲">榴莲</option>
  <option value="葡萄">葡萄</option>
</select>
<h2>您选择的水果是: {{mySelects}}</h2>

<script>
  const app = new Vue({
    el: '#app',
    data: {
      mySelect: '香蕉',
      mySelects: []
    }
  })
</script>
2.7.4 值绑定
  • 值绑定就是动态的给value赋值而已
    • 我们前面的value中的值,可以回头去看一下,都是在定义input的时候直接给定的
    • 但是真实开发中,这些input的值可能是从网络获取或定义在data中的
    • 所以我们可以通过v-bind:value动态的给value绑定值
  <label v-for="item in originHobbies" :for="item">
    <input type="checkbox" :value="item" :id="item" v-model="hobbies">{{item}}
  </label>
  <script>
	  const app = new Vue({
	    el: '#app',
	    data: {
	      hobbies: [], // 多选框,
	      originHobbies: ['篮球', '足球', '乒乓球', '羽毛球', '台球', '高尔夫球']
	    }
	  })
</script>
2.7.5 修饰符
  • lazy修饰符
    • 默认情况下,v-model默认是在input事件中同步输入框的数据
    • 也就是说,一旦有数据发生改变对应的data中的数据就会自动发生改变
    • lazy修饰符可以让数据在失去焦点或者回车时才会更新
  • number修饰符
    • 默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理
    • 但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理
    • number修饰符可以让在输入框中输入的内容自动转成数字类型
  • trim修饰符
    • 如果输入的内容首尾有很多空格,通常我们希望将其去除
    • trim修饰符可以过滤内容左右两边的空格
<!--1.修饰符: lazy-->
<input type="text" v-model.lazy="message">
<h2>{{message}}</h2>

<!--2.修饰符: number-->
<input type="number" v-model.number="age">
<h2>{{age}}-{{typeof age}}</h2>

<!--3.修饰符: trim-->
<input type="text" v-model.trim="name">
<h2>您输入的名字:{{name}}</h2>
2.8 过滤器
2.8.1 自定义全局过滤器
  • 什么是过滤器?
    • 过滤器和函数和计算属性一样都是用来处理数据
    • 但是过滤器一般用于格式化插入的文本数据
  • 如何自定义全局过滤器
    • Vue.filter("过滤器名称", 过滤器处理函数):
  • 如何使用全局过滤器
    • {{ msg | 过滤器名称 }}
    • :value="msg | 过滤器名称"
  • 过滤器注意点
    • 只能在插值语法v-bind中使用
    • 过滤器可以连续使用
<div id="app">
    <!--Vue会把name交给指定的过滤器处理之后, 再把处理之后的结果插入到指定的元素中-->
    <p>{{name | formartStr1 | formartStr2}}</p>
</div>
<script>
    /*
    如何自定义一个全局过滤器
    通过Vue.filter();
    filter方法接收两个参数
    第一个参数: 过滤器名称
    第二个参数: 处理数据的函数
    注意点: 默认情况下处理数据的函数接收一个参数, 就是当前要被处理的数据
    * */
    Vue.filter("formartStr1", function (value) {
        // console.log(value);
        value = value.replace(/学院/g, "大学");
        console.log(value);
        return value;
    });
    Vue.filter("formartStr2", function (value) {
        // console.log(value);
        value = value.replace(/大学/g, "幼儿园");
        console.log(value);
        return value;
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            name: "Java学院, Python学院, 前端学院, 区块链学院"
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
2.8.2 自定义局部过滤器
  • 自定义全局过滤器的特点
    • 任何一个Vue实例控制的区域中都可以使用
  • 自定义局部过滤器的特点
    • 只能在自定义的那个Vue实例中使用
  • 如何自定义一个局部指令
    • 给创建Vue实例时传递的对象添加
filters: {
    // key: 过滤器名称
    // value: 过滤器处理函数
    'formartStr': function (value) {}
}
<div id="app1">
    <p>{{name | formartStr}}</p>
</div>
<div id="app2">
    <p>{{name | formartStr}}</p>
</div>
<script>
    // 这里就是MVVM中的View Model
    let vue1 = new Vue({
        el: '#app1',
        // 这里就是MVVM中的Model
        data: {
            name: "Java学院, Python学院, 前端学院, 区块链学院"
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
    // 这里就是MVVM中的View Model
    let vue2 = new Vue({
        el: '#app2',
        // 这里就是MVVM中的Model
        data: {
            name: "Java学院, Python学院, 前端学院, 区块链学院"
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        },
        // 专门用于定义局部过滤器的
        filters: {
            "formartStr": function (value) {
                // console.log(value);
                value = value.replace(/学院/g, "大学");
                // console.log(value);
                return value;
            }
        }
    });
</script>
2.8.3 自定义过滤器练习
  • 利用过滤器对时间进行格式化
<div id="app">
    <p>{{time | dateFormart("yyyy-MM-dd")}}</p>
</div>
<script>
    /*
    注意点: 在使用过滤器的时候, 可以在过滤器名称后面加上()
            如果给过滤器的名称后面加上了(), 那么就可以给过滤器的函数传递参数
    */
    Vue.filter("dateFormart", function (value, fmStr) {
        // console.log(fmStr);
        let date = new Date(value);
        let year = date.getFullYear();
        let month = date.getMonth() + 1 + "";
        let day = date.getDate() + "";
        let hour = date.getHours() + "";
        let minute = date.getMinutes() + "";
        let second = date.getSeconds() + "";
        if(fmStr && fmStr === "yyyy-MM-dd"){
            return `${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")}`;
        }
        return `${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")} ${hour.padStart(2, "0")}:${minute.padStart(2, "0")}:${second.padStart(2, "0")}`;
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            time: Date.now()
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
2.9 Vue 综合小练习
  • 学生管理系统
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>27-Vue-综合小练习</title>
    <script src="js/vue.js"></script>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        #app{
            width: 800px;
            margin: 50px auto;
            background: skyblue;
        }
        table{
            width: 100%;
            background: #000;
        }
        table tr{
            background: #fff;
        }
        form{
            width: 100%;
            display: flex;
            justify-content: space-between;
        }
    </style>
</head>
<body>
<!--这里就是MVVM中的View-->
<div id="app">
    <form v-show="isShow">
        <input type="text" placeholder="请输入序号" v-model="person.id">
        <input type="text" placeholder="请输入名称" v-model="person.name">
        <input type="text" placeholder="请输入分数" v-model="person.score">
        <input type="submit" value="新增" @click.prevent="add">
        <input type="submit" value="查询" @click.prevent="query">
    </form>
    <table>
        <tr>
            <th>序号</th>
            <th>姓名</th>
            <th>分数</th>
            <th>时间</th>
            <th>操作</th>
        </tr>
        <tr v-for="(person, index) in persons">
            <!--<td><input type="text" :value="person.id" :disabled="isDisabled"></td>
            <td><input type="text" :value="person.name" :disabled="isDisabled"></td>
            <td><input type="text" :value="person.score" :disabled="isDisabled"></td>
            <td><input type="text" :value="person.time | dateFormart" :disabled="isDisabled"></td>-->
            <td><input type="text" v-model="person.id" :disabled="isDisabled"></td>
            <td><input type="text" v-model="person.name" :disabled="isDisabled"></td>
            <td><input type="text" v-model="person.score" :disabled="isDisabled"></td>
            <td><input type="text" :value="person.time | dateFormart" disabled></td>
            <td>
                <a href="#" @click.prevent="edit">编辑</a>
                <a href="#" @click.prevent="del(index)">删除</a>
                <br/>
                <a href="#" @click.prevent="toggle">更多操作</a>
            </td>
        </tr>
    </table>
</div>
<script>
    Vue.filter("dateFormart", function (value, fmStr) {
        // console.log(fmStr);
        let date = new Date(value);
        let year = date.getFullYear();
        let month = date.getMonth() + 1 + "";
        let day = date.getDate() + "";
        let hour = date.getHours() + "";
        let minute = date.getMinutes() + "";
        let second = date.getSeconds() + "";
        if(fmStr && fmStr === "yyyy-MM-dd"){
            return `${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")}`;
        }
        return `${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")} ${hour.padStart(2, "0")}:${minute.padStart(2, "0")}:${second.padStart(2, "0")}`;
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            isDisabled:true,
            isShow: false,
            persons: [{
                id: 1,
                name: "zs",
                score: "99",
                time: Date.now()
            },{
                id: 2,
                name: "ls",
                score: "88",
                time: Date.now()
            },{
                id: 3,
                name: "ww",
                score: "88",
                time: Date.now()
            }],
            person: {
                id: "",
                name: "",
                score: ""
            }
        },
        // 专门用于存储监听事件回调函数
        methods: {
            edit(){
                this.isDisabled = !this.isDisabled;
            },
            toggle(){
                this.isShow = !this.isShow;
            },
            del(index){
                // console.log(index);
                this.persons.splice(index, 1);
            },
            add(){
                this.person.time = Date.now();
                this.persons.push(this.person);
                this.person = {
                    id: "",
                    name: "",
                    score: ""
                };
            },
            query(){
                let newPersons = this.persons.filter((person) => {
                    // console.log(person.score === this.person.score);
                    if(person.score === this.person.score){
                        return true;
                    }
                });
                // console.log(newPersons);
                this.persons = newPersons;
            }
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
</body>
</html>

3. Vue 过渡动画

3.1 初始过渡动画
3.1.1 添加过渡动画
  • 将需要执行动画的元素放到transition组件中
    • 当transition组件中的元素显示时会自动查找.v-enter/.v-enter-active/.v-enter-to类名
    • 当transition组件中的元素隐藏时会自动查找.v-leave/ .v-leave-active/.v-leave-to类名
  • 我们只需要在.v-enter和.v-leave-to中指定动画动画开始的状态
    • 在.v-enter-active和.v-leave-active中指定动画执行的状态,即可完成过渡动画
<style>
    *{
        margin: 0;
        padding: 0;
    }
    .box{
        width: 200px;
        height: 200px;
        background: red;
    }
    .v-enter{
        opacity: 0;
    }
    .v-enter-to{
        opacity: 1;
    }
    .v-enter-active{
        transition: all 3s;
    }
    .v-leave{
        opacity: 1;
    }
    .v-leave-to{
        opacity: 0;
    }
    .v-leave-active{
        transition: all 3s;
    }
</style>
<body>
<!--这里就是MVVM中的View-->
<div id="app">
    <button @click="toggle">我是按钮</button>
    <transition>
        <div class="box" v-show="isShow"></div>
    </transition>
</div>
<script>
    // 这里就是 MVVM 中的 View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是 MVVM 中的 Model
        data: {
            isShow: false
        },
        // 专门用于存储监听事件回调函数
        methods: {
            toggle(){
                this.isShow = !this.isShow;
            }
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
3.2 过渡动画注意点
3.2.1 transition注意点
  • transition中只能放一个元素, 多个元素无效
  • 如果想给多个元素添加过渡动画, 那么就必须创建多个transition组件
3.2.2 初始动画设置
  • 默认情况下第一次进入的时候是没有动画的
  • 如果想一进来就有动画, 我们可以通过给transition添加appear属性的方式
  • 告诉Vue第一次进入就需要显示动画
3.2.3 如何给多个不同的元素指定不同的动画
  • 如果有多个不同的元素需要执行不同的过渡动画
  • 可以通过给transition指定name的方式
    • 来指定"进入之前/进入之后/进入过程中, 离开之前/离开之后/离开过程中"对应的类名
    • 来实现不同的元素执行不同的过渡动画
<style>
    *{
        margin: 0;
        padding: 0;
    }
    .box{
        width: 200px;
        height: 200px;
        background: red;
    }
    .one-enter{
        opacity: 0;
    }
    .one-enter-to{
        opacity: 1;
        margin-left: 500px;
    }
    .one-enter-active{
        transition: all 3s;
    }
    .two-enter{
        opacity: 0;
    }
    .two-enter-to{
        opacity: 1;
        margin-top: 500px;
    }
    .two-enter-active{
        transition: all 3s;
    }
</style>

<!--这里就是MVVM中的View-->
<div id="app">
    <button @click="toggle">我是按钮</button>
    <transition appear name="one">
        <div class="box" v-show="isShow"></div>
<!--        <div class="box" v-show="isShow"></div>-->
    </transition>
    <transition appear name="two">
        <div class="box" v-show="isShow"></div>
    </transition>
</div>
<script>
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            isShow: true
        },
        // 专门用于存储监听事件回调函数
        methods: {
            toggle(){
                this.isShow = !this.isShow;
            }
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
3.3 钩子函数实现过渡动画
3.3.1当前过渡存在的问题
  • 通过transition+类名的方式确实能够实现过渡效果
  • 但是实现的过渡效果并不能保存动画之后的状态
  • 因为Vue内部的实现是在过程中动态绑定类名, 过程完成之后删除类名
  • 正式因为删除了类名, 所以不能保存最终的效果
3.3.2 在Vue中如何保存过渡最终的效果
  • 通过Vue提供的JS钩子来实现过渡动画
钩子函数含义
v-on:before-enter=“beforeEnter”进入动画之前
v-on:enter=“enter”进入动画执行过程中
v-on:after-enter=“afterEnter”进入动画完成之后
v-on:enter-cancelled=“enterCancelled”进入动画被取消
v-on:before-leave=“beforeLeave”离开动画之前
v-on:leave=“leave”离开动画执行过程中
v-on:after-leave=“afterLeave”离开动画完成之后
v-on:leave-cancelled=“leaveCancelled”离开动画被取消
3.3.3 JS钩子实现过渡注意点
  • 在动画过程中必须写上el.offsetWidth或者el.offsetHeight
  • enterleave方法中必须调用done方法, 否则after-enterafter-leave不会执行
  • 需要添加初始动画, 那么需要把done方法包裹到setTimeout方法中调用
<style>
    *{
        margin: 0;
        padding: 0;
    }
    .box{
        width: 200px;
        height: 200px;
        background: red;
    }
    .v-enter{
        opacity: 0;
    }
    .v-enter-to{
        opacity: 1;
        margin-left: 500px;
    }
    .v-enter-active{
        transition: all 3s;
    }
</style>
<!--这里就是MVVM中的View-->
<div id="app">
    <button @click="toggle">我是按钮</button>
    <!--
    注意点: 虽然我们是通过JS钩子函数来实现过渡动画
            但是默认Vue还是回去查找类名, 所以为了不让Vue去查找类名
            可以给transition添加v-bind:css="false"
    -->
    <transition appear
                v-bind:css="false"
                v-on:before-enter="beforeEnter"
                v-on:enter="enter"
                v-on:after-enter="afterEnter">
        <div class="box" v-show="isShow"></div>
    </transition>
</div>
<script>
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            isShow: true
        },
        // 专门用于存储监听事件回调函数
        methods: {
            toggle(){
                this.isShow = !this.isShow;
            },
            beforeEnter(el){
                // 进入动画开始之前
                console.log("beforeEnter");
                el.style.opacity = "0";
            },
            enter(el, done){
                // 进入动画执行过程中
                console.log("enter");
                /*
                注意点: 如果是通过JS钩子来实现过渡动画
                        那么必须在动画执行过程中的回调函数中写上
                        el.offsetWidth / el.offsetHeight
                * */
                // el.offsetWidth;
                el.offsetHeight;
                el.style.transition = "all 3s";
                /*
                注意点: 动画执行完毕之后一定要调用done回调函数
                        否则后续的afterEnter钩子函数不会被执行
                * */
                // done();
                /*
                注意点: 如果想让元素一进来就有动画, 那么最好延迟以下再调用done方法
                * */
                setTimeout(function () {
                    done();
                }, 0);
            },
            afterEnter(el){
                // 进入动画执行完毕之后
                console.log("afterEnter");
                el.style.opacity = "1";
                el.style.marginLeft = "500px";
            }
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
3.4 过渡动画 Velocity 库
3.4.1 配合 Velocity 实现过渡动画
  • 在Vue中我们除了可以自己实现过渡动画以外, 还可以结合第三方框架实现过渡动画
3.4.2 导入Velocity 库
  • 在动画执行过程钩子函数中编写Velocity动画
<style>
    *{
        margin: 0;
        padding: 0;
    }
    .box{
        width: 200px;
        height: 200px;
        background: red;
    }
</style>

<!--这里就是MVVM中的View-->
<div id="app">
    <button @click="toggle">我是按钮</button>
    <transition appear
                v-bind:css="false"
                v-on:before-enter="beforeEnter"
                v-on:enter="enter"
                v-on:after-enter="afterEnter">
        <div class="box" v-show="isShow"></div>
    </transition>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<script>
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            isShow: true
        },
        // 专门用于存储监听事件回调函数
        methods: {
            toggle(){
                this.isShow = !this.isShow;
            },
            beforeEnter(el){
            },
            enter(el, done){
                Velocity(el, {opacity: 1, marginLeft: "500px"}, 3000);
                done();
            },
            afterEnter(el){

            }
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
3.5 自定义过渡动画
3.5.1 自定义类名动画
  • 在Vue中
    • 除了可以使用 默认类名(v-xxx)来指定过渡动画
    • 除了可以使用 自定义类名前缀(yyy-xx)来指定过渡动画(transition name=“yyy”)
    • 除了可以使用 JS钩子函数来指定过渡动画以外
  • 还可以使用自定义类名的方式来指定过渡动画
enter-class进入动画开始之前
enter-active-class进入动画执行过程中
enter-to-class进入动画执行完毕之后
leave-class离开动画开始之前
leave-active-class离开动画执行过程中
leave-to-class离开动画执行完毕之后
<style>
    *{
        margin: 0;
        padding: 0;
    }
    .box{
        width: 200px;
        height: 200px;
        background: red;
    }
    .a{
        opacity: 0;
    }
    .b{
        opacity: 1;
        margin-left: 500px;
    }
    .c{
        transition: all 3s;
    }
</style>
<!--这里就是MVVM中的View-->
<div id="app">
    <button @click="toggle">我是按钮</button>
    <transition appear
                enter-class="a"
                enter-active-class="c"
                enter-to-class="b">
        <div class="box" v-show="isShow"></div>
    </transition>
</div>
<script>
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            isShow: true
        },
        // 专门用于存储监听事件回调函数
        methods: {
            toggle(){
                this.isShow = !this.isShow;
            }
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
3.6 过渡动画 Animate 库
  • 配合Animate.css实现过渡动画
    • 导入Animate.css库
    • 在执行过程中的属性上绑定需要的类名
<style>
    *{
        margin: 0;
        padding: 0;
    }
    .box{
        width: 200px;
        height: 200px;
        background: red;
    }
</style>
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
<!--这里就是MVVM中的View-->
<div id="app">
    <button @click="toggle">我是按钮</button>
    <transition appear
                enter-class=""
                enter-active-class="animated bounceInRight"
                enter-to-class="">
        <div class="box" v-show="isShow"></div>
    </transition>
</div>
<script>
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            isShow: true
        },
        // 专门用于存储监听事件回调函数
        methods: {
            toggle(){
                this.isShow = !this.isShow;
            }
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
3.7 列表动画
3.7.1 如何同时给多个元素添加过渡动画
  • 通过transition可以给单个元素添加过渡动画
  • 如果想给多个元素添加过渡动画, 那么就必须通过transition-group来添加
  • transition-grouptransition的用法一致
    • 只是一个是给单个元素添加动画
    • 一个是给多个元素添加动画而已
<style>
    *{
        margin: 0;
        padding: 0;
    }
    .v-enter{
        opacity: 0;
    }
    .v-enter-to{
        opacity: 1;
    }
    .v-enter-active{
        transition: all 3s;
    }
    .v-leave{
        opacity: 1;
    }
    .v-leave-to{
        opacity: 0;
    }
    .v-leave-active{
        transition: all 3s;
    }
</style>
<body>
<!--这里就是MVVM中的View-->
<div id="app">
    <form>
        <input type="text" v-model="name">
        <input type="submit" value="添加" @click.prevent="add">
    </form>
    <ul>
        <transition-group appear>
        <li v-for="(person,index) in persons" :key="person.id" @click="del(index)">
            <input type="checkbox">
            <span>{{index}} --- {{person.name}}</span>
        </li>
        </transition-group>
    </ul>
</div>
<script>
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            persons: [
                {name: "zs", id: 1},
                {name: "ls", id: 2},
                {name: "ww", id: 3}
                ],
            name: ""
        },
        // 专门用于存储监听事件回调函数
        methods: {
            add(){
                let lastPerson = this.persons[this.persons.length - 1];
                let newPerson = {name: this.name, id: lastPerson.id + 1};
                // this.persons.push(newPerson);
                this.persons.unshift(newPerson);
                this.name = "";
            },
            del(index){
                this.persons.splice(index, 1);
            }
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
3.7.2 transition-group 注意点
  • 默认情况下transition-group会将动画的元素放到span标签
  • 我们可以通过tag属性来指定将动画元素放到什么标签中
3.7.3 transition-group 动画混乱问题
  • 一般情况下组动画出现动画混乱都是因为v-for就地复用导致
  • 我们只需要保证所有数据key永远是唯一的即可
<style>
    *{
        margin: 0;
        padding: 0;
    }
    .v-enter{
        opacity: 0;
    }
    .v-enter-to{
        opacity: 1;
    }
    .v-enter-active{
        transition: all 3s;
    }
    .v-leave{
        opacity: 1;
    }
    .v-leave-to{
        opacity: 0;
    }
    .v-leave-active{
        transition: all 3s;
    }
</style>
<!--这里就是MVVM中的View-->
<div id="app">
    <form>
        <input type="text" v-model="name">
        <input type="submit" value="添加" @click.prevent="add">
    </form>
<!--    <ul>-->
        <transition-group appear tag="ul">
        <li v-for="(person,index) in persons" :key="person.id" @click="del(index)">
            <input type="checkbox">
            <span>{{index}} --- {{person.name}}</span>
        </li>
        </transition-group>
<!--    </ul>-->
</div>
<script>
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            persons: [
                {name: "zs", id: 1},
                {name: "ls", id: 2},
                {name: "ww", id: 3}
                ],
            name: "",
            id: 3
        },
        // 专门用于存储监听事件回调函数
        methods: {
            add(){
                this.id++;
                // let lastPerson = this.persons[this.persons.length - 1];
                let newPerson = {name: this.name, id: this.id};
                // this.persons.push(newPerson);
                this.persons.unshift(newPerson);
                this.name = "";
            },
            del(index){
                this.persons.splice(index, 1);
            }
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>

4. 组件化开发

4.1 认识组件化
4.1.1 什么是组件化
  • 在前端开发中组件就是把一个很大的界面拆分为多个小的界面, 每一个小的界面就是一个组件
  • 每个组件都用于实现页面的一个功能块,而每一个组件又可以进行细分
  • 将大界面拆分成小界面就是组件化
    在这里插入图片描述
  • 组件化的好处
    • 可以简化Vue实例的代码
    • 可以提高复用性
4.1.2 Vue组件化思想
  • 组件化是Vue.js中的重要思想
    • 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
    • 任何的应用都会被抽象成一颗组件树
      在这里插入图片描述
  • 组件化思想的应用
    • 有了组件化的思想,我们在之后的开发中就要充分的利用它
    • 尽可能的将页面拆分成一个个小的、可复用的组件
    • 这样让我们的代码更加方便组织和管理,并且扩展性也更强
4.2 组件化基础
4.2.1 注册组件
  • 组件的使用分成三个步骤:
    • 创建组件构造器
    • 注册组件
    • 使用组件

在这里插入图片描述

  • 注意点
    • 在创建组件指定组件的模板的时候, 模板只能有一个根元素
<div id="app">
  <!--3.使用组件(步骤三)-->
  <my-cpn></my-cpn>
</div>
<script>
// 1.创建组件构造器对象(步骤一)
const myComponent = Vue.extend({
  template: `
    <div>
      <h2>组件标题</h2>
      <p>我是组件中的一个段落内容</p>
    </div>`
})

// 2.注册组件,并且定义组件标签的名称(步骤二)
// 第一个参数: 指定注册的组件的名称
// 第二个参数: 传入已经创建好的组件构造器
Vue.component('my-cpn', myComponent)

const app = new Vue({
  el: '#app'
})
</script>
4.2.2 注册组件步骤解析
  • Vue.extend()
    • 调用Vue.extend()创建的是一个组件构造器
    • 通常在创建组件构造器时,传入template代表我们自定义组件的模板
    • 该模板就是在使用到组件的地方,要显示的HTML代码
    • 事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础
  • Vue.component()
    • 调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称
    • 所以需要传递两个参数
      • 注册组件的标签名
      • 组件构造器
  • 组件必须挂载在某个Vue实例下,否则它不会生效
4.2.3 创建组件的其他方式
  • 在注册组件的时候, 除了传入一个组件构造器以外, 还可以直接传入一个对象
  • 在编写组件模板的时候, 除了可以在字符串模板中编写以外, 还可以像过去的art-template一样在script中编写
  • 在编写组件模板的时候, 除了可以在script中编写以外, vue还专门提供了一个编写模板的标签template
<!--这里就是MVVM中的View-->
<div id="app">
    <!--// 3.3使用注册好的组件-->
    <abc></abc>
</div>
<!--
<script id="info" type="text/html">
    <div>
        <img src="images/fm.jpg"/>
        <p>我是描述信息</p>
    </div>
</script>
-->
<template id="info">
    <div>
        <img src="images/fm.jpg"/>
        <p>我是描述信息</p>
    </div>
</template>
<script>
    // 3.1创建组件构造器
    /*
    let Profile = Vue.extend({
        // 注意点: 在创建组件指定组件的模板的时候, 模板只能有一个根元素
        template: `
           <div>
                <img src="images/fm.jpg"/>
                <p>我是描述信息</p>
            </div>
        `
    });
     */
    /*
    let obj = {
        // 注意点: 在创建组件指定组件的模板的时候, 模板只能有一个根元素
        template: `
           <div>
                <img src="images/fm.jpg"/>
                <p>我是描述信息</p>
            </div>
        `
    };
     */
    // 3.2注册已经创建好的组件
    // 第一个参数: 指定注册的组件的名称
    // 第二个参数: 传入已经创建好的组件构造器
    // Vue.component("abc", Profile );
    // Vue.component("abc", obj );
    /*
    Vue.component("abc", {
        // 注意点: 在创建组件指定组件的模板的时候, 模板只能有一个根元素
        template: `
           <div>
                <img src="images/fm.jpg"/>
                <p>我是描述信息</p>
            </div>
        `
    });
     */
    Vue.component("abc", {
        // 注意点: 在创建组件指定组件的模板的时候, 模板只能有一个根元素
        template: "#info"
    });

    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
4.3 自定义组件
4.3.1 全局组件
  • 当我们通过调用Vue.component()注册组件时,组件的注册是全局
  • 这意味着该组件可以在任意Vue示例下使用
  • 全局组件
<!--这里就是MVVM中的View-->
<div id="app1">
	<!--会被渲染-->
    <abc></abc>
</div>
<div id="app2">
	<!--会被渲染-->
    <abc></abc>
</div>
<template id="info">
    <div>
        <img src="images/fm.jpg"/>
        <p>我是描述信息</p>
    </div>
</template>
<script>
    // 自定义全局组件
    Vue.component("abc", {
        // 注意点: 在创建组件指定组件的模板的时候, 模板只能有一个根元素
        template: "#info"
    });

    // 这里就是MVVM中的View Model
    let vue1 = new Vue({
        el: '#app1',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
    // 这里就是MVVM中的View Model
    let vue2 = new Vue({
        el: '#app2',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
4.3.2 局部组件
  • 如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件
  • 局部组件
<!--这里就是MVVM中的View-->
<div id="app1">
	<!--不会被渲染-->
    <abc></abc>
</div>
<div id="app2">
	<!--会被渲染-->
    <abc></abc>
</div>
<template id="info">
    <div>
        <img src="images/fm.jpg"/>
        <p>我是描述信息</p>
    </div>
</template>
<script>
    // 这里就是MVVM中的View Model
    let vue1 = new Vue({
        el: '#app1',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
    // 这里就是MVVM中的View Model
    let vue2 = new Vue({
        el: '#app2',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        },
        // 专门用于定义局部组件的
        components: {
            "abc": {
                // 注意点: 在创建组件指定组件的模板的时候, 模板只能有一个根元素
                template: "#info"
            }
        }
    });
</script>
4.4 组件中的 data 和 methods
4.4.1 自定义组件中的data和methods
  • Vue实例控制的区域相当于一个大的组件, 在大组件中我们可以使用datamethods
  • 而我们自定义的组件也是一个组件, 所以在自定义的组件中也能使用datamethods
4.4.2 自定义组件中data注意点
  • 在自定义组件中不能像在vue实例中一样直接使用data
  • 而是必须通过返回函数的方式来使用data
<!--这里就是MVVM中的View-->
<div id="app">
    <!--
    由于我们是在Vue实例控制的区域中使用的函数
    所以系统回去Vue实例中查找有没有对应的方法
    所以我们需要在Vue实例中实现对应的方法
    -->
    <button @click="appFn">我是按钮</button>
    <!--
    由于我们是在Vue实例控制的区域中使用了数据
    所以系统回去Vue实例中查找有没有对应的数据
    所以我们需要在Vue实例中添加对应的数据
    -->
    <p>{{appMsg}}</p>
    <abc></abc>
</div>
<template id="info">
    <div>
        <img src="images/fm.jpg"/>
        <!--
        由于我们是在自定义组件中使用了函数
        所以系统会去自定义的组件中查找有没有对应的方法
        所以我们需要在自定义的组件中实现对应的方法
        -->
        <button @click="abcFn">我是按钮</button>
        <p>{{abcMsg}}</p>
    </div>
</template>
<script>
    // 自定义全局组件
    Vue.component("abc", {
        // 注意点: 在创建组件指定组件的模板的时候, 模板只能有一个根元素
        template: "#info",
        methods: {
            abcFn(){
                alert("abcFn");
            }
        },
        // data: {
        //     abcMsg: "学院"
        // }
        // 注意点: 虽然在自定义的组件中也可以使用data, 但是在使用的时候, 使用的方式和Vue实例中不太一样
        //         在自定义组件中使用data必须赋值一个函数, 然后通过函数的返回值来定义有哪些数据
        data: function () {
            return {
                abcMsg: "学院"
            }
        }
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            appMsg:"张三"
        },
        // 专门用于存储监听事件回调函数
        methods: {
            appFn(){
                alert("appFn");
            }
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
4.4.3 组件中的 data 为什么是一个函数
  • 自定义组件中的data为什么是一个函数
    • 因为自定义组件可以复用, 为了保证复用时每个组件的数据都是独立的, 所以必须是一个函数
    • 组件中的data如果不是通过函数返回的, 那么多个组件就会共用一份数据, 就会导致数据混乱
    • 如果组件中的data是通过函数返回的, 那么每创建一个新的组件, 都会调用一次这个方法
    • 将这个方法返回的数据和当前创建的组件绑定在一起, 这样就有效的避免了数据混乱
<!--这里就是MVVM中的View-->
<div id="app">
    <abc></abc>
    <abc></abc>
    <abc></abc>
</div>
<template id="info">
    <div>
        <button @click="add">累加</button>
        <p>{{number}}</p>
    </div>
</template>
<script>
    // 自定义全局组件
    Vue.component("abc", {
        template: "#info",
        data: function () {
            return {
                number: 0
            }
        },
        methods: {
            add(){
                this.number++;
            }
        }
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
4.5 组件切换
  • 对于普通的元素我们可以通过v-if来实现切换
  • 对于组件我们也可以通过v-if来实现切换
  • 因为组件的本质就是一个自定义元素
<!--这里就是MVVM中的View-->
<div id="app">
    <button @click="toggle">切换</button>
    <!--<p v-if="isShow">我是首页</p>
    <img v-else src="images/fm.jpg" alt="">-->
    <home v-if="isShow"></home>
    <photo v-else></photo>
</div>
<template id="home">
    <div>
        <p>我是首页</p>
    </div>
</template>
<template id="photo">
    <div>
        <img src="images/fm.jpg" alt="">
    </div>
</template>
<script>
    // 自定义全局组件
    Vue.component("home", {
        template: "#home",
    });
    Vue.component("photo", {
        template: "#photo",
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            isShow: true
        },
        // 专门用于存储监听事件回调函数
        methods: {
            toggle(){
                this.isShow = !this.isShow;
            }
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
4.6 动态组件
4.6.1 什么是动态组件?
  • 通过v-if/v-else-if/v-else确实能够切换组件
  • 但是在Vue中切换组件还有一种更专业的方式
  • component我们称之为动态组件, 也就是你让我显示谁我就显示谁
<component v-bind:is="需要显示组件名称"></component>
4.6.2 为什么可以通过 v-if 切换还要有 component
  • 因为component可以配合keep-alive保存被隐藏组件隐藏之前的状态
<!--这里就是MVVM中的View-->
<div id="app">
    <button @click="toggle">切换</button>
    <keep-alive>
        <component v-bind:is="name"></component>
    </keep-alive>

</div>
<template id="home">
    <div>
        <p>我是首页</p>
        <input type="checkbox">
    </div>
</template>
<template id="photo">
    <div>
        <img src="images/fm.jpg" alt="">
    </div>
</template>
<script>
    // 自定义全局组件
    Vue.component("home", {
        template: "#home",
    });
    Vue.component("photo", {
        template: "#photo",
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            name: "home"
        },
        // 专门用于存储监听事件回调函数
        methods: {
            toggle(){
                this.name = this.name === "home" ? "photo" : "home";
            }
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
4.7 组件动画
4.7.1 如何给组件添加动画?
  • 给组件添加动画和过去给元素添加动画一样
  • 如果是单个组件就使用transition
  • 如果是多个组件就使用transition-group
4.7.2 过渡动画注意点
  • 默认情况下进入动画和离开动画是同时执行的
  • 如果想一个做完之后再做另一个, 需要指定动画模式
<style>
    .v-enter{
        opacity: 0;
        margin-left: 500px;
    }
    .v-enter-to{
        opacity: 1;
    }
    .v-enter-active{
        transition: all 3s;
    }
    .v-leave{
        opacity: 1;
    }
    .v-leave-to{
        opacity: 0;
    }
    .v-leave-active{
        transition: all 3s;
        margin-left: 500px;
    }
</style>
<!--这里就是MVVM中的View-->
<div id="app">
    <button @click="toggle">切换</button>
    <transition mode="out-in">
        <component v-bind:is="name"></component>
    </transition>
</div>
<template id="home">
    <div>
        <p>我是首页</p>
        <input type="checkbox">
    </div>
</template>
<template id="photo">
    <div>
        <img src="images/fm.jpg" alt="">
    </div>
</template>
<script>
    // 自定义全局组件
    Vue.component("home", {
        template: "#home",
    });
    Vue.component("photo", {
        template: "#photo",
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
            name: "home"
        },
        // 专门用于存储监听事件回调函数
        methods: {
            toggle(){
                this.name = this.name === "home" ? "photo" : "home";
            }
        },
        // 专门用于定义计算属性的
        computed: {
        }
    });
</script>
4.8 父子组件
4.8.1 什么是父子组件?
  • 在一个组件中又定义了其它组件就是父子组件
  • 其实局部组件就是最简单的父子组件, 因为我们说过可以把Vue实例看做是一个大组件
  • 我们在Vue实例中定义了局部组件, 就相当于在大组件里面定义了小组件, 所以局部组件就是最简单的父子组件
4.8.2 如何定义其它的父子组件
  • 前面我们说过, 自定义组件中可以使用data, 可以使用methods. 当然自定义组件中也可以使用components
  • 所以我们也可以在自定义组件中再定义其它组件
4.8.3 父子组件错误用法
  • 子标签的形式在Vue实例中使用
    • 因为当子组件注册到父组件的components时,Vue会编译好父组件的模块
    • 该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了)
    • <son></son>是只能在父组件中被识别的
    • 类似这种用法,<son></son>是会被浏览器忽略的
<!--这里就是MVVM中的View-->
<div id="app">
<!--    <home></home>-->
    <father></father>
<!--    <son></son>-->
</div>
<!--
<template id="home">
    <div>
        <p>我是首页</p>
    </div>
</template>
-->
<template id="father">
    <div>
        <p>我是父组件</p>
        <son></son>
    </div>
</template>
<template id="son">
    <div>
        <p>我是子组件</p>
    </div>
</template>
<script>
    /*
    Vue.component("father", {
        template: "#father",
        components: {
            "son": {
                template: "#son"
            }
        }
    });
     */
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        },
        // 专门用于定义局部组件的
        components: {
            // "home": {
            //     template: "#home"
            // }
            "father": {
                template: "#father",
                components: {
                    "son": {
                        template: "#son"
                    }
                }
            }
        }
    });
</script>
4.9 父子组件数据与方法传递
  • 在开发中,往往一些数据确实需要从上层传递到下层
    • 比如在一个页面中,我们从服务器请求到了很多的数据
    • 其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示
    • 这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)
  • 如何进行父子组件间的通信呢?Vue官方提到
    • 通过props向子组件传递数据
    • 通过事件向父组件发送消息

在这里插入图片描述

4.9.1 父级向子级传递数据
  • 父子组件数据传递?

    • 在Vue中子组件是不能访问父组件的数据的
    • 如果子组件想要访问父组件的数据, 必须通过父组件传递
  • 如何传递数据

    • 在父组件中通过v-bind 传递数据
    • 传递格式
      • v-bind:自定义接收名称 = "要传递数据"
  • 在子组件中通过props 接收数据

    • 接收格式
      • props: ["自定义接收名称"]
4.9.2 props 基本用法
  • 在组件中,使用选项props来声明需要从父级接收到的数据
  • props的值有两种方式:
    • 方式一:字符串数组
      • 数组中的字符串就是传递时的名称
    • 方式二:对象
      • 对象可以设置传递时的类型,也可以设置默认值等
<div id="app">
  <child-cpn :cmessage="message"></child-cpn>
</div>

<template id="childCpn">
  <div>显示信息:{{message}}</div>
</template>

<script src="../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      'child-cpn': {
      	template: '#childCpn',
      	props: ['cmessage']
      }
    }
  })
</script>
4.9.3 props 数据验证
  • 在前面,我们的props选项是使用一个数组
  • 我们说过,除了数组之外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法
  • 验证都支持哪些数据类型呢?
    • String
    • Number
    • Boolean
    • Array
    • Object
    • Date
    • Function
    • Symbol
  • 当我们有自定义构造函数时,验证也支持自定义的类型
vue.component('my-component',{
	props: {
		// 基础的类型检查(`null`匹配任何类型)
		propA: Number,
		// 多个可能的类型
		propB: [String,Number],
		// 必填的字符串
		propC: {
			type: String,
			required: true
		},
		// 带有默认值的数字
		propD: {
			type: Number,
			default: 100
		},
		// 带有默认值的对象
		propE: {
			type: Object,
			// 对象或数组默认值必须从一个工厂函数获取
			default: function(){
				return { message: 'hello' }
			}
		},
		// 自定义验证函数
		propF: {
			validator: function(value){
				// 这个值必须匹配下列字符串中的一个
				return ['success','warning','danger'].indexOf(value) !== -1 
				}
			}
		}
	})
				
function Person (firstName,lastName) {
	this.firstName = firstName
	this.lastName = lastName
}

vue.component('blog-post',{
	props: {
		author: Person
	}
})
4.9.4 props 中的驼峰标识
  • 驼峰处-代替
<div id="app">
  <cpn :c-info="info" :child-my-message="message" v-bind:class></cpn>
</div>

<template id="cpn">
  <div>
    <h2>{{cInfo}}</h2>
    <h2>{{childMyMessage}}</h2>
  </div>
</template>
const cpn = {
    template: '#cpn',
    props: {
      cInfo: {
        type: Object,
        default() {
          return {}
        }
      },
      childMyMessage: {
        type: String,
        default: ''
      }
    }
  }
4.9.5 父级向子级传递方法
  • 父子组件方法传递?
    • 在Vue中子组件是不能访问父组件的方法的
    • 如果子组件想要访问父组件的方法, 必须通过父组件传递
  • 如何传递方法
    • 在父组件中通过v-on传递方法
    • 传递格式
      • v-on:自定义接收名称 = "要传递方法"
  • 在子组件中自定义一个方法
    • 在自定义方法中通过 this.$emit('自定义接收名称');触发传递过来的方法
  • 注意点
    • 和传递数据不同, 如果传递的是方法, 那么在子组件中不需要接收
    • 如果传递的是方法, 那么需要在子组件中自定义一个方法
    • 如果传递的是方法, 那么在子组件中直接使用自定义的方法即可
    • 如果传递的是方法, 那么需要在子组件自定义的方法中通过this.$emit("自定义接收的名称")的方法来触发父组件传递过来的方法
<!--这里就是MVVM中的View-->
<div id="app">
    <father></father>
</div>
<template id="father">
    <div>
        <h6>father</h6>
        <button @click="say">我是按钮</button>
        <!--这里通过parentsay将父组件的say方法传递给了子组件-->
        <son @parentsay="say"></son>
    </div>
</template>
<template id="son">
    <div>
        <h6>son</h6>
        <button @click="sonFn">我是按钮</button>
    </div>
</template>
<script>
    // 父组件
    Vue.component("father", {
        template: "#father",
        methods: {
            say(){
                alert("www.it666.com");
            }
        },
        // 子组件
        components: {
            "son": {
                template: "#son",
                methods: {
                    sonFn(){
                        this.$emit("parentsay");
                    }
                }
            }
        }
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        },
        // 专门用于定义局部组件的
        components: {
        }
    });
</script>
4.9.6 子级向父级传递数据
  • 如何将子组件数据传递给父组件
    • 既然我们可以将父组件的方法传递给子组件
    • 既然我们可以在子组件中调用父组件中的方法
    • 那么我们就可以在调用方法的时候给方法传递参数
    • 传递的参数, 就是我们需要传递的数据
  • 子组件传递数据或事件到父组件中
    • 需要使用自定义事件来完成
    • v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件
  • 自定义事件的流程
    • 子组件中,通过$emit()来触发事件
    • 父组件中,通过v-on来监听子组件事件
<div id="app">
  <cpn @increment='changeTotal' @decrement='changeTotal'></cpn>
  <h2>点击次数:{{total}}</h2>
</div>

<template id="childCpn">
  <div class="">
    <button @click='increment'>+1</button>
    <button @click='decrement' :disabled='counter <= 0'>-1</button>
  </div>
</template>

<script src="../vue.js" charset="utf-8"></script>
<script type="text/javascript">
  const cpn = {
    template: '#childCpn',
    data(){
      return {
        counter: 0
      }
    },
    methods: {
      increment(){
        this.counter++;
        this.$emit('increment',this.counter)
      },
      decrement(){
        this.counter--;
        this.$emit('decrement',this.counter)
      }
    }
  }

  const vm = new Vue({
    el: '#app',
    data: {
      total: 0
    },
    methods: {
      changeTotal(counter) {
        this.total = counter;
      }
    },
    components: {
      cpn
    }
  })
</script>
<!--这里就是MVVM中的View-->
<div id="app">
    <father></father>
</div>
<template id="father">
    <div>
        <button @click="say">我是按钮</button>
        <!--这里通过parentsay将父组件的say方法传递给了子组件-->
        <son @parentsay="say"></son>
    </div>
</template>
<template id="son">
    <div>
        <button @click="sonFn">我是按钮</button>
    </div>
</template>
<script>
    // 父组件
    Vue.component("father", {
        template: "#father",
        methods: {
            say(data){
                // alert("www.it666.com");
                console.log(data);
            }
        },
        // 子组件
        components: {
            "son": {
                template: "#son",
                methods: {
                    sonFn(){
                        // 第一个参数: 需要调用的函数名称
                        // 后续的参数: 给调用的函数传递的参数
                        this.$emit("parentsay", "张三");
                    }
                }
            }
        }
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        },
        // 专门用于定义局部组件的
        components: {
        }
    });
</script>
4.9.7 数据和方法的多级传递
  • 在Vue中如果儿子想使用爷爷的数据, 必须一层一层往下传递
  • 在Vue中如果儿子想使用爷爷的方法, 必须一层一层往下传递
<!--这里就是MVVM中的View-->
<div id="app">
    <grandfather></grandfather>
</div>
<template id="grandfather">
    <div>
        <p>{{name}}</p>
        <button @click="say">我是按钮</button>
        <father :gfname="name" @gfsay="say"></father>
    </div>
</template>
<template id="father">
    <div>
        <p>{{gfname}}</p>
        <button @click="fatherFn">我是按钮</button>
        <son :fname="gfname" @fsay="fatherFn"></son>
    </div>
</template>
<template id="son">
    <div>
        <p>{{fname}}</p>
        <button @click="sonFn">我是按钮</button>
    </div>
</template>
<script>
    // 爷爷组件
    Vue.component("grandfather", {
        template: "#grandfather",
        data: function(){
            return {
                name: "lnj"
            }
        },
        methods: {
            say(){
                console.log("我是爷爷的方法");
            }
        },
        // 爸爸组件
        components: {
            "father": {
                template: "#father",
                props: ["gfname"],
                methods:{
                    fatherFn(){
                        this.$emit("gfsay");
                    }
                },
                // 儿子组件
                components: {
                    "son": {
                        template: "#son",
                        props: ["fname"],
                        methods: {
                            sonFn(){
                                this.$emit("fsay");
                            }
                        },
                    }
                }
            }
        }
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        },
        // 专门用于定义局部组件的
        components: {
        }
    });
</script>
4.10 父子组件的访问方式
  • 有时候我们需要父组件直接访问子组件子组件直接访问父组件,或者是子组件访问根组件
    • 父组件访问子组件:使用$children$refs reference(引用)
    • 子组件访问父组件:使用$parent
4.10.1 父组件访问子组件 $children
  • 我们先来看下$children的访问
    • this.$children是一个数组类型,它包含所有子组件对象
    • 我们这里通过一个遍历,取出所有子组件的message状态
<div id="app">
  <parent-cpn></parent-cpn>
</div>

<!--父组件-->
<template id="parentCpn">
  <div>
	<child-cpn1></child-cpn1>
	<child-cpn2></child-cpn2>
	<button @click="showChildCpn">显示所有子组件信息</button>
  </div>
</template>

<!--子组件1-->
<template id="childCpn1">
  <h2>我是子组件1</h2>
</template>

<!--子组件2-->
<template id="childCpn2">
  <h2>我是子组件2</h2>
</template>
Vue.component('parent-cpn',{
	template: '#parentCpn',
	methods: {
		showChildCpn() {
			for(let i = 0; i < this.$children.length; i++) {
				console.log(this.$children[i].message)
			}
		}
	}
}
  • $children的缺陷:
    • 通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值
    • 但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化
    • 有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs
4.10.2 父组件访问子组件 $refs
  • $refs的使用:
    • $refsref指令通常是一起使用的
    • 首先,我们通过ref给某一个子组件绑定一个特定的ID
    • 其次,通过this.$refs.ID就可以访问到该组件
<child-cpn1 ref="child1"></child-cpn1>
<child-cpn2 ref="child2"></child-cpn2>
<button @click="showRefsCpn">通过refs访问子组件</button>
showRefsCon() {
	console.log(this.$rsfs.child1.message);
	console.log(this.$rsfs.child2.message);
}
4.10.3 子组件访问父组件 $parent
  • 如果我们想在子组件中直接访问父组件,可以通过$parent
  • 注意事项
    • 尽管在Vue开发中,我们允许通过$parent来访问父组件,但是在真实开发中尽量不要这样做
    • 子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高
    • 如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题
    • 另外,更不好做的是通过$parent直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于调试和维护
<div id="app">
  <parent-cpn></parent-cpn>
</div>

<template id="parentCpn">
  <child-cpn></child-cpn>
</template>

<template id="childCpn">
  <button @click="showParent">显示父组件信息</button>
</template>
Vue.component('parent-cpn',{
	template: '#parentCpn',
	data() {
		return {
			message: '我是父组件‘
		}
	},
	components: {
		'child-cpn': {
			template: '#childCpn',
			methods: {
				showParent() {
					console.log(this.$parent.message);
				}
			}
		}
	}
})
4.11 非父子组件通信
  • 刚才我们讨论的都是父子组件间的通信,那如果是非父子关系呢?
  • 非父子组件关系包括多个层级的组件,也包括兄弟组件的关系
  • Vue1.x的时候,可以通过$dispatch$broadcast完成
    • $dispatch用于向上级派发事件
    • $broadcast用于向下级广播事件
    • 但是在Vue2.x都被取消了
  • Vue2.x中,有一种方案是通过中央事件总线,也就是一个中介来完成
    • 但是这种方案和直接使用Vuex的状态管理方案还是逊色很多
    • 并且Vuex提供了更多好用的功能

5. Vue 插槽

5.1 Vue 插槽基本概念
5.1.1 什么是插槽
  • 默认情况下使用子组件时在子组件中编写的元素是不会被渲染
  • 如果子组件中有部分内容是使用时才确定的, 那么我们就可以使用插槽
  • 插槽就是在子组件中放一个"坑", 以后由父组件来"填"
5.1.2 为什么使用 slot
  • slot翻译为插槽
  • 插槽的目的是让我们原来的设备具备更多的扩展性
  • 组件的插槽
    • 组件的插槽也是为了让我们封装的组件更加具有扩展性
    • 让使用者可以决定组件内部的一些内容到底展示什么
  • 举例
    • 移动网站中的导航栏
    • 导航栏我们必然会封装成一个插件,比如nav-bar组件
    • 一旦有了这个组件,我们就可以在多个页面中复用
      在这里插入图片描述
  • 抽取共性,保留不同
    • 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽
    • 一旦预留插槽,就可以让使用者根据自己的需求决定插槽中插入什么内容
5.1.3 slot 基本使用
  • 如何使用slot
    • 在子组件中,使用特殊的元素<slot>就可以为子组件开启一个插槽
    • 该插槽插入什么内容取决于父组件如何使用
  • 我们通过一个简单的例子,来给子组件定义一个插槽:
    • <slot>中的内容表示,如果没有在该组件中插入任何其他内容,就默认显示该内容
    • 有了这个插槽后,父组件如何使用呢?
<div id="app">
  <my-cpn></my-cpn>
  <my-cpn>
	<h2>我是替换插槽的内容</h2>
  </my-cpn>
</div>

<template id="myCpn">
  <div>
    <slot>我是一个插槽的默认内容</slot>
  </div>
</template>
<script src="../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    components: {
      myCpn: {
        template: '#myCpn'
      }
    }
  })
</script>
5.2 Vue 匿名插槽
5.2.1 什么是匿名插槽
  • 插槽是可以指定名称的, 默认情况下如果没有指定名称, 我们称之为匿名插槽
    • 匿名插槽, 会利用使用者指定的内容替换整个插槽
  • 插槽可以指定默认数据
    • 如果使用者没有填这个坑, 那么就会显示默认数据
    • 如果使用者填了这个坑, 那么就会利用使用者填坑的内容替换整个插槽
  • 匿名插槽的特点
    • 有多少个匿名插槽, 填充的数据就会被拷贝几份
    • 虽然我们可以指定多个匿名插槽, 但是在企业开发中推荐只写一个匿名插槽
<!--这里就是MVVM中的View-->
<div id="app">
    <father></father>
</div>
<template id="father">
    <div>
        <!--需求: 在使用子组件的时候给子组件动态的添加一些内容-->
        <son>
            <!--注意点: 默认情况下是不能在使用子组件的时候, 给子组件动态的添加内容的
                        如果想在使用子组件的时候, 给子组件动态的添加内容, 那么就必须使用插槽-->
            <div>我是追加的内容1</div>
            <div>我是追加的内容2</div>
            <div>我是追加的内容3</div>
        </son>
    </div>
</template>
<template id="son">
    <div>
        <div>我是头部</div>
        <!--这里的slot标签就是插槽, 插槽其实就是一个坑
            只要有了这个坑, 那么以后使用者就可以根据自己的需要来填这个坑-->
        <slot>我是默认数据</slot>
        <div>我是底部</div>
    </div>
</template>
<script>
    // 父组件
    Vue.component("father", {
        template: "#father",
        // 子组件
        components: {
            "son": {
                template: "#son",
            }
        }
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        },
        // 专门用于定义局部组件的
        components: {
        }
    });
</script>
5.3 Vue 具名插槽
5.3.1 什么是具名插槽
  • 默认情况下有多少个匿名插槽, 我们填充的数据就会被拷贝多少份
    • 这导致了所有插槽中填充的内容都是一样的
  • 那么如果我们想给不同的插槽中填充不同的内容怎么办呢?
  • 这个时候就可以使用具名插槽
5.3.2 具名插槽的使用
  • 通过插槽的name属性给插槽指定名称
  • 在使用时可以通过slot="name"方式, 指定当前内容用于替换哪一个插槽
  • 注意点
    • 默认情况下填充的内容是不会被填充到具名插槽中的
    • 只有给填充的内容指定了要填充到哪一个具名插槽之后
    • 才会将填充的内容填充到具名插槽中
    • 如果没有指定要替换哪个插槽中的内容, 则不会被替换
<div id="app">
  <!-- 只替换中间的插槽 -->
  <cpn><span slot="center">标题</span></cpn>
  <!-- 只替换右边的插槽 -->
  <cpn><button slot="left">返回</button></cpn>
</div>

<template id="cpn">
  <div>
    <slot name="left"><span>左边</span></slot>
    <slot name="center"><span>中间</span></slot>
    <slot name="right"><span>右边</span></slot>
  </div>
</template>

<script src="../vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn: {
        template: '#cpn'
      }
    }
  })
</script>
5.4 Vue v-slot 指令
5.4.1 什么是 v-slot 指令
  • v-slot 指令是Vue2.6中用于替代 slot 属性的一个指令
    • 在Vue2.6之前, 我们通过slot属性告诉Vue当前内容填充到哪一个具名插槽
    • 从Vue2.6开始, 我们通过v-slot指令告诉Vue当前内容填充到哪一个具名插槽
  • 注意点
    • v-slot 指令只能用在template标签上
    • 可以使用#号替代v-slot:
<!--这里就是MVVM中的View-->
<div id="app">
    <father></father>
</div>
<template id="father">
    <div>
        <son>
            <!--这里通过slot属性告诉Vue,当前的内容是要填充到哪一个插槽中的-->
            <!--
            <div slot="one">我是追加的内容1</div>
            <div slot="one">我是追加的内容11</div>
            <div slot="two">我是追加的内容2</div>
            <div slot="two">我是追加的内容22</div>
            -->
            <!--
            <template v-slot:one>
                <div>我是追加的内容1</div>
                <div>我是追加的内容11</div>
            </template>
            <template v-slot:two>
                <div>我是追加的内容2</div>
                <div>我是追加的内容22</div>
            </template>
            -->
            <!--v-bind: :  v-on: @-->
            <template #one>
                <div>我是追加的内容1</div>
                <div>我是追加的内容11</div>
            </template>
            <template #two>
                <div>我是追加的内容2</div>
                <div>我是追加的内容22</div>
            </template>
        </son>
    </div>
</template>
<template id="son">
    <div>
        <div>我是头部</div>
        <slot name="one">我是one默认内容</slot>
        <slot name="two">我是two默认内容</slot>
        <div>我是底部</div>
    </div>
</template>
<script>
    // 父组件
    Vue.component("father", {
        template: "#father",
        // 子组件
        components: {
            "son": {
                template: "#son",
            }
        }
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        },
        // 专门用于定义局部组件的
        components: {
        }
    });
</script>
5.5 Vue slot 作用域插槽
5.5.1 什么是作用域插槽
  • 作用域插槽就是带数据的插槽
    • 就是让父组件在填充子组件插槽内容时也能使用子组件的数据
5.5.2 如何使用作用域插槽
  • slot中通过 v-bind:数据名称="数据名称" 方式暴露数据
  • 父组件中通过 <template slot-scope="作用域名称"> 接收数据
  • 父组件<template></template>中通过 作用域名称.数据名称 方式使用数据
<!--这里就是MVVM中的View-->
<div id="app">
    <father></father>
</div>
<template id="father">
    <div>
        <son>
<!--            <div>我是填充的内容 {{names}}</div>-->
            <!--
            slot-scope="abc"作用: 接收子组件插槽暴露的数据
            -->
            <!--
            作用域插槽的应用场景: 子组件提供数据, 父组件决定如何渲染
            -->
            <template slot-scope="abc">
<!--                <div>我是填充的内容 {{abc.names}}</div>-->
                <li v-for="(name, index) in abc.names">{{name}}</li>
            </template>
        </son>
    </div>
</template>
<template id="son">
    <div>
        <div>我是头部 {{names}}</div>
        <!--
        v-bind:names="names"作用: 将当前子组件的names数据暴露给父组件
        -->
        <slot v-bind:names="names">我是默认内容 {{names}}</slot>
        <div>我是底部</div>
    </div>
</template>
<script>
    // 父组件
    Vue.component("father", {
        template: "#father",
        // 子组件
        components: {
            "son": {
                template: "#son",
                data:function () {
                    return {
                        names: ["zs", "ls", "ww", "zl"]
                    }
                }
            }
        }
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        },
        // 专门用于定义局部组件的
        components: {
        }
    });
</script>
5.5.3 使用 v-slot 进行作用域插槽
  • 在 2.6.0 中,我们为具名插槽作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)
  • v-slot 指令取代了 slot 和 slot-scope
    • 除了可以通过v-slot指令告诉Vue内容要填充到哪一个具名插槽中
    • 还可以通过v-slot指令告诉Vue如何接收作用域插槽暴露的数据
    • v-slot:插槽名称="作用域名称"
<!--这里就是MVVM中的View-->
<div id="app">
    <father></father>
</div>
<template id="father">
    <div>
        <son>
            <!--
            <template slot-scope="abc">
                <li v-for="(name, index) in abc.names">{{name}}</li>
            </template>
            -->
            <!--
            <template v-slot:default="abc">
                <li v-for="(name, index) in abc.names">{{name}}</li>
            </template>
            -->
            <!--
            <template #default="abc">
                <li v-for="(name, index) in abc.names">{{name}}</li>
            </template>
            -->
            <template #one="abc">
                <li v-for="(name, index) in abc.names">{{name}}</li>
            </template>
        </son>
    </div>
</template>
<template id="son">
    <div>
        <div>我是头部 {{names}}</div>
<!--        <slot v-bind:names="names">我是默认内容 {{names}}</slot>-->
        <slot name="one" v-bind:names="names">我是默认内容 {{names}}</slot>
        <div>我是底部</div>
    </div>
</template>
<script>
    // 父组件
    Vue.component("father", {
        template: "#father",
        // 子组件
        components: {
            "son": {
                template: "#son",
                data:function () {
                    return {
                        names: ["zs", "ls", "ww", "zl"]
                    }
                }
            }
        }
    });
    // 这里就是MVVM中的View Model
    let vue = new Vue({
        el: '#app',
        // 这里就是MVVM中的Model
        data: {
        },
        // 专门用于存储监听事件回调函数
        methods: {
        },
        // 专门用于定义计算属性的
        computed: {
        },
        // 专门用于定义局部组件的
        components: {
        }
    });
</script>

6. 总结

  • Vue.js 框架是前端工程师必不可少的技能,趁着寒假学习相关视频教程,写下这篇笔记,主要用于自己以后忘了的时候,复习一下,😁
  • 下一篇 Vue.js 框架进阶笔记
  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值