Vue.js 框架基础笔记
- 1. Vue.js 基本概念
- 2. Vue 基础语法
- 3. Vue 过渡动画
- 4. 组件化开发
- 5. Vue 插槽
- 6. 总结
1. Vue.js 基本概念
1.1 遇见 Vue.js
1.1.1 为什么要学习 Vue.js
组件化
- 其中以
React
的组件化最为彻底 - 甚至可以到函数级别的原子组件
- 高度的组件化可以是我们的工程易于维护、易于组合拓展
- 其中以
天然分层
jQuery
时代的代码大部分情况下是面条代码,耦合严重- 现代框架不管是
MVC
、MVP
还是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对象
挂载到哪一个元素上 - 我们这里是挂载到了
id
为app
的元素上
- 该属性决定了这个
{}
中包含了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 Binding
让obj
中的数据实时的在DOM
中显示 - 其次
ViewModel
通过DOM Listener
来监听DOM
事件,并且通过methods
中的操作,来改变obj
中的数据
- 首先
- 我们的计数器中就有严格的
1.4.2 Vue 中的 MVVM
View
层:视图层
- 在我们前端开发中,通常就是
DOM
层 - 主要的作用是
给用户展示各种信息
展示数据, 与用户交互
Model
层:数据层
- 数据可能是我们固定的死数据,更多的是来自我们服务器,从网络上请求下来的数据
保存数据, 处理数据业务逻辑
ViewModel
层:视图模型层
- 视图模型层是
View
和Model
沟通的桥梁 - 通过
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 语法
- 在某些情况下,我们可能不希望界面随意的跟随改变
- 这个时候,我们就可以使用一个
Vue
的v-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
类型
- 会将string
的html
解析出来并且进行渲染
<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
绑定元素的src
和href
<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-bind
给style
属性绑定CSS内联样式
数据- 将数据放到
对象
中:style="{color:'red','font-size':'50px'}"
- 将数据放到
Model对象
中
- 将数据放到
obj: {
color: 'red',
'font-size': '80px',
}
注意点
- 可以使用
驼峰式 (camelCase) fontSize
- 如果属性名称包含
-
, 那么必须用引号括起来
- 如果
需要绑定Model中的多个对象
, 可以放到一个数组
中赋值
- 可以使用
对象语法
style
后面跟的是一个对象类型
- 对象的
key
是CSS
属性名称 - 对象的
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-if
、v-else-if
、v-else
- 这三个指令与
JavaScript
的条件语句if
、else
、else 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-show
和v-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 为了提升性能, 在更新已渲染过的元素列表时,会采用“
就地复用
”策略 - 也正是因为这个策略, 在某些时刻会导致我们的
数据混乱
- 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
的虚拟DOM
的Diff
算法有关系 - 这里我们借用
React’s diff algorithm
中的一张图来简单说明一下:- 当某一层有很多相同的节点时,也就是列表节点时,我们希望插入一个新的节点
- 我们希望可以在B和C之间加一个F,
Diff
算法默认执行起来是这样的 - 即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?
- 我们希望可以在B和C之间加一个F,
- 所以我们需要使用
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 注意点:
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 计算属性的缓存
methods
和computed
区别- 计算属性
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
,所以会实时将输入的内容传递给message
,message
发生改变 - 当
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
即为布尔值
- 此时
input
的value
并不影响v-model
的值
多个复选框
:- 当是多个复选框时,因为可以选中多个,所以对应的
data
中属性是一个数组 - 当选中某一个时,就会将
input
的value
添加到数组中
- 当是多个复选框时,因为可以选中多个,所以对应的
- 复选框分为两种情况:
<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
绑定值
- 我们前面的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
- 在
enter
和leave
方法中必须调用done
方法, 否则after-enter
和after-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-group
和transition
的用法一致- 只是一个是给
单个元素
添加动画 - 一个是给
多个元素
添加动画而已
- 只是一个是给
<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实例控制的区域相当于一个大的组件, 在大组件中我们可以使用
data
和methods
- 而我们自定义的组件也是一个组件, 所以在自定义的组件中也能使用
data
和methods
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中
子组件是不能访问父组件的数据的
- 如果子组件想要访问父组件的数据, 必须通过
父组件传递
- 在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中
子组件是不能访问父组件的方法的
- 如果子组件想要访问父组件的方法, 必须
通过父组件传递
- 在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
的使用:$refs
和ref
指令通常是一起使用的
- 首先,我们通过
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当前内容填充到哪一个具名插槽
- 在Vue2.6之前, 我们通过
注意点
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 框架进阶笔记