1. 认识与准备
Vue是渐进式JavaScript框架
特定: 易用, 灵活, 高效(超快的虚拟DOM)
渐进式理解:
- 如果你已经有一个现成的服务端应用, 你可以将vue作 为该应用的一部分嵌入到里面,带来更加丰富的交互体验
- 如果想将更多的交互放在前端来实现, 那么Vue的核心库及其生态系统也可以满足你的各式需求(core+vuex+vue-router).和其他的前端框架一样Vue允许你讲一个网页分割成可复用的组件,每个组件都包含自己的HTML,css,javascript以用来渲染网页中相应的地方
- 如果我们构建一个大型的应用,在这一点上,我们可以需要将东西分割成为各自的组件和文件,vue可以用脚手架搭建开发的工程
- 所谓的渐进式就是: 从中心的视图层开始向外扩散的结构工具层.这个过程会经历: 视图层渲染 -> 组件机制 -> 路由机制 -> 状态管理 -> 构建工具这五个层级
兼容性: Vue不支持IE8及以下版本,因为Vue如果使用了IE8无法模拟的ECMAScript5特性。但它支持所有兼容ECMAScrip5t的浏览器
Vue Devtools: 浏览器安装Vue Devtools, 用来调试与审查应用
使用Vue
<body>
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue()
</script>
</body>
源码
function Vue (options) {
/**
* 如果在开发环境下,并且this不是Vue对象构造函数实例, 则对象构造函数调用warn函数并传入字符串作为一个参数,通知开发者使用new关键字将Vue作为构造函数来调用
*
* 开发环境下什么情况this不指向Vue
*/
// 如果当前是生产环境 &&
if (process.env.NODE_ENV !== 'production' &&
// 检查this是否不是Vue对象构造函数实例, 这句话也就是问用户是否new Vue
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// Vue构造函数, 调用this._init传入options
this._init(options)
}
2. MVVM与MVC
MVC(Model-View-Controller)
这里的M、V和MVVM中的M、V意思一样,C页面业务逻辑, 使用MVC就是为了将M和V分离,MVC是单向的通信也就是View到Model.必须通过Controller来承上启下.
MVVM(Model-View-ViewModel)
MVVM就是MVC的改进版, MVVM就是将其中的View的状态和行为抽象化,让我们将视图和业务逻辑分开。
模型: 后端传递的数据
视图: 所看见的页面
视图模型: MVVM核心, 它是连接view和model的桥梁,可以分为两个方向:
- 将model转化为view,将后端的数据转化为所看到的页面. 实现方式数据绑定
- 将view转化为model,页面转化为后端的数据, 实现方式DOM事件监听
这两个方法都实现我们称为双向数据绑定
在MVVM的框架下试图和模型不能直接通信.他们通过ViewModel来通信,ViewModel通常要实现一个observer观察者,当数据发生改变, ViewModel能够监听到这种变化,然后通过对应的视图做出更新。当用户操作视图ViewModel也能监听到视图变化,然后通过数据做改动,这实际上就实现了数据的双向绑定
MVC和MVVM并不是ViewModel取代Controller, ViewModel存在目的是在于抽离Controller中展示的业务逻辑,而不是Controller,其他视图操作业务等还是放在Controller里面实现,也就是说MVVM实现的是业务逻辑组件重用
在Vue中
- model: js中的数据,如对象,数组等.
- View: 页面视图view
- ViewModel: Vue实例化对象
3. 基础
3.1 Vue生命周期
Vue生命周期图
beforeCreate
: 是new Vue()之后触发的第一个钩子, 在当前阶段data、methods、computed以及watch上的数据都不能访问;
created
: 在实例创建完成后发生,当前阶段已经完成数据观测,也就是可以用数据,更改数据,在这里更改数据不会触发updated函数,可以做一些初始数据的获取,在当前阶段无法与DOM进行交互, 如果想要交互, 可以通过vm.$nextTick来访问DOM
beforeMount
: 发生在挂载之前,在这之前tempate模板已导入渲染函数编译。而当前阶段虚拟DOM已经创建完成,即将开始渲染。在此时也尅对数据进行更改,不会触发updated
mounted
在挂载完成后发生,在当前阶段,真实的DOM挂载完毕,数据完成双向绑定,可以访问到DOM节点,使用$refs属性对DOM进行操作
beforeUpdate
: 发生在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前触发,你可以在当前阶段进行数据更改,不会操作重渲染
updated
发生在更新完成之后, 当前阶段组件DOM以完成更新,要注意的是避免在此期间更改数据,因为可能导致无限循环更新
beforeDestory
发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行售后收尾工作,比如清除计时器
destoryed
发生在实例销毁之后,这时候只剩下dom空壳. 组件已被拆解,数据绑定被卸除,监听被移出,自实例也统统被销毁
生命周期列表
相比上面添加了, 三个生命周期activated
、deactivated
、errorCaptured
activated
: 被keep-alive
缓存的组件激活时调用, 该钩子在服务器端渲染期间不被调用
deactivated
: 被keep-alive
缓存的组件停用时调用
errorCaptured
:
- 类型: (err: Error, vm: Component, info: string) => ?boolean
- 详情: 当捕获一个来自子孙组件的错误时被调用。此钩子会接受三个参数: 错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子返回false以阻止该错误继续向上传播
注意: 可以在这个钩子里面修改组件的状态。因此在捕获错误时,在模板或渲染函数中有一条件判断来绕过其他内容就很重要; 不然该组件可能会进入一个无限的渲染循环
错误传播规则
- 默认情况下, 如果全局的
config.errorHandler
被定义,所有的错误扔会发送它, 因此这些错误仍然会向单一的分析服务的地方进行汇报 - 如果一个组件的继承或父级从属链路中存在多个
errorCaptured
钩子,则它们将会被相同错误逐个唤起 - 如果此
errorCaptured
钩子自身抛出一个错误,则这个新错误和原本被捕获的错误都会发送给全局的config.errorHandler
- 一个
errorCaptured
钩子能够返回false
以阻止错误继续向上传播。本质上是说“这个错误已经被搞定了且应该被忽略”。它会阻止其它任何会被这个错误唤起的errorCaptured
钩子和全局的config.errorHandler
。
3.2 声明式渲染
Vue核心就是允许采用简洁的模板语法来声明式地将数据渲染进DOM的系统
通过描述状态和dom之间的映射关系,就可以将状态渲染成dom呈现在用户界面中,也就是渲染到网页上
模板语法在底层的实现上,Vue将模板编译成虚拟DOM渲染函数。结合响应式系统,Vue能够只能地计算出最少需要中心渲染多少组件,并把DOM操作次数减到最少
如果不喜欢虚拟DOM,喜欢原生的JS,可以不用模板,直接写渲染(render)函数,使用可选的JSX语法
如果模板语法中使用表达式:{{ message.split('').reverse().join('') }}
这些表达式会在所属Vue实例的数据作用域下作为JavaScript被解析
这里涉及14中指令中v-text、v-html、v-pre、v-once、v-cloak
<body>
<div id="app">
<!-- 这种差值表达式, 仔细看会有闪动效果: 从 {{message}} 变成 Hello Vue! -->
<div>{{message}}</div>
<!--
v-cloak: 这个指令保持在元素上知道关联实例结束编译,
和CSS规则`[v-cloak]{display: none}`一起使用,
这个指令可以隐藏未编译的Mustache标签知道实例准备完毕(不会显示,直到编译结束)
于是这种办法闪动问题
-->
<style> [v-cloak] { display: none;}</style>
<div v-cloak>{{message}}</div>
<!-- v-text: 的值会把div中的数据全部替换,部分替换,只能差值表达式,没有闪动 -->
<div v-text="message"></div>
<!--
v-once: 只渲染元素和组件一次. 后面的重新渲染,元素,组件及其所有的子节点将被视为静态内容跳过
这个可以优化性能(只会渲染一次,后面的响应式直接跳过)
-->
<div v-once>{{message}}</div>
<!--
v-html: 更新元素的innerHTML,
注意: 内容按照普通的HTML插入-不会作为Vue模板编译
所以data里面的html模板直接插入下面, 里面的{{message}}不会编译
-->
<div v-html="html"></div>
<!--v-pre: 跳过这个元素和子元素编译过程, 可以用来显示原始数据与标签, 跳过大量没有指令的节点加快编译-->
<div v-pre>{{message}}</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
// 基于querySelector获取视图容器: 指定的容器不能是html与body标签
el: '#app',
data() {
return {
message: 'Hello Vue!',
html: `
<div>{{message}}</div>
`
}
}
})
</script>
</body>
注意上面的message是响应式的, 修改下面data里面的message,上面的div里面的内容也会相应的修改
浏览器渲染成
<div id="app">
<div>Hello Vue!</div>
<div>Hello Vue!</div>
<div>Hello Vue!</div> <div>
<div>{{message}}</div>
</div>
<div>Hello Vue!
</div>
<div>{{message}}</div>
</div>
3.3 属性、class和Style绑定
3.3.1 绑定属性
指令v-bind
: 动态绑定属性:v-bind:attribute = "value"
, 简写:attribute="value"
<body>
<div id="app">
<div v-bind:>123</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
message: 'Hello Vue!'
}
}
})
</script>
</body>
浏览器渲染成
<body>
<div id="app">
<div >123</div>
</div>
</body>
3.3.2 绑定class
对象语法与数组语法、还有个组件上使用请看下面
<body>
<style>
.active { color: red }
</style>
<div id="app">
<!-- 注意如果属性是 'text-danger' 连接类型需要加上引号-->
<div v-bind:class="{active: isActive, 'text-danger': hasError}">
绑定类型在内联样式上
</div>
<!-- 如果已经有class属性, 则会追加 -->
<div class="static"
v-bind:class="{active: isActive, 'text-danger': hasError}">
456
</div>
<!-- 2. 绑定类名在对象上 -->
<div class="static" :class="classObj">绑定类型在对象上</div>
<!-- 3. 绑定返回计算属性, 这是一个非常强大的模式 -->
<div class="static" :class="classObject">类名在计算属性上</div>
<!-- 4. 绑定类名在给数组-->
<div :class="[activeClass, errorClass]">类名绑定在数组上</div>
<!-- 根据条件切换类名 -->
<div :class="[isActive ? activeClass:'', errorClass]">根据条件切换数组类名</div>
<!-- 根据条件写,如果太多逻辑很负责, 所以也可以在数组中使用对象 -->
<div :class="[{active: isActive}, errorClass]">数组中使用对象语法切换</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
isActive: true,
hasError: false,
error: null,
classObj: {
active: true,
'text-danger': false,
},
activeClass: 'active',
errorClass: 'text-danger'
}
},
computed: {
classObject() {
return {
// !this.error = null 返回true
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type == 'fatal'
}
}
}
})
</script>
</body>
浏览器渲染效果
<div id="app">
<div class="active">绑定类型在内联样式上</div>
<div class="static active">456</div>
<div class="static active">绑定类型在对象上</div>
<div class="static active">类名在计算属性上</div>
<div class="active text-danger">类名绑定在数组上</div>
<div class="active text-danger">根据条件切换数组类名</div>
<div class="active text-danger">数组中使用对象语法切换</div>
</div>
组件上使用
<body>
<div id="app">
<my-component
class="baz boo"
:class="{ active: isActive}">
</my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component', {
template:`<div class="foo bar">class绑定在组件上</div>`
})
new Vue({
el: '#app',
data() {
return {
isActive: true
}
}
})
</script>
</body>
浏览器效果
<div id="app">
<div class="foo bar baz boo active">class绑定在组件上</div>
</div>
3.3.3 绑定style
v-bind:style
使用有个好处, 当需要添加浏览器引擎前缀的CSS property
时,Vue.js会自动侦测并添加相应的前缀
从2.3.0起可以为style
绑定中的property
提供一个包含多个值的数组,常用于提供多个带前缀的值
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
这样写只会渲染数组中最后一个呗浏览器支持的值, 如果浏览器一个一个不支持前缀,那么就会渲染display: flex
<body>
<style>
</style>
<div id="app">
<!-- 如果属性名是两个连接在一起的: 属性名可以写成小驼峰式或者用引用引起来 -->
<div :style="{color: activeColor, fontSize: fontSize}">style属性绑定</div>
<div :style="{color: activeColor, 'font-size': fontSize}">style属性绑定</div>
<!-- 直接绑定到一个样式对象更好, 代码更清晰 -->
<div :style="styleObj">把属性与属性值写入对象中</div>
<!-- 返回对象的计算属性 -->
<div :style="styleObject"> 把属性用计算属性返回</div>
<!-- 绑定多个类名在数组, 与class优点区别, 因为class是一个类名, 而这个是多个属性 -->
<div :style="[styleObject]">绑定多个类名在数组</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
activeColor: 'red',
fontSize: '20px',
styleObj: {
color: 'red',
fontSize: '20px',
'border-bottom': '1px solid red'
}
}
},
computed: {
styleObject() {
return {
color: 'red',
fontSize: '20px'
}
}
}
})
</script>
</body>
浏览器渲染效果
<div id="app">
<div style="color: red; font-size: 20px;">style属性绑定</div>
<div style="color: red; font-size: 20px;">style属性绑定</div>
<div style="color: red; font-size: 20px; border-bottom: 1px solid red;">
把属性与属性值写入对象中
</div>
<div style="color: red; font-size: 20px;"> 把属性用计算属性返回</div>
<div style="color: red; font-size: 20px;">绑定多个类名在数组</div>
</div>
3.4 条件与循环
14个指令上面已经接触了6个: v-text、v-html、v-pre、v-once、v-cloak、v-bind
循环与条件指令: v-if、v-else、v-else-if、v-for、v-show
3.4.1 条件渲染
<body>
<div id="app">
<div v-if="isShow">显示</div>
<div v-else>隐藏</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
isShow: true
}
}
})
</script>
</body>
一般用于<template>
元素上使用v-if
条件渲染分组
<body>
<div id="app">
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>A | B</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
type: 'C'
}
}
})
</script>
</body>
v-show
用于根据条件展示元素的选项, , 与v-if
不同的是的元素始终会被渲染并保留在DOM中,v-show
只是简单地切换元素的CSS属性display
注意: v-show
不支持<template>
元素,也不支持v-else
v-if
与v-show
区别
v-if
是"真正"的条件渲染,因为他会确保在切换过程中条件内的事件监听和子组件适当地被销毁和重建
v-if
也是懒惰的: 如果在初始渲染时条件为假,则什么也不做, 一直到条件第一次变为真时,才会开始渲染条件块
相比之下,v-show
就简单很多,不管初始条件是什么,元素总是会被渲染,并且只是简单的基于css进行切换
一般来说, v-if
有更高的开销,而v-show
有更高的初始渲染开销。因此如果需要非常频繁的切换,则使用v-show
较好; 如果在运行条件很少改变,则使用v-if
较好
3.5.2 列表渲染
可以使用v-for
指令基于一个数组或对象来渲染一个列表。
渲染的是数组item
就是代表被迭代的数组元的每一项元素的别名, index
代表的是当前项的索引值,可以省略
渲染的是对象value
必选, 代表当前被迭代的属性值,;name
可选, 代表当前被迭代的属性名;index
可选, 当前项的索引
注意: 遍历对象时, 会按Object.keys()
的结果遍历,但是不能保证它的结果在不同的JavaScript引擎下都一致
当Vue正在更新使用v-for
渲染的元素列表时, 它默认使用"就地更新"策略。如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素,确保他们在每个索引位置正确渲染。这个模式是高效的, 但是只适用于不依赖子组件状态或DOM状态(例如: 表单输入值)的列表渲染输出
。为了给Vue一个提示,以便它能耿总每个节点的身份,从而重用和重新排序现有的元素,需要为每项提供一个唯一的key
属性
注意: 不要使用对象或者数组之类的非基本类型值作为v-for
的key
尽可能在使用v-for
时提供key
属性,除非遍历输出的DOM内容非常简单或者刻意依赖默认行为以获取性能的提升
因为它是Vue识别节点的一个通用机制, key
并不仅与v-for
特别关联
<body>
<div id="app">
<div class="arr" v-for="(item, index) in arr" v-bind:key="index">
{{item.name}} -- {{index}}
</div>
<div class="obj" v-for="(value, name, index) of obj" v-bind:key="id">
{{name}} -- {{value}} -- {{index}}
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
arr: [
{ name: '张三' },
{ name: '李四' }
],
obj: {
id: 1,
title: '科技',
author: 'lisi'
}
}
}
})
</script>
</body>
v-for
可以在<template>
上使用, 来循环渲染一段包含多个元素的内容,v-if
也可以用在<template>
上, 但是v-show
与v-else
不支持<template>
- 不推荐同时使用
v-if
与v-for
, 当他们两一起使用的时候,v-for
比v-if
更高的优先级, 这就意味着v-if
将分别重复运行于每个v-for
循环中; 如果想要跳过for
循环的优先级, 可以将v-if
置于外层元素(或者<template>
)上
3.5 conputed与watch
3.5.1 计算属性
模板内的表达式非常便利,但是设计他们的初衷是用于简单的运算。在模板中放入太多的逻辑会让模板过重且难以维护
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
上面模板不在是简单的声明逻辑。你必须看一段时间才能意识到,这里是想要显示变量message
的反转字符串.当你想要在模板中多包含此处的反转字符串时,就会更加难以处理, 所以对于任何复杂逻辑,应该使用计算属性
<body>
<div id="app">
<div>{{message}}</div>
<div>计算属性: {{reverMessage}}</div>
<div>方法: {{reverMethodsMessage()}}</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello'
},
computed: {
// 计算属性的getter
reverMessage() {
// 这里this指向Vue的实例
return this.message.split('').reverse().join('')
}
},
methods: {
reverMethodsMessage() {
return this.message.split('').reverse().join('')
}
}
})
</script>
</body>
但是方法也可以达到这个结果, 我们可以将同一函数定义为一个方法而不是计算属性。两种方式最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的
。只有在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要message
还没有发生改变,对此访问reverdMessage
计算属性会立即返回之前的计算结果,而不必再次执行函数
着也意味着下面的计算属性将不再更新,因为Date.noe()不是响应式依赖
computed: {
now: function () {
return Date.now()
}
}
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数
我们为什么需要混存呢? 假设我们有一个性能开销比较大的计算属性A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于A。如果没有缓存,我们将不可避免的多次执行A的getter, 如果不希望有缓存,就用方法替代
3.5.2 setter
计算属性默认只有getter,不过在需要时可以提供setter
<body>
<div id="app">
计算属性: {{fullNames}}
<button v-on:click="handle">点我</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
firstName: 'foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
methods: {
handle: function() {
this.firstName = 'GOO'
}
},
computed: {
fullNames: {
get() { // getter
return this.firstName + this.lastName
},
set(val) { // setter
var name = val.split(' ')
this.firstName = name[0]
this.lastName = name[name.length -1]
}
}
}
})
</script>
</body>
3.5.3 侦听器
Vue提供一种更通用的方式来观测和相应Vue实例上的数据变动: 侦听属性
。当你有一些数据需要随着其他数据变动而变动时,你很容易滥用watch
, 然而,通常更好的做法是使用计算属性而不是命令式的watch
回调
虽然大部分的情况下计算属性更合适, 但是有时也需要一个自定义的侦听器。这就是为什么Vue通过watch
选项提供一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作的时候,这个方法更适合
<body>
<div id="app">
侦听器:
{{fullName}}
{{firstName}}
<button v-on:click="handle">点我</button>
计算属性: {{fullNames}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
firstName: 'foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
methods: {
handle: function() {
this.firstName = 'GOO'
}
},
watch: {
firstName(val) {
this.fullName = val + ' ' + this.lastName
},
lastName(val) {
this.fullName = this.lastName + ' ' + val
}
},
computed: { // 这种的比侦听器相比好的多, 上面代码重复
fullNames() {
return this.firstName + this.lastName
}
}
})
</script>
</body>
4. 事件处理
4.1 事件处理方法
前面已经接触了14个指令中的11个, 现在使用v-on
指令监听事件,并在触发时运行一些JavaScript代码
监听事件
<body>
<div id="app">
{{count}}
<button v-on:click="count++">点我</button>
<button v-on:click="handleAdd">事件处理方法</button>
<button v-on:click="handle(3)">内联处理器中的方法</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
count: 0
}
},
methods: {
handleAdd() {
this.count += 1
},
handle(val) {
console.log(val)
this.count += val
}
}
})
</script>
</body>
有时候也需要在内联语句处理中访问原始的DOM事件, 可以用特殊变量$event
把它传入方法
<body>
<div id="app">
<button type="submit" v-on:click="handle('Hello', $event)">提交表单</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
methods: {
handle(val, event) {
if(event) event.peventDefault()
console.log(event)
}
}
})
</script>
</body>
4.2 事件修饰符
在事件处理程序中调用event.preventDefault()
或eventPropagation()
是非常常见的需求。尽管我们可以在方法中实现这点, 但跟高的当时是: 方法只有纯粹的数据逻辑, 而不是去处理DOM事件细节
为了处理这个问题, Vue.js为v-on
提供事件修饰符: .stop
、.prevent
、.capture
、.self
、.once
、.passive
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面,阻止表单默认提交事件 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
不要把.passive
与.prevent
一起使用, 因为.prevent
将会被忽略, 同时浏览器可能打印出警告。.passive
会告诉浏览器你不想阻止事件的默认行为
为什么在HTML中监听事件?
因为所有的 Vue.js 事件处理方法和表达式都严格绑定在当前视图的 ViewModel 上,它不会导致任何维护上的困难。实际上,使用 v-on
有几个好处:
- 扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。
- 因为你无须在 JavaScript 里手动绑定事件,你的 ViewModel 代码可以是非常纯粹的逻辑,和 DOM 完全解耦,更易于测试。
- 当一个 ViewModel 被销毁时,所有的事件处理器都会自动被删除。你无须担心如何清理它们。
5. 表单
5.1 表单使用
第13个指令: v-model
v-model
指令在表单<input>
、<textarea>
、<select>
元素上创建双向绑定。他会根据控件类型自动选取正确的方式来更新元素。但是v-model
本质上是v-bind
与input
事件的语法糖而已。它负责监听用户输入事件以更新数据。
<body>
<div id="app">
<input type="text" v-on:input="handle" v-bind:value="message">
{{message}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data() {
return { message: '123'}
},
methods: {
handle(e) {
this.message = e.target.value
}
}
})
</script>
</body>
v-model
会忽略所有表单元素value
、checked
、selected
属性的初始值,总是将Vue实例的数据作为数据来源
v-model
在内部为不同的输入元素使用不同的属性并抛出不同的事件:
- text和textarea元素使用
value
属性和input
事件 - checkbox和radio使用
checked
属性和change
事件 - select字段将
value
作为prop并将change
作为事件
注意: 多行文本<textarea>{{text}}</textarea>
, 不会生效, 应用v-model
来代替
<body>
<div id="app">
<form action="https://www.baidu.com/">
<div>
<span>姓名: </span>
<span>
<input type="text" v-model="uname">
</span>
</div>
<div>
<span>性别: </span>
<span>
<input type="radio" id = 'male' v-model="gender" value = '1'>
<label for='male'>男</label>
<input type="radio" id='monmale' v-model = "gender" value = '2'>
<label for='monmale'>女</label>
</span>
</div>
<div>
<span>爱好: </span>
<input type="checkbox" id = 'ball' v-model="hobby" value="1">
<label for = 'ball'>篮球</label>
<input type="checkbox" id='sing' v-model="hobby" value="2">
<label for="sing">唱歌</label>
<input type="checkbox" id = 'code' v-model="hobby" value="3">
<label for = "code">写代码</label>
</div>
<div>
<span>职业: </span>
<select v-model="occupation">
<option value="1" >选择职业...</option>
<option value="2">教师</option>
<option value="3">软件工程师</option>
<option value="4">律师</option>
</select>
</div>
<div class = "item">
<span style = "vertical-align: top;">职业: </span>
<select v-model="occupation1" multiple="multiple">
<option value="1" >选择职业...</option>
<option value="2">教师</option>
<option value="3">软件工程师</option>
<option value="4">律师</option>
</select>
</div>
<div>
<span style = "vertical-align: top;">个人简介: </span>
<textarea rows="10px" cols="20px" style="resize:none;overflow: hidden;"
v-model="desc">
</textarea>
</div>
<div>
<input type="submit" value = "提交" @click.prevent="handle"/>
</div>
</form>
</div>
<script type="text/javascript" src="../vue.js"></script>
<script type="text/javascript">
let vm = new Vue({
el: '#app',
data: {
uname: '123',
gender: 1,
hobby: [],
occupation: 1,
occupation1: [1,2],
desc: 'nihao'
},
methods: {
handle: function() {
console.log(this.hobby.toString()+ '---'+this.occupation);
}
}
});
</script>
</body>
5.2 修饰符
.lazy
在默认情况下, v-model
在每次input
事件触发后将输入框的值与数据进行同步, 可以添加lazy
修饰符,从而转为change
事件(失去交单触发)
.number
如果想自动将用户输入的值转为数值类型, 可以给v-model
添加number
修饰符, 这通常很有用, 因为即使在type="number"
时,HTML输入元素的值也总会返回字符串。如果这个值无法被parseFloat()
解析, 则会返回原始值
trim
如果要自动过滤用户输入的首尾空白字符,可以给v-model
添加trim
修饰符
<input v-model.lazy="msg">
<input v-model.number="age" type="number">
<input v-model.trim="msg">
6. 神奇的模板语法
在底层的实现上, Vue将模板语法编译成虚拟DOM渲染函数。结合响应式系统,Vue能够智能地计算出最少需要重新渲染多少组件,并把DOM操作次数减到最少
<body>
<div id="app">
{{count}} <button @click="handle(2)">点我+2</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data() {
return {
count: 1
}
},
methods: {
handle(e) {
console.log(e)
this.count += e
}
}
})
// 输出Vue替我们生成的渲染函数
console.log(vm.$options.render)
</script>
</body>
输出:
(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"app"}},[_v("\n "+_s(count)+" "),_c('button',{on:{"click":function($event){return handle(2)}}},[_v("点我+2")])])}
})
<body>
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data() {
return {
count: 1
}
},
methods: {
handle(e) {
console.log(e)
this.count += e
}
},
render() { // 这个是上面#app模板语法里面编译, 生成的渲染函数, 执行它也能得到模板一样的结果
with(this){
return _c('div',{attrs:{"id":"app"}},[_v("\n "+_s(count)+" "),_c('button',{on:{"click":function($event){return handle(2)}}},[_v("点我+2")])])
}
}
})
</script>
</body>
7. 组件基础
7.1 组件使用
在注册一个组件的时候,需要给组件一个名字, 该组件名就是Vue.component
的第一个参数, 给与组件的名字可能依赖于你打算拿它干什么。当直接在DOM中使用一个组件(不是字符串模板或单文件组件)的时候, 组钉子组件名(字母全小写且必须包含一个连字符)。这会避免和当前以及未来的HTML元素起冲突
7.2 全局组件
全局注册的组件可以用在被注册之后的任何(通过 new Vue
)新创建的Vue根实例, 也包括其组件树中的所有子组件的模板中
<body>
<div id="app">
<!-- 组件复用 -->
<button-count></button-count>
<button-count></button-count>
<button-count></button-count>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('button-count', {
data() {
return { count: 0 }
},
methods: {
handle(e) {
this.count += e
}
},
template: `<div>{{count}}<button @click="handle(2)">点我加2</button></div>`
})
new Vue({
el: '#app',
})
</script>
</body>
组件是可复用的Vue实例, 且带有一个名字: 上面例子中<button-count>
。我们可以通过new Vue()
创建的Vue根实例中,把这个组件作为自定义元素使用
因为组件是可复用Vue实例, 所以他们与new Vue
接收相同选项,例如: data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像el
这样根实例特有的选项
一个组件的data选项必须是一个函数, 因此每个实例可以维护一份被返回对象对立的拷贝
7.3 局部组件
全局注册往往是不够理想的。比如,如果使用一个像webpack这样的构建系统,全局注册所有的组件意味着即便你已经不在需要这个组价了, 它仍然会被包含在你最终的构建结果中。这造成用户下载的JavaScript的无谓的增加
这种情况下, 可以通过一个普通的JavaScript对象来定义组件
var HelloTom = { // 局部组件
data() {
return {
msg1: 'Hell0 局部组件'
};
},
template: `<div>{{msg1}}</div>`
};
let vm = new Vue({
el: '#app',
components: {// 局部组件
'hello-tom': HelloTom,
}
});
7.4 prop
所有的prop都使得其父子prop之间形成一个单向下行绑定: 父级prop的更新会向下流动到子组件中,但是反过来则不行。这样会阻止从子组件意外变更父级组件的状态,从而导致你的应用的数据流难以理解, 另外每次父组件发生变更时,子组件中所有的prop都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变prop。如果这样做了, Vue会在浏览器的控制台发生警告
props验证
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
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
}
}
}
})
当prop验证失败的时候, 开发环境下Vue将会产生一个控制台警告
7.4 父组件向子组件传值
Prop
是你可以在组件上注册的一些自定义属性,当一个值传递给一个prop属性的时候,它就变成那个组件实例的一个property
一个组件默认可以拥有任意数量的prop,任何值都可以传递给任何prop。在下面模板中能够在组件实例中访问这个值, 就像访问data
中的值一样
<body>
<div id="app">
<button-count :count="count"></button-count>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('button-count', {
props: ['count'],
template: `<div>{{count}}<button>点我加2</button></div>`
})
new Vue({
el: '#app',
data() {
return {
count: 0
}
}
})
</script>
</body>
7.5 子组件向父组件传值
子组件触发自定义事件("handle-count"), 同时父组件监听自定义组件(v-on:handle-count="handleAdd"), 当子组件触发的时候会抛向父组件,父组件触发会执行相应的动作
<body>
<div id="app">
<button-count :count="count" v-on:handle-count="handleAdd" v-on:handle-add2="handleAdd2"></button-count>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('button-count', {
props: ['count'],
methods: {
handleAdd2() {
this.$emit('handle-add2', 10)
}
},
template: `<div>
{{count}}
<button @click="$emit('handle-count', 5)">子组件向父组件传值</button>
<button @click="handleAdd2">子组件向父组件传值</button>
</div>`
})
new Vue({
el: '#app',
data() {
return {
count: 0
}
},
methods: {
handleAdd(e) {
this.count += e
},
handleAdd2(e) {
this.count += e
}
}
})
</script>
</body>
7.6 组件小技巧
7.6.1 组件双向绑定v-model
<input v-model="searchText">
<!-- 等价于 -->
<input :value="searchText" :input="searchText=$event.target.value">
<!-- 当用在组件上时, `v-model`则就这样 -->
<custom-input v-bind:value="searchText" v-on:input="searchText=$event"/>
为了让这个组件内的<input>
必须:
- 将其
value
属性绑定到一个名叫value
的prop上 - 在将其
input
事件被触发时, 将新的值通过自定义的input
事件抛出
<body>
<div id="app">
{{value}}
<!-- 当子组件自定义事件名为input, 则可以把:value与@input 简写成v-model="value" -->
<my-input :value="value" @input-text="handleInput"></my-input>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-input', {
props: {
value: {
default: '',
type: String
}
},
template: `
<div>
<input type="text" :value="value" @input="handle">
</div>
`,
methods: {
handle(e) {
this.$emit('input-text', e.target.value)
}
}
})
new Vue({
el: '#app',
data() {
return {
value: '12'
}
},
methods: {
handleInput(e) {
console.log(e)
}
}
})
</script>
</body>
7.6.2 .sync修饰符
有些情况下我们需要对一个prop进行"双向绑定"。但是真正的双向绑定会带来维护上的问题, 因为子组件可以变更父组件, 且在父组件和子组件都没有明显的变更来源, 这也是推荐update:myPropName
的模式触发事件取而代之。例如, 在一个包含value
prop的组件中, 我们可以用一下方法表达对其赋新值的意图
this.$emit('update:value', this.value+2) // this.value+2 与下面父组件中的 $event 对应
然后父组件可以监听那个事件, 并根据需要更新的一个本地数据property
<my-component :value="value" @update:value="value=$event"></my-component>
为了方便, 可以缩写成
<my-component :value.sync="value" ></my-component>
注意: .sync
修饰符的v-bind
不能和表达式一起使用(v-bind:)
, 这个是无效的。取而代之的是, 你只能提供你想要绑定的property名, 类似v-model
当我们对一个对象同时设置多个prop的时候, 也可以将这个.sync
修饰符和v-bind
配合使用
<my-component v-bind.sync="doc"></my-component>
这会把doc
对象中的每一个property(如: title)都作为一个独立的prop传进去, 然后各自添加用于更新的v-on
鉴定器
<body>
<div id="app">
<div>父组件value: {{value}}</div>
<!-- 子组件里面的this.value + 2 就是等于父组件中的$event -->
<!-- <my-component :value="value" @update:value="value=$event"></my-component> -->
<my-component :value.sync="value" ></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component', {
props: ['value'],
template: `
<div>
<div>子组件value: {{value}}</div>
<button @click="clickAdd(2)">点我加+2</button>
</div>
`,
methods: {
clickAdd(e) {
this.$emit('update:value', this.value+2)
}
}
})
new Vue({
el: '#app',
data() {
return {
value: '12'
}
},
handle(e) {
console.log(e)
}
})
</script>
</body>
7.6.3 .native与$listener
想要在组件的根元素上直接监听一个原生事件。这时可以使用v-on
的.native
的修饰符
<base-input v-on:focus.native="onFocus"></base-input>
<body>
<div id="app">
{{value}}
<!--
.native父组件给子组件绑定一个原生事件, 将子组件变成普通的html标签
加上.native 表单获取焦点就会立马触发focus事件,e就是input的对象, 也就是把focus变成原生事件
不加.native 那么focus就是自定义的事件, 不会立马触发changeInput方法, 那么e就是自定义事件的参数
如果子组件里面自定义了focus自定义事件, 那么就会忽略
-->
<my-input :value="value" @focus.native="changeInput" @input-text="clickInput"></my-input>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-input', {
template: `
<input type="text" :value="value" @input="handle">
`,
props: ['value'],
methods: {
handle(e) {
console.log(e.target.value)
// 这里面的e是 input输入框对象, 如果不加.native就是value值
this.$emit('input-text', e.target.value)
}
}
})
new Vue({
el: '#app',
data() {
return {
value: ''
}
},
methods: {
changeInput(e) {
// 输出的是input对象
console.log(e)
},
clickInput(e) {
// 输出的是input-text事件的参数
this.value = e
}
}
})
</script>
</body>
上面到代码有时候非常有用, 不过在监听一个类似<input>
的非常特定的元素时,这并不是个好方法。比如上面的<base-input>
组件可能做如下重构, 所以根元素实际上是<label>
元素
<label>
{{ label }}
<input v-bind="$attrs" v-bind:value="value"
v-on:input="$emit('input', $event.target.value)" >
</label>
<body>
<div id="app">
{{value}}
<!-- @focus 不会触发, .native是给子组件的根组件设置监听事件, 那就是label所以不生效-->
<my-input :value="value" @focus.native="changeInput" @input-text="clickInput"></my-input>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-input', {
template: `
<label> 用户名:
<input type="text" :value="value" @input="handle">
</label>
`,
props: ['value'],
methods: {
handle(e) {
console.log(e.target.value)
// 这里面的e是 input输入框对象, 如果不加.native就是value值
this.$emit('input-text', e.target.value)
}
}
})
new Vue({
el: '#app',
data() {
return {
value: ''
}
},
methods: {
changeInput(e) {
// 输出的是input对象
console.log(e)
},
clickInput(e) {
// 输出的是input-text事件的参数
this.value = e
}
}
})
</script>
</body>
这时, 父级的.native
监听器就会失败。不会产生任何报错, 但是onFocus
处理函数不会如预期被调用, 解决这个问题, Vue可以提供一个$listener
属性, 它是一个对象, 里面包含了作用在这个组件上的所有监听器
当组件的根元素不具备一些DOM事件, 但是根元素内部具有相对应的DOM事件, 那么就可以使用$listeners获取父组件传递进来的所有事件函数, 在通过v-on="xxx"绑定到相对应的内部元素上就可以
{
focus: function (event) { /* ... */ }
input: function (value) { /* ... */ },
}
有了这个$listeners
属性, 就可以配合v-on="$listeners"
将所有的事件监听器指向这个组件的某个特定的子元素。对于类似<input>
的希望也能配合v-model
工作的组件来说, 为这些监听器创建一个类似下面inputListeners
的计算属性是非常有用的
<body>
<div id="app">
{{value}}
<my-input :value="value" @input-text="clickInput"></my-input>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-input', {
template: `
<label> 用户名:
<input type="text" :value="value" v-on="inputeners">
</label>
`,
props: ['value'],
methods: {
handle(e) {
console.log(e.target.value)
}
},
computed: {
inputeners() {
console.log(this)
var vm = this
/*
参数2: 从父级添加所有的监听器: 父组件身上当前的所有事件
参数3: 对象, 自定义事件, 直接给到子元素身上
*/
return Object.assign({}, this.$listeners, {
input(e) {
vm.$emit('input-text', e.target.value)
},
focus(e) {
console.log(e)
}
})
}
},
mounted() {
// 父组件添加的所有事件都在这
console.log(this.$listeners)
}
})
new Vue({
el: '#app',
data() {
return {
value: ''
}
},
methods: {
clickInput(e) {
// 输出的是input-text事件的参数
this.value = e
}
}
})
</script>
</body>
现在<my-input>
组件是一个完全透明的包裹器, 也就是他可以像一个的<input>
元素一样使用了: 所以跟它相同的属性和监听器都可以工作, 不必在使用.native
7.9 插槽
7.9.1 基本插槽
现在引入最后一个指令, 也是就第14个指令: v-slot
Vue实现了一套内容分发的API, 将<slot>
元素作为承载分发内容的出口, 他允许向下面一样合成组件
<navigation-link url="/profile">
Your Profile
</navigation-link>
<!-- 在navigation-link的模板中可以写成这样 -->
<a v-bind:href="url" class="nav-link">
<slot></slot>
</a>
当上面的组件渲染的时候<slot>
将会被替换成"Your Profile"。插槽内可以包含任何模板代码, 包括HTML, 甚至是其他的组件
插槽slot
中间大部分都是没有内容的, 有时候为插槽设置具体的内容还是很有用的,他只会在没有提供内容的时候被渲染
声明: 子组件的插槽内容, 是显示在父组件中间
<button type="submit">
<slot></slot>
</button>
现在可能希望这个<button>
内绝大多数情况下都渲染文本"Submit", 为了将"Submit"作为后备内容, 我们可以将它放在<slot>
标签内
<!-- 父组件 -->
<subimt-button></subimt-button>
<!-- 子组件 -->
<button type="submit">
<slot>Submit</slot>
</button>
注意上面父组件的标签中间并没有提供任何的插槽内容, 后备内容"Submit"将会被渲染
<button type="submit">Submit</button>
如果父组件提供内容, 就会覆盖默认内容
<submit-button>Save<submit>
则这个提供的内容将会被渲染从而取代后备内容
<button type="submit">Save</button>
7.9.2 具名插槽
<div class="container">
<header>
<!-- 我们希望把页头放这里 -->
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
</footer>
</div>
如上面的情况, 我们需要多个插槽, 对于这种情况, <slot>
元素有一个特殊的属性: name, 这个属性可以定义而外的插槽
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
一个不带有name
的<slot>
出口会带有隐含的名字"default"; 在向具名插槽提供内容的时候, 我们可以在一个<template>
元素上使用v-slot
指令, 并以v-slot
的参数的形式提供其名称
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
现在template
元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有v-slot
的<template>
中的内容都会被视为默认插槽内容, 然而, 如果希望更明确一些,仍然可以在一个<template>
中的内容都会被视为默认插槽内容
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
注意: v-slot
只能添加在<template>
上
7.9.3 作用域插槽
有时候让插槽内容能够访问子组件中的数据是很有用的。
<!--
user属性是子组件的, 如果父组件来访问则代码不会运行
只有<current-user>组件中才能访问到`user`, 而我们提供的内容是在父级渲染
-->
<current-user>{{user.firstName}}</current-user>
为了让user
在父级的插槽内容中可用, 我们可以将user
作为<slot>
元素的一个属性绑定上去
<span>
<slot v-bind:user="user"></slot>
</span>
绑定在<slot>
元素上的属性被称为插槽prop。现在在父级作用域中, 我们可以使用带值的v-slot
来定义我们提供的插槽prop的名字
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
上面试默认插槽的情况下, 组件的标签才可以被当做插槽的模板来使用。这样我们就可以把v-slo
直接用在组件上
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
上面的写法还可以更简单
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
</current-user>
注意默认插槽的缩写语法不能和具名插槽混用,因为他会导致作用域不明确
<!-- 无效,会导致警告 -->
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
<template v-slot:other="otherSlotProps">
slotProps is NOT available here
</template>
</current-user>
只要出现多个插槽, 请时钟为所有的插槽使用完整的基于<template>
的语法
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</current-user>
结构插槽Prop
作用域插槽的内部工作原理是将你的插槽内容包括在一个传入单个参数的函数里
function (slotProps) { // 插槽内容 }
这以为这v-slot
的值实际上可以是任何能够作为函数定义中的参数的JavaScript表达式。所以在支持的环境下(单文件组件或现代浏览器), 可以使用ES2015解构来传入具体的插槽prop
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
这样可以使模板更简洁,尤其是在该插槽提供了多个 prop 的时候。它同样开启了 prop 重命名等其它可能,例如将 user
重命名为 person
:
<current-user v-slot="{ user: person }">
{{ person.firstName }}
</current-user>
你甚至可以定义后备内容,用于插槽 prop 是 undefined 的情形:
<current-user v-slot="{ user = { firstName: 'Guest' } }">
{{ user.firstName }}
</current-user>
动态插槽
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
v-slot缩写
v-slot
也有缩写, 即把参数之前的内容(v-slot:
)替换为字符串#
, 例如: v-slot:header
可以缩写为#header
<base-layout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
然而,和其它指令一样,该缩写只在其有参数的时候才可用。这意味着以下语法是无效的:
<!-- 这样会触发一个警告 -->
<current-user #="{ user }">
{{ user.firstName }}
</current-user>
如果你希望使用缩写的话,你必须始终以明确插槽名取而代之:
<current-user #default="{ user }">
{{ user.firstName }}
</current-user>
7.9.4 可复用插槽
插槽prop允许我们将插槽转换为可复用的模板, 这些木板可以基于输入的prop渲染出不同的内容, 这在设计封装数据逻辑同时允许组件自定义部分布局的可复用组件时是最有用的
<ul>
<li v-for="todo in filteredTodos" v-bind:key="todo.id">
{{ todo.text }}
</li>
</ul>
我们可以将每个todo作为父级组件的插槽, 以此通过父级组件对其进行控制, 然后todo
作为插槽prop进行绑定
<ul>
<li v-for="todo in filteredTodos" v-bind:key="todo.id">
<!--
我们为每个 todo 准备了一个插槽, 将 `todo` 对象作为一个插槽的 prop 传入。
-->
<slot name="todo" v-bind:todo="todo">
<!-- 后备内容 -->
{{ todo.text }}
</slot>
</li>
</ul>
现在当我们使用 <todo-list>
组件的时候,我们可以选择为 todo 定义一个不一样的 <template>
作为替代方案,并且可以从子组件获取数据:
<todo-list v-bind:todos="todos">
<template v-slot:todo="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>
7.10 动态组件
在多标签界面切换不同的组件, 当这些组件之间的切换的时候, 会想保持这些组件的状态, 以避免反复重渲染导致的性能问题
有时间在这个页面操作了, 然后切换页面, 又切换回来, 就会发现页面被重新渲染了, 并不是上回操作的内容, 这是一位每次切换的时候, Vue都会创建一个新实例
重新创建动态组件行为通常是非常有用的, 但是有些时候, 我们更希望组件实例能够被在它们第一次创建的时候缓存下来。为了解决这个问题, 我们可以用一个<keep-alive>
元素将其动态包裹起来
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
这样component组件都会被缓存起来, 不管怎么切换, 还是切换之前的操作内容
注意: 这个<keep-alive>
要求被切换到的组件都有自己的名字, 不论是通过name
选项还是局部/全局注册
keep-alive详情参考
7.11 异步组件
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
如你所见,这个工厂函数会收到一个 resolve
回调,这个回调函数会在你从服务器得到组件定义的时候被调用。你也可以调用 reject(reason)
来表示加载失败。这里的 setTimeout
是为了演示用的,如何获取组件取决于你自己。一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})
你也可以在工厂函数中返回一个 Promise
,所以把 webpack 2 和 ES2015 语法加在一起,我们可以这样使用动态导入:
Vue.component(
'async-webpack-example',
// 这个动态导入会返回一个 `Promise` 对象。
() => import('./my-async-component')
)
当使用局部注册的时候,你也可以直接提供一个返回 Promise
的函数:
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
处理加载状态
这里的异步组件工厂函数也可以返回一个如下格式的对象:
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
注意如果你希望在 Vue Router 的路由组件中使用上述语法的话,你必须使用 Vue Router 2.4.0+ 版本。
7.12 处理边界情况
7.12.1 访问元素 & 组件
在绝大多数情况下,我们最好不要触达另一个组件实例内部或手动操作 DOM 元素。不过也确实在一些情况下做这些事情是合适的。
访问根实例
在每个 new Vue
实例的子组件中,其根实例可以通过 $root
property 进行访问。例如,在这个根实例中
<body>
<div id="app">
<div>父组件访问: {{foo}}</div>
<one-component @button-text="handleAdd"></one-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('one-component', {
template: `
<div>
<button @click="handle">点我</button>
</div>
`,
methods: {
handle() {
// 访问根组件数据, 还可以访问计算属性, 调用根方法
// 这种对于小型的应用或者demo很适合, 但是大型的应用还是vuex吧
console.log(this.$root.foo)
// 如果层次很深可以: this.$parent.$parent.$parent...
console.log(this.$parent) // 访问的就是#app
this.$emit('button-text', this.$root.foo+ '123')
}
}
})
new Vue({
el: '#app',
data() {
return {
foo: 'Hello World'
}
},
methods: {
handleAdd(e) {
this.foo += e
}
}
})
</script>
</body>
所有的子组件都可以将这个实例作为一个全局 store 来访问或使用
注意: 对于 demo 或非常小型的有少量组件的应用来说这是很方便的。不过这个模式扩展到中大型应用来说就不然了。因此在绝大多数情况下,我们强烈推荐使用 Vuex 来管理应用的状态
访问父组件实例
$parent
property可以用来从一个子组件访问父组件的实例。它提供一种机会, 可以在后期随时触达父级组价, 以替代将数据以prop的方式传入子组件的方式
注意: 在绝大多数情况下,触达父级组件会使得你的应用更难调试和理解,尤其是当你变更了父级组件的数据的时候。当我们稍后回看那个组件的时候,很难找出那个变更是从哪里发起的
案例在上面$root里面
访问子组件实例或子元素
尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,你可以通过 ref
这个属性为子组件赋予一个 ID 引用
ref被用来给元素或子组件引用信息。引用信息将会注册在父组件的$refs对象上。如果在普通的DOM元素元素上使用,引用指向的就是DOM元素; 如果用在子组件上, 引用就是指向组件
<base-input ref="usernameInput"></base-input>
现在在你已经定义了这个 ref
的组件里,你可以使用
this.$refs.usernameInput
来访问这个 <base-input>
实例,以便不时之需。比如程序化地从一个父级组件聚焦这个输入框。在刚才那个例子中,该 <base-input>
组件也可以使用一个类似的 ref
提供对内部这个指定元素的访问
<input ref="input">
甚至可以通过其父级组件定义方法:
<body>
<div id="app">
<p ref="p" @click="handle">Hello World</p>
<base-input ref="usernameInput" @focus.native="focus"></base-input>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('base-input', {
template: `<input type="text" placeholder="请输入用户名">`
})
new Vue({
el: '#app',
data() {
return {
}
},
methods: {
focus() {
// ref在组件上, 引用就是指向组件
console.log(this.$refs.usernameInput)
},
handle() {
// ref在dom元素上, 获取的就是dom
console.log(this.$refs.p)
}
}
})
</script>
</body>
当 ref
和 v-for
一起使用的时候,你得到的 ref 将会是一个包含了对应数据源的这些子组件的数组。
注意: $refs
只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问 $refs
依赖注入
<body>
<div id="app">
<div>父组件: {{msg}}</div>
<my-son></my-son>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-son', {
template: `
<div>
<div>我是子组件</div>
<my-son-son></my-son-son>
</div>
`,
components: {
'my-son-son': {
inject: ['msg'], // 方法一: 依赖注入
template: `
<div>
<div>我是孙子组件, 我需要拿取爷爷的值: {{msg}}</div>
<div>我是孙子组件, 我需要拿取爷爷的值: {{data}}</div>
</div>
`,
data() {
return { data: '' }
},
mounted() { // 方法2: 一直点上去父组件拿取数据, 如果层级多不真实
this.data = this.$parent.$parent.msg
}
// msg是根组件的数据, 还可以: this.$root.msg
}
}
})
new Vue({
el: '#app',
provide() {
return { msg: this.msg }
},
data() {
return {
msg: 'Hello World'
}
}
})
</script>
</body>
如果祖先级别的组件需要和后代通信那么$parent
就不真实了, 使用 $parent
property 无法很好的扩展到更深层级的嵌套组件上。这也是依赖注入的用武之地,它用到了两个新的实例选项:provide
和 inject
。
provide
选项允许我们指定我们想要提供给后代组件的数据/方法。
provide() {
return { msg: this.msg }
},
然后在任何后代组件里,我们都可以使用 inject
选项来接收指定的我们想要添加在这个实例上的 property:
inject: ['msg']
实际上,你可以把依赖注入看作一部分“大范围有效的 prop”,除了:
- 祖先组件不需要知道哪些后代组件使用它提供的 property
- 后代组件不需要知道被注入的 property 来自哪里
然而,依赖注入还是有负面影响的。它将你应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。同时所提供的 property 是非响应式的。这是出于设计的考虑,因为使用它们来创建一个中心化规模化的数据跟使用 $root
做这件事都是不够好的。如果你想要共享的这个 property 是你的应用特有的,而不是通用化的,或者如果你想在祖先组件中更新所提供的数据,那么这意味着你可能需要换用一个像 Vuex 这样真正的状态管理方案了。
7.12.2 程序化的事件侦听器
现在知道$emit
的用法, 它可以被v-on
侦听, 但是Vue实例同时在其事件接口中提供了其他的方法, 我们可以:
- 通过
$on(eventName, eventHandler)
侦听一个事件 - 通过
$once(eventName, eventHandler)
一次性侦听一个事件 - 通过
$off(eventName, eventHandler)
停止侦听一个事件
当你需要在一个组件实例上手动侦听事件时, 它们是派的上用场。他们也可以用于代码组织工具
// 一次性将这个日期选择器附加到一个输入框上
// 它会被挂载到 DOM 上。
mounted: function () {
// Pikaday 是一个第三方日期选择器的库
this.picker = new Pikaday({
field: this.$refs.input,
format: 'YYYY-MM-DD'
})
},
// 在组件被销毁之前,
// 也销毁这个日期选择器。
beforeDestroy: function () {
this.picker.destroy()
}
这里有两个潜在的问题:
- 它需要在这个组件实例中保存这个
picker
,如果可以的话最好只有生命周期钩子可以访问到它。这并不算严重的问题,但是它可以被视为杂物。 - 我们的建立代码独立于我们的清理代码,这使得我们比较难于程序化地清理我们建立的所有东西。
你应该通过一个程序化的侦听器解决这两个问题:
mounted: function () {
var picker = new Pikaday({
field: this.$refs.input,
format: 'YYYY-MM-DD'
})
this.$once('hook:beforeDestroy', function () {
picker.destroy()
})
}
使用了这个策略,我甚至可以让多个输入框元素同时使用不同的 Pikaday,每个新的实例都程序化地在后期清理它自己
mounted: function () {
this.attachDatepicker('startDateInput')
this.attachDatepicker('endDateInput')
},
methods: {
attachDatepicker: function (refName) {
var picker = new Pikaday({
field: this.$refs[refName],
format: 'YYYY-MM-DD'
})
this.$once('hook:beforeDestroy', function () {
picker.destroy()
})
}
}
7.13 Vue.set
向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi'
)
使用方法: vue.set(target, propertyName/index, value)
7.14 Vue.delete
删除对象的属性。如果对象是响应式的,确保删除能触发更新视图
使用方法: Vue.delete(target, propertyName/index)
7.15 $bus
通过在Vue原型上添加一个Vue实例作为事件总线, 实现组件间相互通信, 而且不受组件间关系的影响
Vue.prototype.$bus = new Vue()
这样做可以在任意组建中使用this.$bus
访问到该Vue实例
7.15 组件化理解
组件化是Vue的精髓, Vue应用就是由一个个组件构成的。面试的时候经常会被问道谈一下对Vue组件化的理解
定义: 组件是可复用的Vue实例, 准确讲他们是VueComponent的实例, 继承自Vue
优点: 从上面的案例可以看出组件化可以增加代码的复用性、可维护性和可测性
使用场景: 什么时候使用组件?
- 通用组件: 实现最基本的功能, 具有通用性、复用性,例如按钮组件、输入框组件、布局组件等
- 业务组件: 他们完成具体业务, 具有一定的复用性, 例如登录组件、轮播图组件
- 页面组件: 组织应用各部分对立内容, 需要时在不同页面组件间切换, 例如列表页、详情页
如何使用组件
- 定义: VUe.component()、component选项、sfc
- 分类: 有状态组件、functional, abstract
- 通信: props、emit/on、provide/inject、children/parent/root/attrs/$listeners
- 内容分发:
<slot>
、<template>
、v-slot
- 使用及优化: is、keep-alive、异步组件
组件的本质
Vue中的组件经历如下过程: 组件配置 -> VueComponent实例 -> render() -> Vireual DOM -> DOM, 所以组件的本质时产生虚拟DOM
8. 过滤器
Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind
表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示:
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
在组件的选项中定义本地过滤器
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
或者在创建Vue实例之前全局定义过滤器
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
new Vue({
// ...
})
当全局过滤器和局部过滤器重名的时候, 会采用局部过滤器
9. 自定义指令
除了核心功能默认内置的指令 (v-model
和 v-show
),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
如果想注册局部指令,组件中也接受一个 directives
的选项:
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
然后你可以在模板中任何元素上使用新的 v-focus
property,如下
<input v-focus>
<body>
<div id="app">
<input type="text" v-focus>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.directive('focus', {
inserted(el) {
el.focus()
}
})
new Vue({
el: '#app'
})
</script>
</body>
9.1 自定义指令钩子
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
-
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 -
inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 -
update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。 -
componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。 -
unbind
:只调用一次,指令与元素解绑时调用。
接下来我们来看一下钩子函数的参数 (即 el
、binding
、vnode
和 oldVnode
)。
钩子函数参数
指令钩子函数会被传入以下参数:
-
el
:指令所绑定的元素,可以用来直接操作 DOM。 -
binding
: 一个对象,包含以下 property: -
name
:指令名,不包括v-
前缀。value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
-
vnode
:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。 -
oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
注意: 除了 el
之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset
来进行。
10. 混入
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
<body>
<div id="app">
<my-text></my-text>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const myMixin = {
data() {
return {
message: 'Hello',
foo: 'abc'
}
},
created() {
this.hello()
},
methods: {
hello() {
console.log('Hello Mixin')
}
}
}
Vue.component('my-text', {
// 混入到my-text组件中
mixins: [myMixin],
// 这里data数据与mixins同名会把mixin里面的数据覆盖
data() {
return {
message: 'Vue',
foo: 'def'
}
},
created() {
console.log(this.$data)
},
template: `
<div>
one two three
</div>
`
})
new Vue({
el: '#app',
})
</script>
</body>
当组件和混入对象含有同名选项时, 这些选项以恰当的方式进行"合并", 比如, 数据对象在内部会进行递归合并, 并在发生冲突时以数组数据优先
全局注册混入
混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})
// => "hello!
11 jsx与渲染函数
<body>
<div id="app">
<my-render :level="1">Hello GO</my-render>
<my-render :level="2">Hello C++</my-render>
<my-render :level="3">Hello JavaScript</my-render>
<my-render :level="4">Hello Java</my-render>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// $slots 用来访问插槽分发的内容
Vue.component('my-render', {
props: {
level: {
type: Number,
required:true
}
},
render(createElement) {
return createElement(
'h' + this.level,
this.$slots.default
)
}
})
new Vue({
el: '#app',
data() {
return {
}
}
})
</script>
</body>
浏览器渲染
<div id="app">
<h1>Hello GO</h1>
<h2>Hello C++</h2>
<h3>Hello JavaScript</h3>
<h4>Hello Java</h4>
</div>
Vue通过建立一个虚拟DOM来追踪自己要如何改变真实DOM
createElement
到底会返回什么呢?其实不是一个实际的 DOM 元素. 它更准确的名子可能是createNodeDescription,因为它所包含的信息会告诉Vue页面上需要渲染什么样的节点,包括及其子节点的描述信息. 我们把这样的节点称为"虚拟节点(Virtual node)", 也简称为"VNode". "虚拟DOM"是我们对由Vue组件树建立起来的整个VNode树的称呼
createElement参数
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',
// {Object}
// 一个与模板中 attribute 对应的数据对象。可选。
{
// (详情见下一节)
},
// {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
深入数据对象
有一点要注意:正如 v-bind:class
和 v-bind:style
在模板语法中会被特别对待一样,它们在 VNode 数据对象中也有对应的顶层字段。该对象也允许你绑定普通的 HTML attribute,也允许绑定如 innerHTML
这样的 DOM property (这会覆盖 v-html
指令)。
11 动画
11.1 简单使用
vue在插入、更新或者移除DOM时,提供各种不同方式的应用过渡效果,包括以下工具:
- 在css过渡和动画中自动应用class
- 可以配合使用第三方css动画库,如Animate.css
- 在过渡钩子函数中使用JavaScript直接操作DOM
- 可以配合使用第三方JavaScript动画库, 如Velocity.js
单元素/组件的过渡
Vue提供transition
的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
- 条件渲染(使用v-if)
- 条件展示(使用v-show)
- 动态组件
- 组件根节点
<body>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>
<div id="app">
<button @click="isShow = !isShow">切换</button>
<transition name="fade">
<p v-if="isShow">Hello World</p>
</transition>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
isShow: true
}
}
})
</script>
</body>
在插入或删除包含在transition
组件中的元素时, Vue将会做一下处理
- 自动嗅探目标元素是否应用了CSS过渡或动画, 如果是,在恰当的实际添加/删除CSS类名
- 如果过渡组件提供JavaScript钩子函数,这些钩子函数将在恰当的时机被调用
- 如果没有找到JavaScript钩子并且也没有检测到css动画/过渡,DOM操作(插入/删除)在下一帧中立即执行(注意: 此浏览器逐帧动画机制),和Vue的nextTick概念不同)
11.2 过渡类名
在进入/离开的过渡中,会有6个class切换
v-enter
定义进入过渡的开始状态. 在元素被插入之前生效, 在元素被插入之后的下一帧移除v-enter-active
定义进入过渡生效时的状态. 在整个进入过渡阶段中应用, 在元素被插入之前生效,在过渡/动画之后移除. 这个类可以被用来定义进入过渡的过程事件,延迟和曲线函数v-enter-to
定义进入过渡的结束状态. 在元素被插入之后下一帧生效(于此同时v-enter
被移除), 在过渡/动画完成之后移除v-leave
定义离开过渡的开始状态.在离开过渡被触发时立刻生效,下一帧移除v-leave-active
定义离开过渡生效时的状态. 在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效, 在过渡/动画完成之后移除. 这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数v-leave=to
离开过渡的结束状态. 在离开过渡被触发之后下一帧生效(与此同时v-leave
被删除), 在过渡/动画完成之后移除
对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>
,则 v-
是这些类名的默认前缀。如果你使用了 <transition name="my-transition">
,那么 v-enter
会替换为 my-transition-enter
。
v-enter-active
和 v-leave-active
可以控制进入/离开过渡的不同的缓和曲线,
<body>
<style>
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active for below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
}
</style>
<div id="app">
<button @click="show = !show">Toggle</button>
<transition name="slide-fade">
<p v-if="show">Hello World</p>
</transition>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
show: true
}
}
})
</script>
</body>
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!