![418339af6f56922bae30e9607b134a4f.png](https://img-blog.csdnimg.cn/img_convert/418339af6f56922bae30e9607b134a4f.png)
本篇文章是细谈 vue 系列的第三篇,这篇文章主要会介绍一下 vue
的内置组件 transition
。前几篇链接如下
![26b5972a70fa42971717af1c573317ba.png](https://img-blog.csdnimg.cn/img_convert/26b5972a70fa42971717af1c573317ba.png)
![d5b4e889a2dad7cb75e89eb039259074.png](https://img-blog.csdnimg.cn/img_convert/d5b4e889a2dad7cb75e89eb039259074.png)
开始之前,我们先看下官方对 transition
的定义
- 自动嗅探目标元素是否使用了 CSS 过渡或动画,如果使用,会在合适的时机添加/移除 CSS 过渡 class。
- 如果过渡组件设置了 JavaScript 钩子函数,这些钩子函数将在合适的时机调用。
- 如果没有检测到 CSS 过渡/动画,并且也没有设置 JavaScript 钩子函数,插入和/或删除 DOM 的操作会在下一帧中立即执行
一、抽象组件
在 vue
中,对于 options
有一条属性叫 abstract
,类型为 boolean
,他代表组件是否为抽象组件。但找寻了有关 options
的类型定义,却只找到这么一条
static options: Object;
但实际上,vue
还是有对 abstract
属性进行处理的,在 lifecycle.js
的 initLifecycle()
方法中有这么一段逻辑
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
从这段逻辑我们能看出,vue
会一层一层往上找父节点是否拥有 abstract
属性,找到之后则直接将 vm
丢到其父节点的 $children
中,其本身的父子关系是直接被忽略的
然后在create-component.js
的 createComponent()
方法中对其处理如下
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners & slot
// work around flow
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
二、举个例子
分析之前,先看个官方 transition
的例子
<template>
<div id="example">
<button @click="show = !show">
Toggle render
</button>
<transition name="slide-fade">
<p v-if="show">hello</p>
</transition>
</div>
</template>
<script>
export default {
data () {
return {
show: true
}
}
}
</script>
<style lang="scss">
.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 {
transform: translateX(10px);
opacity: 0;
}
</style>
当点击按钮切换显示状态时,被 transition
包裹的元素会有一个 css 过渡效果。接下来,我们将分析具体它是如何做到这种效果的。
三、transition 实现
从上面的用法中,我们应该能感知出,对于 <transition>
,vue
给我们提供了一整套 css 以及 js 的钩子,这些钩子本身都已经帮我定义好并绑定在实例上面,剩下的就是需要我们自己去重写 css 或者 js 钩子函数。其实这和 vue
本身的 hooks
实现非常类似,都会在不同的钩子帮我们做好对应的不同的事情。
这种编程思维非常像函数式编程中的思想,或者说它就是。
函数式编程关心数据的映射,函数式编程中的 lambda 可以看成是两个类型之间的关系,一个输入类型和一个输出类型。你给它一个输入类型的值,则可以得到一个输出类型的值。
其实这个反映到这块,在其不同的钩子里面进行不同程度的重写,即可得到不同的输出结果,当然这里的输出其实就是过渡效果,而这种方式不恰好是 输入 => 输出
的方式吗?接下来我们就讲一下这块的具体实现。
<transtion>
组件的实现是 export
出 一个对象,从这里我们能看出它会将预先设定好的 props
绑定到 transition
上,而后我们只需对其中的点进行输入即可得到对应的样式输出,具体如何进行的过程,就不需要我们去考虑了
export default {
name: 'transition',
props: transitionProps,
abstract: true,
render (h: Function) {
// 具体 render 等会再看
}
}
其中支持的 props
如下,你可以对 enter
和 leave
的样式进行任意形式的重写
export const transitionProps = {
name: String,
appear: Boolean,
css: Boolean,
mode: String,
type: String,
enterClass: String,
leaveClass: String,
enterToClass: String,
leaveToClass: String,
enterActiveClass: String,
leaveActiveClass: String,
appearClass: String,
appearActiveClass: String,
appearToClass: String,
duration: [Number, String, Object]
}
接下来我们来看看 render
里面的内容是如何对我们预先绑定好的东西进行 输入 => 输出
的一个转换的
children
处理逻辑:首先从默认插槽中获取到<transition>
包裹的子节点,随后对其进行filter
过滤,将文本节点以及空格给过滤掉。如果不存在子节点则直接return
,如果子节点为多个,则报错,因为<transition>
组件只能有一个子节点
let children: any = this.$slots.default
if (!children) {
return
}
children = children.filter(isNotTextNode)
if (!children.length) {
return
}
if (process.env.NODE_ENV !== 'production' && children.length > 1) {
warn(
'<transition> can only be used on a single element. Use ' +
'<transition-group> for lists.',
this.$parent
)
}
mod