易懂好上手的 transition-group 函数式复用组件
前情提要
Vue.js 中内置了弱小好用的过渡和动画辅助零碎,让咱们在插入、更新或者移除 DOM 时,提供多种不同形式的利用过渡成果。Vue 提供的 <transition>
和 <transition-group>
的封装组件,能够包裹在咱们须要增加进入 / 来到过渡的元素和组件的里面。可是,这两个组件只是提供了一个能够配置成果的外包装骨架,如果咱们想把某些罕用的过渡动画成果重复使用在咱们的组件上的时候,就只能每次都从新同样来一次次配置在 transition 和 transition-group 上了吗?
不,就算咱们想这样做,Vue 也不心愿咱们这样做,在官网文档里的“过渡 & 动画 > 进入 / 来到 & 列表过渡 > 可复用的过渡”这个章节里提到:
要创立一个可复用过渡组件,你须要做的就是将
<transition>
或者<transition-group>
作为根组件,而后将任何子组件搁置在其中就能够了。函数式组件更适宜实现这个工作。
ok,这就是咱们明天的主题,如何制作一个可复用的 <transition-group>
函数式组件。
嗯?这官网上不是有么?有是有,然而那是间接用函数做的组件,纯代码式创立组件对大伙儿看着其实不怎么敌对,能有 SFC(单文件组件)看着直观么?所以明天做的就是用 SFC 做的可复用的 <transition-group>
函数式组件~~
注:本文用到的是 Vue 2.x 的版本,前期小伙伴能够自行迁徙到 3.x 上~~
函数式组件
为什么写成函数式组件
先来做一下必要的阐明,为什么说函数式组件做 <transition-group>
会更好呢?首先从业务上来说,咱们的过渡组件并须要不关怀本人底下子元素,也不须要给子元素传递什么属性,它就是一个全心全意给子元素提供过渡的组件:有子元素呈现,触发过渡钩子,出现进场的过渡成果;有子元素变动,提供旧地位挪动到新地位的过渡动画成果;有子元素隐没,出现登场成果。所以它能够不须要任何状态,如果一个组件不须要状态的话,咱们就能够思考将其写成无状态的函数式组件,而在 Vue 2.x 中对函数式组件有肯定性能优化,初始化速度比有状态组件快得多。这就是咱们为什么把复用的 <transition-group>
写成函数式组件的次要起因。不过上面你就会发现,写成函数式组件的另外一个起因。
函数式组件怎么写
在官网文档上,其实曾经写得很分明了,纯代码的就不说了,这个不是咱们明天要关注的,就先略过;单文件组件上写函数式组件只有简略地在 template 标签上写上 functional,就成了:
<template functional>
</template>
非常简单!
此外,所有传进来的 props 都间接能够在模板上进行拜访,不须要在 script 标签上的对象上进行申明;而且如果你没有任何须要自行定义或者额定配置的货色的话,连 script 标签也能够不必写!就像这样:
<template functional>
<h2>{{props.title}}</h2>
</template>
下面就是一个最简略的函数组件,几乎太好写了有没有!
当然,当你须要对 props 外面的属性进行计算的时候,还是能够写写 script 标签的,比方你须要对文字做一些转换:
<template functional>
<h2>{{$options.coverTitle(props.title)}}</h2>
</template>
<script>
export default {coverTitle(str) {return str.toLowerCase();
}
}
</script>
这时候,你能够应用 $options 来拜访到你定义在 script 标签中的对象。
又或者你想绑定一些简略事件:
<template functional>
<h2 @click="$option.clickTitle">{{props.title}}</h2>
</template>
<script>
export default {clickTitle(title) {alert('这是题目');
}
}
</script>
都是能够的。
不过要留神的是,在 script 对象外面的代码是没有 this 的,也不能拜访到文档中提到 context 外面的字段。
所以这也是为啥咱们用函数式组件的起因,因为太简略了!
开始干活
开始干活!
首先先确认需要,咱们要做一个含有多种过渡成果的蕴含 <transition-group>
的组件,而后能够依据内部的传进来的参数来定制咱们所须要的成果。
就这么简略!
先来把 <transition-group>
写进咱们的函数式组件里!
<template functional>
<transition-group
:name="props.effectName"
tag="div"
class="transition-group-container"
>
<slot></slot>
</transition-group>
</template>
内部传进去的 effectName 属性就间接绑定到 <transition-group>
就成!完事!
啊等等!不是这样的,你听我解释!这只是个示例!别关掉浏览器页面!
咱们当然想要复用的过渡成果当然不是这么简略的啦,所以咱们要做一点简单的货色,能力有复用的价值,所以咱们要做这个成果:
这个顺次进入的成果是不是看着很简单!然而应用了 <transition-group>
就能够非常简单的实现!咱们接着写!
官网中也有个例子相似这个成果的,思路就是应用了 <transition-group>
的 JavaScript 钩子,再联合 data attribute,失去节点的 index,而后进行绝对应提早渲染。动画的写法应用了 Velocity.js。咱们也能够来试试。
下面说过,能够在 script 标签中定义一些办法,而后能够在 template 上应用 $options 来拜访失去,所以咱们能够这样写:
<template functional>
<transition-group
v-on:before-enter="$options.beforeEnter"
v-on:enter="$options.enter"
tag="div"
class="transition-group-container"
>
<slot></slot>
</transition-group>
</template>
<script>
export default {beforeEnter(el) {// ...},
enter(el, done) {// ...},
}
</script>
咱们就在这写进入成果,其余成果同理的。而后就能够在这两函数里写成果了,先写好 beforeEnter:
<template functional>
<transition-group
v-on:before-enter="$options.beforeEnter"
v-on:enter="$options.enter"
tag="div"
class="transition-group-container"
>
<slot></slot>
</transition-group>
</template>
<script>
export default {beforeEnter(el) {
el.style.opacity = 0;
el.style.transform = 'translateX(-50%)';
el.style.transition = 'all 1s';
},
enter(el, done) {// ...},
}
</script>
beforeEnter 设置好元素进入前的款式状态,而后要记得把 transition 属性设置上,这样才有过渡成果。
接下来写 enter 成果,咱们心愿进入之后是这样的状态:
<template functional>
<transition-group
v-on:before-enter="$options.beforeEnter"
v-on:enter="$options.enter"
tag="div"
class="transition-group-container"
>
<slot></slot>
</transition-group>
</template>
<script>
export default {beforeEnter(el) {
el.style.opacity = 0;
el.style.transform = 'translateX(-50%)';
el.style.transition = 'all 1s';
},
enter(el, done) {
dom.style.opacity = 1;
dom.style.transform = 'translateX(0%)';
dom.addEventListener('transitionend', done);
},
}
</script>
很好,很简略!然而,这样写的话是没有成果的。
不晓得出于什么起因(对,文档也没写,我也没想分明),在只用 JavaScript 钩子的时候 done 回调函数是肯定要有的,而且还不能同步调用,同步调用会失去成果,肯定要异步进行,所以咱们加上一点点的延时去执行这块的代码:
<template functional>
<transition-group
v-on:before-enter="$options.beforeEnter"
v-on:enter="$options.enter"
tag="div"
class="transition-group-container"
>
<slot></slot>
</transition-group>
</template>
<script>
export default {beforeEnter(el) {
el.style.opacity = 0;
el.style.transform = 'translateX(-50%)';
el.style.transition = 'all 1s';
},
enter(el, done) {setTimeout(() => {
el.style.opacity = 1;
el.style.transform = 'translateX(0%)';
el.addEventListener('transitionend', done);
}, 100);
},
}
</script>
这样过渡成果就失效了~~
然而这还不是咱们想要的成果,这样写是同时进入的成果,不是咱们想要的顺次进入的成果,所以咱们问题来了:官网文档中的成果是在用 v -for 渲染的同时给 dom 元素的 data 属性赋上子元素在汇合中的 index,那咱们齐全不晓得咱们 transition-group 将要蕴含多少个子元素,又应该如何晓得他们的在汇合中的程序呢?
把他们当做 vue 的组件看的话,咱们可能不晓得,然而如果咱们把他们当做 dom 节点来看的话呢?
只有晓得子节点的后面有多少个兄弟节点,不就晓得本人排第几了吗?
所以一个很简略的辅助函数就写进去了:
function getDomNodeIndex(el) {
let node = el;
let index = 0;
while (node.previousSibling) { // 一直去拜访前一个兄弟节点
index += 1;
node = node.previousSibling;
}
return index;
}
于是咱们的组件就能够这样写:
<template functional>
<transition-group
v-on:before-enter="$options.beforeEnter"
v-on:enter="$options.enter"
tag="div"
class="transition-group-container"
>
<slot></slot>
</transition-group>
</template>
<script>
function getDomNodeIndex(el) {
let node = el;
let index = 0;
while (node.previousSibling) { // 一直去拜访前一个兄弟节点
index += 1;
node = node.previousSibling;
}
return index;
}
export default {beforeEnter(el) {
el.style.opacity = 0;
el.style.transform = 'translateX(-50%)';
el.style.transition = 'all 1s';
},
enter(el, done) {const delay = getDomNodeIndex(el) * 200;
setTimeout(() => {
el.style.opacity = 1;
el.style.transform = 'translateX(0%)';
el.addEventListener('transitionend', done);
}, delay);
},
}
</script>
这样就实现了咱们的一个成果!
进一步提高复用性
好了,咱们的第一个成果就写好了,然而呢,咱们不能满足于此,既然能够从右边滑进来,那是不是也能够从左边、从顶部、从底部滑进来?那既然能够顺次滑进来,那是不是也能够一起从右边、从左边、从顶部、从底部滑进来?那既然能够滑进来了是不是也能够用其余成果写上?
行行行,慢慢来,接下来传授你进一步提高这个组件复用性的技巧。
下面都说了咱们是函数式组件,所以依据内部传入的属性来造成不同的过渡成果就是咱们接下来要攻克的难题。
先来提一下函数式组件的一个坑,就是 template 上无奈动静绑定 $options 上的办法,这兴许是 Vue 进步函数式组件渲染效率所做的一个措施,然而对于咱们来说是有点不便的,具体来说是这样的:
<template functional>
<transition-group
v-on:before-enter="$options[props.animateName].beforeEnter"
v-on:enter="$options[props.animateName].enter"
tag="div"
class="transition-group-container"
>
<slot></slot>
</transition-group>
</template>
这里尝试应用通过 props 传进来的 animateName 属性来动静绑定进入的 JavaScript 钩子,然而没有失效。这样,咱们就要给绕一下了。
怎么绕过这个限度呢?家喻户晓,Vue 的模板中 v -bind 和 v -on 指令里能够写一些简略的 js 代码来实现简略的性能,所以咱们这里能够这样:
<template functional>
<transition-group
v-on:before-enter="(el) => $options[props.animateType].beforeEnter(el)"
v-on:enter="(el, done) => $options[props.animateType].enter(el, done)"
tag="div"
class="transition-group-container"
>
<slot></slot>
</transition-group>
</template>
这样咱们就能够奇妙绕过了无奈动静绑定办法的限度了~~ 当初咱们就能够在组件里定义多几个过渡成果函数了:
<template functional>
<transition-group
v-on:before-enter="(el) => $options[props.animateType].beforeEnter(el)"
v-on:enter="(el, done) => $options[props.animateType].enter(el, done)"
tag="div"
class="transition-group-container"
>
<slot></slot>
</transition-group>
</template>
<script>
function getDomNodeIndex(el) {
let node = el;
let index = 0;
while (node.previousSibling) { // 一直去拜访前一个兄弟节点
index += 1;
node = node.previousSibling;
}
return index;
}
export default {
slideLeftInOrder: {beforeEnter(el) {
el.style.opacity = 0;
el.style.transform = 'translateX(-50%)';
el.style.transition = 'all 1s';
},
enter(el, done) {const delay = getDomNodeIndex(el) * 200;
setTimeout(() => {
el.style.opacity = 1;
el.style.transform = 'translateX(0%)';
el.addEventListener('transitionend', done);
}, delay);
},
},
slideRightInOrder: {beforeEnter(el) {
el.style.opacity = 0;
el.style.transform = 'translateX(50%)';
el.style.transition = 'all 1s';
},
enter(el, done) {const delay = getDomNodeIndex(el) * 200;
setTimeout(() => {
el.style.opacity = 1;
el.style.transform = 'translateX(0%)';
el.addEventListener('transitionend', done);
}, delay);
},
},
// ... 更多的过渡成果
}
</script>
好啦,一个好用的 transition-group 函数式复用组件这样就写好,下面就是所有的源码,十分的好用简略!
参考:Vue.js
本文原创,未经受权不可转载