这篇文章是接着我的上部vue学习笔记写的,之所以分开写这两篇文章是因为我意识到不知道什么时候就写了非常多的字了,所以我决定拆分来写,这一篇我一定写得比上一篇还要简洁争取用更少的字更通俗易懂的解释让大家都能学会甚至精通vue
过度&动画
Vue 提供了 transition 的封装组件用于封装需要过渡的组件
Vue过渡的类名
v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
v-enter-to: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
v-leave: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
v-leave-to: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。
这样就会实现一个非常简单的小小动画,给大家详细解释一下
v-enter 动画开始之前的状态 这里设置 opacity 0 , v-enter-to 动画的结束状态,但是会从v-enter被移除一直到动画结束才会被移除,然后结合v-enter-active设置的transition就可以完美的实现动画,可能大家还有疑惑,那么我这样设置动画你懂了吧
<style>
/* 这两个代表显示,文字慢慢显示出来 */
.v-enter {
opacity: 0;
}
.v-enter-to {
opacity: 1;
}
.v-enter-active {
/* 结合此类设置的transition,vue动画就实现了,懂了吗 */
transition: all 300s ease 0s;
}
/* 这两个代表离开自然起始状态是1,结束透明度为0呗 */
.v-leave {
opacity: 1;
}
.v-leave-to {
opacity: 0;
}
.v-leave-active {
/* 我决定装个怪 */
transition: all .3s ease 0s;
}
</style>
Vue提供的自定义标签transition提供了一个name属性,用来替换vue动画中的css类名中的v
<div id="app">
<button type="button" @click="show = !show">切换显示隐藏</button>
<!-- transition提供一个name属性,里面的值可以替换css动画类名的v -->
<transition name="bounce">
<p v-show="show">这是一个需要被过渡的元素</p>
</transition>
</div>
都是有点基础的,所以我这下只贴核心代码了
可以使用transition的一些属性定义对应的类名
- enter-class
- enter-active-class
- enter-to-class (2.1.8+)
- leave-class
- leave-active-class
- leave-to-class (2.1.8+)
每一个属性代表一个vue动画类名,这对于我们使用第三方动画库非常有用,这里我们可以将vue官方的例子copy下来直接用
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
Vue的transition中提供了一个duration属性,用于指定动画的完成时间,如果不设置vue可以默认计算出对应的动画的完成时间,但是当我们显示指定动画的完成时间之后,vue就会在对应的指定的时间内完成动画,算了说不清看例子
<body>
<div id="app">
<button @click="show = !show">
Toggle render
</button> <br>
<transition :duration="500">
<p v-if="show">这是一个测试的文字</p>
</transition>
</div>
<style>
p {
position: absolute;
left: 50px;
top: 50px;
}
.v-enter,
.v-leave-to {
opacity: 0;
left: 500px;
transform: scale(2);
}
.v-enter-to,
.v-leave {
opacity: 1;
transform: scale(1);
left: 50px;
}
.v-enter-active,.v-leave-active{
transition: all 3s;
}
</style>
上面这个例子中我故意将duration设置为500(ms),然后过渡的时间为3s,这就代表着当你开始动画时vue会以你设置的500ms为动画时间所以实际上当动画了500ms之后vue就会执行动画之后的操作也就是
opacity 0 left500px scale2 ,大致上就是动画到你设置的duration时之后就会直接干嘣到对应的样式中,大家直接试一下就行了
同时duration可以指定更细致的进入和离开动画的时间
<transition :duration="{enter:1000,leave:3000}">
<p v-if="show">这是一个测试的文字</p>
</transition>
这样设置你会发现元素就类似于进入动画为1s会干嘣,但是离开动画正好3s不会干嘣(最后小伙伴们肯定想问,那么我的duration设置大于transition的过渡时长会怎么办呢?那肯定是动画全程过渡完成,然后再等一段时间才能触发另外一种动画呗,因为vue会以你设置的duration为基准进行判断动画的执行情况)你实在不懂,直接设置一次,动画的时候看动画的类名就行了
动画钩子函数
vue在动画从开始到结束的这一过程中,提供了许多钩子函数方便我们更好的实现我们的逻辑,具体如下
- before-enter
- enter
- after-enter
- enter-cancelled --> 说说这个吧,就是当你正在进行开始动画的时候,触发了某种条件需要执行结束动画,这时候这个钩子函数生效)
- before-leave
- leave
- after-leave
- leave-cancelled -->(此钩子函数在v-if中无效,只能用于v-show)
直接小小演示一遍就行了,这里需要使用v-on监听这些事件,在transition组件中监听
<body>
<div id="app">
<button @click="show = !show">
Toggle render
</button> <br>
<transition @before-enter="beforeEnter" @enter="Enter" @after-enter="afterEnter"
@enter-cancelled="enterCancelled" @before-leave="beforeLeave" @leave="Leave" @after-leave="afterLeave"
@leave-cancelled="leaveCancelled">
<p v-show="show">这是一个测试的文字</p>
</transition>
</div>
<style>
p {
position: absolute;
left: 50px;
top: 50px;
}
.v-enter,
.v-leave-to {
opacity: 0;
left: 500px;
transform: scale(2);
}
.v-enter-to,
.v-leave {
opacity: 1;
transform: scale(1);
left: 50px;
}
.v-enter-active,
.v-leave-active {
transition: all .3s linear 0s;
}
</style>
<script src="../lib/vue.js"></script>
<script>
new Vue({
data: {
show: true
},
methods: {
// 动画准备中,每个函数都有一个el参数,用来指示正在动画的元素
beforeEnter() {
console.log('beforeEnter');
},
// 动画准备完成
Enter(el,done) {
console.log('Enter');
setTimeout(() => {
done();
}, 1000);
},
afterEnter() {
console.log('afterEnter');
},
enterCancelled() {
console.log('enterCancelled');
},
beforeLeave() {
console.log('beforeLeave');
},
Leave(el,done) {
console.log('Leave');
setTimeout(() => {
done();
}, 1000);
},
afterLeave() {
console.log('afterLeave');
},
leaveCancelled() {
console.log('leaveCancelled');
}
},
}).$mount("#app");
</script>
</body>
(每一个钩子函数都有一个可选的el,代表正在动画的元素)
这里其实一切按道理来说都是没问题的,Vue为Enter和Leave两个钩子函数提供了一个可选的done()回调,这个回调有什么用呢?
这是一个贼坑的东西,一个正常的动画流程是 beforeEnter-->Enter-->afterEnter,因为Enter有一个done回调,代表着如果你不调用done()回调那么动画就不知道什么时候完成,所以在你不调用done的回调的时候你会发现,钩子函数afterEnter不会调用,当你切换动画的时候就是enterCancelled函数调用了,这就是因为Vue只有在你调用了done函数之后才认为动画已经执行结束了,然后当你直接调用done()函数就会出现干嘣的情况,为什么呢?(很坑啊)就是因为Enter()函数是在动画准备完成的时候调用,这不就代表着动画一准备完成就done(动画不就完成了吗?)所以就干嘣了,那就只能苦逼的写setTimeOut了吗?不是的,在纯CSS3动画的时候我们只需要不接收这个done函数一切就正常了,像这样
一旦选择接受done函数,就必须承担就收done函数需要做的事情,标记动画什么时候结束,这主要是用于js实现动画的,看看下面的小例子
new Vue({
el: '#example-4',
data: {
show: false
},
methods: {
beforeEnter: function (el) {
el.style.opacity = 0
el.style.transformOrigin = 'left'
},
enter: function (el, done) {
Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
Velocity(el, { fontSize: '1em' }, { complete: done })
},
leave: function (el, done) {
Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 })
Velocity(el, { rotateZ: '100deg' }, { loop: 2 })
Velocity(el, {
rotateZ: '45deg',
translateY: '30px',
translateX: '30px',
opacity: 0
}, { complete: done })
}
}
})
大致上就是这样用的
初始渲染过渡
Vue还提供了一个叫做元素的初始渲染,就是元素的进场状态,只会渲染一次,在第一次渲染时
<transition
appear
appear-class="custom-appear"
appear-to-class="custom-appear-to"
appear-active-class="custom-appear-active"
>
p {
position: absolute;
left: 50px;
top: 50px;
}
.custom-appear{
top: 500px;
left: 300px;
}
.custom-appear-to{
left: 50px;
top: 50px;
}
.custom-appear-active{
transition: all 1s linear 0s;
}
这里的四个属性一个都不能少,第一个appear也不能少,这样就形成了一个元素的初始进场动画了,同样的这个appear也是有钩子函数的
- before-appear
- appear
- after-appear
- appear-cancelled
就不显示了都是一样的,appear钩子函数一样有可选的done回调(实测)
多个元素的过渡
有时我们可能需要多个元素之间的过渡,对于多个元素之间的过渡,vue官方有一句话
当有相同标签名的元素切换时,需要通过 key 特性设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容。即使在技术上没有必要,给在 <transition> 组件中的多个元素设置 key 是一个更好的实践。
大致上意思就是,两个相同标签元素进行过渡,必须给每一个标签指定一个唯一的key,不然的话vue会直接替换标签中的内容,不会进行动画
我这里有两个h1
如果不加唯一的key的话,就是干嘣状态,这里说一下transition组件中只能有一个唯一的根元素,我这里两个h1是因为最后只会渲染一个元素出来,渲染两个直接报错了,我们必须使用key指定每个标签,以区分他们,这样才能实现过渡,当然我们可以进行上面这样的写法简写
<div id="app">
<button @click="show=!show">toggle show</button>
<transition name="m" mode="out-in">
<h1 :key="show">
<!-- 当key发生改变,即代表着元素发生改变,然后对应的计算属性发生改变 -->
{{mytext}}
</h1>
</transition>
computed: {
mytext() {
if(this.show){
return "我是第一个";
}else{
return "我是第二个";
}
}
},
大家都是有经验的了,就只贴核心代码了(这个我实在解释不清楚了,大家尽量看吧),vue是以key的不同来区分同标签元素的,那么上面的例子,我们的key发生了变化,就需要执行类似于转换标签那种(key不同标签不同)就需要销毁当前key的标签(销毁就是离场动画嘛)然后重新计算新的标签插入就是进场动画了,在不懂没辙了
过渡模式
transition默认执行动画是enter和leave是同步执行的,但是transition提供了一个mode选项可以让我们选择排队执行动画,这是一个同步执行的例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue组件介绍</title>
</head>
<body>
<div id="app">
<transition>
<button :key="doSomet" @click="doSomet=!doSomet">
{{ msgShow }}
</button>
</transition>
</div>
<style>
button{
position: absolute;
left: 50px;
top: 50px;
}
.v-enter{
transform: translateX(100%);
opacity: 0;
}
.v-enter-to{
transform: translateX(0);
opacity: 1;
}
.v-leave-to{
transform: translateX(-100%);
opacity: 0;
}
.v-enter-active,.v-leave-active{
transition: all 1s linear 0s;
}
</style>
<script src="../lib/vue.js"></script>
<script>
new Vue({
data: {
doSomet:false
},
computed: {
msgShow() {
if(this.doSomet){
return "打开";
}else{
return "关闭";
}
}
},
methods: {
}
}).$mount("#app");
</script>
</body>
</html>
如果我们需要排队执行动画的话,transition有一个mode属性实现了这个功能,此属性一共有两个选项
- in-out 先进后出
- out-in 先出后进
这两个相比默认的同步执行的差别还是蛮大的,都是排队执行动画,一个动画结束才执行后一个
多个组件间的过渡
多个组件之间的过渡我们只需要使用动态组件就行了,因为动态组件渲染出来就是一个唯一的组件
<transition name="component-fade" mode="out-in">
<component v-bind:is="view"></component>
</transition>
<body>
<div id="app">
<button @click="view=='ani_a'?view='ani_b':view='ani_a'">toggle show</button>
<transition name="com-fade" mode="out-in">
<component :is="view"></component>
</transition>
</div>
<style>
.com-fade-enter,.com-fade-leave-to{
opacity: 0;
}
.com-fade-enter-to,.com-fade-leave{
opacity: 1;
}
.com-fade-enter-active,.com-fade-leave-active{
transition: all 1s linear;
}
</style>
<script src="../lib/vue.js"></script>
<script>
new Vue({
data: {
view:'ani_a'
},
components:{
'ani_a':{
template:"<h1>我是动画a组件</h1>"
},
'ani_b':{
template:"<h1>我是动画b组件</h1>"
}
}
}).$mount("#app");
</script>
列表过渡
接下来介绍一下怎样同事渲染整个列表,vue有提供一个新的组件叫做
transition-group
,这个组件有几个特点
- 不同于
<transition>
,它会以一个真实元素呈现:默认为一个<span>
。你也可以通过tag
特性更换为其他元素。 - 过渡模式不可用,因为我们不再相互切换特有的元素。
- 内部元素 总是需要 提供唯一的
key
属性值。
下面是一个官方的一个transition-group简单使用
<body>
<div id="list-demo" class="demo">
<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-item">
{{ item }}
</span>
</transition-group>
</div>
<style>
.list-item {
display: inline-block;
margin-right: 10px;
}
.list-enter-active,
.list-leave-active {
transition: all 1s;
}
.list-enter,
.list-leave-to
{
opacity: 0;
transform: translateY(30px);
}
</style>
<script src="../lib/vue.js"></script>
<script>
new Vue({
el: '#list-demo',
data: {
items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
nextNum: 10
},
methods: {
randomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
},
}
})
</script>
</body>
列表的排序过渡
vue有一个v-move特性,它会在元素的改变定位的过程中应用。这个属性跟vue的一些动画类名差不多,当
transition-group
加上name时也会改变其前缀,大家可以看看下面的例子
<body>
<div id="app">
<button type="button" @click="shuffle">shuffle</button>
<transition-group tag="ul">
<li v-for="item of items" :key="item">
{{item}}
</li>
</transition-group>
</div>
<style>
.v-move{
transition: all 1s;
}
</style>
<script src="../lib/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
<script>
new Vue({
data() {
return {
items:[1,2,3,4,5,6,7,8,9]
}
},
methods: {
shuffle(){
this.items = _.shuffle(this.items);
}
},
}).$mount("#app");
</script>
</body>
接下来我们可以将v-move属性结合之前的小例子让插入数值时,列表的选项可以平滑的改变而不是干嘣,我们完全只需要在之前的例子中添加一个样式就行了
.list-move{
transition: all .3s;
}
这样我们就实现了插入一个数字时其他的条目是平滑移动的,然后我们继续在这基础上融合两个例子,只需要在html中添加一个按钮
<button type="button" @click="shuffle">shuffle</button>
在methods中添加对应的方法就行了,
shuffle(){
this.items = _.shuffle(this.items);
}
//记得添加对应的lodash库
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
两个例子就这样融合了,大家可以改变一个transition-group
渲染的p标签样式
p{
display: block;
width: 400px;
}
然后尝试一下,接下来我们继续体验这个v-move属性
>需要注意的是使用 v-move 过渡的元素不能设置为 display: inline 。作为替代方案,可以设置为 display: inline-block 或者放置于 flex 中
同样的我们也可以使用v-move特性实现一个类似于表格的打乱平滑过渡,下面的例子最好大家看看注释,有一个超级大坑在(反正我是花了近半天的时间迈过,看不看自己决定)
<body>
<div id="app">
<button @click="shuffle">动起来</button>
<!--
刚开始做这个例子的时候,transition-group使用了tag为table标签
然后是在tr,td中vFor的(那时用的二维数组),结果就是无法访问
vm的数据,后来测试其他标签都是正常的(div,p进行访问),然后
推测tr,td之类的标签只能用在table标签中使用,可能是因为vue先解析的是
标签之中的一些计算数据吧,然后发现不在table标签中包着就失效了
大家看我下面的例子,我使用td包裹内容,但是页面却并没有打印td标签
或者我直接在html中写一个td,实时证明,tr,td必须在table下否则无法显示的
-->
<table>
<tr>
<td></td>
</tr>
</table>
<!-- 不会显示 -->
<td></td>
<tr></tr>
<!-- ul li为什么行因为,不用ul,li也可以单独存在啊 -->
<li></li>
<!--
所以使用transition-group切记切记这个超级无敌大坑,
某些必须依赖父亲的标签千万不要写在这里面,不然你就算transition-group
tag改成必须依赖的父元素也是不行的,切记切记
昨天就是因为这个超级大坑,导致到现在才明白,继续更新这个动画
-->
<transition-group tag="div">
<span v-for="val in items" :key="val.id">
<td>{{val.number}}</td>
</span>
</transition-group>
</div>
<style>
.v-move {
transition: all 1s ease-in-out;
}
div{
width: 288px;
}
span {
display: inline-block;
width: 30px;
height: 30px;
font-size: 15px;
line-height: 30px;
text-align: center;
border: 1px solid #ccc;
border-collapse: collapse;
user-select: none;
}
</style>
<script src="../lib/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
<script>
new Vue({
data() {
return {
items: Array.apply(null, { length: 81 })
.map(function (_, index) {
return {
id: index,
number: index % 9 + 1
}
})
}
},
methods: {
shuffle() {
this.items = _.shuffle(this.items);
}
},
}).$mount("#app");
</script>
</body>
列表的交错过渡
什么叫做交错过渡呢,就是比如说一个列表可以错开时间进行动画类似于这样的效果,有时候你会发现错开时间的动画比不错开时间的动画更加棒
<body>
<div id="app">
<input type="text" v-model="query">
<transition-group tag="ul" v-bind:css="false" @before-enter="beforeEnter" @enter="enter" @leave="leave">
<li v-for="(item ,index) in computedList" :key="item.msg" :data-index="index">
{{item.msg}}
</li>
</transition-group>
</div>
<style>
</style>
<script src="../lib/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<script>
new Vue({
data() {
return {
query: '',
list: [
{ msg: "大钢炮" },
{ msg: "明明" },
{ msg: "红红" },
{ msg: "张三" },
{ msg: "李四" },
{ msg: "明红" }
]
}
},
computed: {
computedList() {
let vm = this;
return this.list.filter((item) => {
return item.msg.indexOf(vm.query) !== -1;
});
}
},
methods: {
beforeEnter(el) {
el.style.opacity = 0;
el.style.height = 0;
},
enter(el, done) {
// 计算延迟,交错执行动画
var delay = el.dataset.index * 150
setTimeout(function () {
Velocity(
el,
{ opacity: 1, height: '1.6em' },
{ complete: done }
)
}, delay)
},
leave(el, done) {
var delay = el.dataset.index * 150
setTimeout(function () {
Velocity(
el,
{ opacity: 0, height: 0 },
{ complete: done }
)
}, delay)
}
},
}).$mount("#app");
</script>
</body>
大家直接看这个动画交错执行的小例子吧,这里通过dataset绑定index计算延迟时间,每个html标签都有dataset,例如
<div data-index="80"></div>
//元素实例就可以通过dataset对象访问这个index属性
//每一个我们自定义的属性都会用去除data-前缀的字符串作为属性绑定到dataset上
可复用的过渡
过渡可以通过 Vue 的组件系统实现复用。要创建一个可复用过渡组件,你需要做的就是将 <transition> 或者 <transition-group> 作为根组件,然后将任何子组件放置在其中就可以了,两个小例子大家仔细研究
Vue.component('my-special-transition', {
template: '\
<transition\
name="very-special-transition"\
mode="out-in"\
v-on:before-enter="beforeEnter"\
v-on:after-enter="afterEnter"\
>\
<slot></slot>\
</transition>\
',
methods: {
beforeEnter: function (el) {
// ...
},
afterEnter: function (el) {
// ...
}
}
})
函数式组件
Vue.component('my-special-transition', {
functional: true,
render: function (createElement, context) {
var data = {
props: {
name: 'very-special-transition',
mode: 'out-in'
},
on: {
beforeEnter: function (el) {
// ...
},
afterEnter: function (el) {
// ...
}
}
}
return createElement('transition', data, context.children)
}
})
动态过渡
在 Vue 中即使是过渡也是数据驱动的!动态过渡最基本的例子是通过 name 特性来绑定动态值。所有过渡特性都可以动态绑定,但我们不仅仅只有特性可以利用,还可以通过事件钩子获取上下文中的所有数据,因为事件钩子都是方法。这意味着,根据组件的状态不同,你的 JavaScript 过渡会有不同的表现。
关于动态过渡总结一下就是,使用velocity库,对于过渡的一些状态(参数)通过其他的一些特性可以进行动态改变实现不同的过渡效果,或者说在特定的时间段中过渡效果不尽相同,最后直接贴官方的实例代码了,大脑快爆炸了
- v
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<div id="dynamic-fade-demo" class="demo">
Fade In: <input type="range" v-model="fadeInDuration" min="0" v-bind:max="maxFadeDuration">
Fade Out: <input type="range" v-model="fadeOutDuration" min="0" v-bind:max="maxFadeDuration">
<transition
v-bind:css="false"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
>
<p v-if="show">hello</p>
</transition>
<button
v-if="stop"
v-on:click="stop = false; show = false"
>Start animating</button>
<button
v-else
v-on:click="stop = true"
>Stop it!</button>
</div>
- m
new Vue({
el: '#dynamic-fade-demo',
data: {
show: true,
fadeInDuration: 1000,
fadeOutDuration: 1000,
maxFadeDuration: 1500,
stop: true
},
mounted: function () {
this.show = false
},
methods: {
beforeEnter: function (el) {
el.style.opacity = 0
},
enter: function (el, done) {
var vm = this
Velocity(el,
{ opacity: 1 },
{
duration: this.fadeInDuration,
complete: function () {
done()
if (!vm.stop) vm.show = false
}
}
)
},
leave: function (el, done) {
var vm = this
Velocity(el,
{ opacity: 0 },
{
duration: this.fadeOutDuration,
complete: function () {
done()
vm.show = true
}
}
)
}
}
})
可复用性 & 组合
混入
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
var myMixin = {
created() {
this.hello();
},
methods: {
hello: function () {
console.log("hello")
}
}
}
var component = Vue.extend({
mixins:[myMixin]
})
new component();
这就是一个Vue的基本混入代码了
选项合并
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
var myMixin = {
data(){
return {
msg:"hello",
item:[1,2,3,4],
name:'suiyue'
}
}
}
var component = Vue.extend({
mixins:[myMixin],
data:function(){
return {
name:"小钢炮",
age:9,
sex:"男"
}
},
created() {
console.log(this.$data);
}
})
上面的代码输出结果
同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
var myMixin = {
created(){
console.log("混入的钩子函数被调用");
}
}
var component = Vue.extend({
mixins:[myMixin],
data:function(){
return {
name:"小钢炮",
age:9,
sex:"男"
}
},
created() {
console.log("组件钩子函数被调用");
}
})
new component();
值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
var myMixin={
methods: {
foo(){
console.log("foo")
},
sayHello(){
console.log("say Hello")
}
}
}
new Vue({
mixins:[myMixin],
methods:{
bar(){
console.log("bar");
},
sayHello(){
console.log("hello")
}
},
created() {
this.foo();
this.bar();
this.sayHello(); //hello
},
}).$mount("#app");
注意:Vue.extend() 也使用同样的策略进行合并。
全局混入
混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
请谨慎使用全局混入,因为它会影响每个单独创建的 Vue 实例 (包括第三方组件)。大多数情况下,只应当应用于自定义选项,就像上面示例一样。推荐将其作为插件发布,以避免重复应用混入。
自定义选项合并策略 看不懂给个链接吧
自定义指令
除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。一个简单的注册自定义指令的代码如下
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
其中使用到了指令中的钩子函数,如果想注册局部指令,组件中也接受一个 directives 的选项:
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
钩子函数
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
- update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
- unbind:只调用一次,指令与元素解绑时调用。
钩子函数参数
指令钩子函数会被传入以下参数:
-
el
:指令所绑定的元素,可以用来直接操作 DOM 。 -
binding
:一个对象,包含以下属性:-
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 来进行。
动态指令参数
在我们使用自定义指令的时候我们可以动态的传递参数给我们定义的指令
Vue.directive('pin', {//逻辑
})
我使用的时候,v-pin:[myData],那么这里的myData就可以在页面刷新的时候进行动态的更新,那么指令中的处理代码也必须考虑到所有的可能了
函数简写
在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如这样写:
//当触发了bind和update两个钩子的时候调用此函数
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value
})
对象字面量
如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法的 JavaScript 表达式。
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
渲染函数&JSX
基础
Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。
new Vue({
data() {
return {
msg:"Hello Vue"
}
},
// vue可选的render函数替换掉默认的模板
render(createElement) {
return createElement('h1',this.msg)
},
}).$mount("#app");
如果会react的应该知道,render接受的那个方法跟babel转换的jsx语法非常的相似,甚至感觉就是一样的什么都没变(所以这会不会带来某些惊喜)
createElement 参数
createElement的函数的参数可以说是非常复杂,这里我具体给大家直接在一个例子中列出来,我只能说我尽量将注释写出来了,我表示有点懵了,真的有点复杂啊
<body>
<div id="app">
</div>
<script src="../lib/vue.js"></script>
<script>
// 我反正是对这个createEl函数懵了,不知道你呢?
new Vue({
data() {
return {
msg: "Hello Vue"
}
},
// vue可选的render函数替换掉默认的模板
render(createElement) {
return createElement(
// 第一个参数
// {String | Object | Function}
// 可以使一个html标签名,或者一个组件选项对象,或者一个函数返回了前两种(可以是异步)
// {
// // 我这里写一个组件选项对象,这应该是最难理解的吧
// template:"<div>{{this.custom_data}}</div>",
// data(){
// return {
// custom_data:"我喜欢的事物"
// }
// }
// // 应该懂了吧
// },
// 上面注释的就是第一个参数object对象形式,我这里直接写字符串是因为,用了object方式无法正确渲染第三个参数提供的子级Vnode
// 因为提供了Object就只能在template中添加了
'div',
// 第二个参数,用于绑定一些html特性,也允许绑定如 innerHTML 这样的 DOM 属性 (这会覆盖 v-html 指令).
{
// 绑定类名
class: {
// 添加一个foo类
foo: true,
bar: false
},
// 绑定样式
style: {
width: "100px",
height: "200px",
border: "1px solid red",
},
// 普通的html标签属性,如a的href,input的type等等
attrs: {
},
// 组件的props
props: {
// 我这里不传入任何参数所以就不写props了
},
// dom属性
domProps: {
innerHTML: "这是通过domProps对象设置的文字"
},
// 事件监听到on属性中,不在支持修饰符,参数
on: {
click() {
}
},
// 仅用于组件,用于监听原生事件,而不是组件内部使用
// `vm.$emit` 触发的事件。
nativeOn: {
},
// 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
// 赋值,因为 Vue 已经自动为你进行了同步。
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
// 作用域插槽的格式为
// { name: props => VNode | Array<VNode> }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果组件是其它组件的子组件,需为插槽指定名称
slot: 'name-of-slot',
// 其它特殊顶层属性
key: 'myKey',
ref: 'myRef',
// 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
// 那么 `$refs.myRef` 会变成一个数组。
refInFor: true
},
// 第三个参数 {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
// 可以是字符串或者数组,代表子级Vnode
[
'我是子级文本节点',
createElement('p', '我是子级p标签')
]
);
},
}).$mount("#app");
// => "hello!"
</script>
</body>
约束这个直接留个链接在这里吧
使用 JavaScript 代替模板功能也留个链接
(为什么留两个链接,这谁没事记这么多属性的createElement函数啊,直接用JSX了好吧)
函数式组件
我们可以将组件标记为
functional
,这意味它无状态 (没有响应式数据),也没有实例 (没有this
上下文)。
一个函数式组件如下:
Vue.component('my-component', {
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
// ...
}
})
注意:在 2.3.0 之前的版本中,如果一个函数式组件想要接收 prop,则 props 选项是必须的。在 2.3.0 或以上的版本中,你可以省略 props 选项,所有组件上的特性都会被自动隐式解析为 prop。
在2.5以上的版本中,如果你使用了类似于webpack之类的打包工具拆分每一个单独的组件(*.vue文件),那么在每一个单独的.vue文件中生命函数式组件方法
<template functional>
</template>
组件需要的一切都是通过 context 参数传递,它是一个包括如下字段的对象:
-
props
:提供所有 prop 的对象 -
children
: VNode 子节点的数组 -
slots
: 一个函数,返回了包含所有插槽的对象 -
scopedSlots
: (2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。 -
data
:传递给组件的整个数据对象,作为createElement
的第二个参数传入组件 -
parent
:对父组件的引用 -
listeners
: (2.3.0+) 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是data.on
的一个别名。 -
injections
: (2.3.0+) 如果使用了inject
选项,则该对象包含了应当被注入的属性。
那么关于函数式组件大家还需要努力研究,你会发现,Vue的component方法生命函数式组件用的就是render方法,这个重的createElement方法还是得看看,但是函数式组件是直接给你封装好了
<body>
<div id="app">
<my-func-com class="foo" :style="{color:'red',fontSize:'25px'}">
我可以直接在这里定义
<h1>Hello Vue</h1>
</my-func-com>
</div>
<script src="../lib/vue.js"></script>
<script>
Vue.component("my-func-com", {
functional: true,
render(h, context) {
return h(
'div',
context.data,
context.children);
},
})
new Vue({
data() {
return {
msg: "Hello Vue"
}
}
}).$mount("#app");
// => "hello!"
</script>
</body>
所以呢?其实Vue都给你把这些东西封装好了,我们了解一下这个ugly的createElement函数就行了...
插件
使用插件
通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成:
// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
new Vue({
// ...组件选项
})
也可以传入一个可选的选项对象:
Vue.use(MyPlugin, { someOption: true })
Vue.use 会自动阻止多次注册相同插件,届时即使多次调用也只会注册一次该插件。
Vue.js 官方提供的一些插件 (例如 vue-router
) 在检测到 Vue
是可访问的全局变量时会自动调用 Vue.use()
。然而在像 CommonJS 这样的模块环境中,你应该始终显式地调用 Vue.use()
:
// 用 Browserify 或 webpack 提供的 CommonJS 模块环境时
var Vue = require('vue')
var VueRouter = require('vue-router')
// 不要忘了调用此方法
Vue.use(VueRouter)
awesome-vue 集合了大量由社区贡献的插件和库
过滤器
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({
// ...
})
当全局过滤器和局部过滤器重名时,会采用局部过滤器。
过滤器函数总接收表达式的值 (之前的操作链的结果) 作为第一个参数。在上述例子中,capitalize 过滤器函数将会收到 message 的值作为第一个参数。
过滤器可以串联:
{{ message | filterA | filterB }}
过滤器是 JavaScript 函数,因此可以接收参数:
{{ message | filterA('arg1', arg2) }}
这里,filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。
老规矩,一个总结结束
过渡&动画
vue提供的过渡标签
transition
transition-group
过渡的6个类名
- v-enter
- v-enter-to
- v-enter-active
- v-leave
- v-leave-to
- v-leave-active
transition可以通过name属性替换类名的v,同时也可以通过对应的属性直接制定不同时间段的类名用于第三方动画库
- enter-class --- v-enter
- enter-to-class(2.1.8+) --- v-enter-to
- enter-active-class --- v-enter-active
- leave-class --- v-leave
- leave-to-class(2.1.8+) --- v-leave-to
- leave-active-class --- v-leave-active
transition同时提供了一个duration属性,用于指定动画的完成事件,可以直接指定一个数字(ms),同时可以是一个对象
{enter:'',leave:''}
,这里会有一个干嘣和duration大于实际动画时间延迟执行另外的动画切记
动画的钩子函数
- before-enter
- enter
- after-enter
- enter-cancelled
- before-leave
- leave
- after-leave
- leave-cancelled
每一个钩子函数都有可选的一个参数el,用于指向正在动画的实例,同时enter和leave也有一个可选done回调(接收done要承担接收done的责任)
初始渲染过渡
就是元素的第一次渲染的进场过渡,只会在第一次渲染时执行一次
- appear ---- 必加属性
- appear-class
- appear-to-class
- appear-active-class
appear没有默认的v-appear一说啊,必须使用上面的transition属性指定类名才行
当然进场过渡也有钩子函数
- before-appear
- appear
- after-appear
- appear-cancelled
多个元素的过渡
大家一定要记住,多个元素如果有相同的标签名的元素过渡必须加上不同的key否则无过渡效果
transition默认进场离场动画是同时进行的,我们可以通过制定mode
属性可选的先进后出(in-out)还是先出后进(out-in)
多个组件间的过渡
通过Vue提供的component
组件通过动态改变is属性进行组件间的过渡切换
列表过渡
transiton-group
这个组件内部的元素每一个都必须有一个唯一的key否则不显示数据,并且记住那个超级大坑
排序过渡 v-move特性,name同样可以改变前面的v
交错过渡 原本同步动画的元素通过设置不同时间的延迟错开时间进行动画
可复用过渡 直接将transition封装到一个组件中,这样就实现了一个可以复用的过渡,可以是函数式组件和类组件,直接通过钩子函数设置过渡的一些样式的改变
动态过渡 通过JavaScript动态设置过渡的一些样式,可以无时无刻或者在不同的时间点过渡效果不进相同的动画
可复用行&组合
混入 Vue实例组件中都有mixins属性,所以可以在组件或者vue实例中混入,如果有相同的选项会被合并掉,如果发生冲突优先选择组件内部定义的数据,同名生命周期钩子函数将被合并为一个数组,触发对应函数两个函数都会被调用先触发混入的钩子或者生命周期函数
全局混入 使用Vue.mixin进行全局混入,全局混入会影响每一个Vue实例,new Vue啊还是组件啊都会影响
自定义指令
全局注册
Vue.directive
局部注册每一个Vue实例中的directives
选项
指令的钩子函数
- bind
- inserted
- update
- componentUpdated
- unbind
钩子函数参数
- el
- binding
- name
- value
- oldValue
- expression
- arg
- modifiers
- vnode --> 虚拟节点 --> 所有的虚拟节点组成虚拟DOM
- oldVnode
动态指令参数 我们可以直接使用自定义指令动态绑定指令的参数 v-pin:[],当然组件中必须有处理参数所有可能性的逻辑
函数简写 可以直接在声明指令的时候传入一个函数,这个函数会在bind和update时调用
对象字面量 可以给指令的value传入一个对象,进入指令直接binding.value.xxx调用对应的参数
渲染参数&JSX
可以使用一个可选的render函数替换Vue默认的template,render函数有一个必须的createElement函数,这个函数极其复杂,大家直接看上面写的吧
函数式组件,函数式组件使用render实现的,但是函数式组件封装了render方法,提供了第二个参数context,这个context封装了createElement需要的值所以我们不必写createElement的所有参数了,也不需要记,webpack打包环境生命函数式组件只需要在template标签上添加一个functional
就行了
context参数包括如下对象
- props --> obj
- children -->arr
- slots -->func
- scopedSlots(2.6+)
- data --> obj
- parent
- listeners(2.3+) obj
- injections(2.3+)
插件:直接使用Vue.use()就可以使用插件
过滤器
过滤器也分全局和局部注册,全局:
Vue.filter
局部:filters
,过滤器就是可以用来在数据被插入页面的最后一层过滤,比如说我们可以使用过滤器来格式化时间啊变成 (yyyy-mm-dd)这种格式
这里想说的一些话,大概花了3天的时间通看了一遍vue的官方文档记下此笔记,可能大家看完会发现为什么大多数特性都只有代码,我只想说对我而言大多数地方看代码就知道怎么回事了,就像有的地方,我无法解释清楚就会直接贴上代码,就明白了,就像我看vue官方文档一样,有些东西vue官方说的很那啥的一看代码就懂了,还有就是为什么写下这个笔记,因为vue官方文档可能写得有点权威吧,有很多东西都是一笔带过,那么我对于某些东西都说的很大白了,也将踩过的坑给记录了下来,还有就是贴代码同时也代表学习一定要敲一遍,只不过有些地方贴的官方的,有点那啥的!目前还有vue-router,vuex的学习笔记准备开写中,希望大家继续支持我!
(如果你想跟我讨论前端或者对于前端学习有什么疑惑的可以加一下此QQ群:78484-----5854
----> 感谢支持)