《微信小程序-进阶篇》Lin-ui组件库源码分析-动画组件Transition(三)

发布于:2023-01-22 ⋅ 阅读:(13) ⋅ 点赞:(0) ⋅ 评论:(0)

大家好,这是小程序系列的第十九篇文章,在这一个阶段,我们的目标是 由简单入手,逐渐的可以较为深入的了解组件化开发,从本文开始,将记录分享lin-ui的源码分析,期望通过对lin-ui源码的学习能加深组件化开发的能力:
1.《微信小程序-进阶篇》Lin-ui组件库源码分析-Icon组件
2.《微信小程序-进阶篇》Lin-ui组件库源码分析-动画组件Transition(一)
3.《微信小程序-进阶篇》Lin-ui组件库源码分析-动画组件Transition(二)
——
求关注,求收藏,求点赞,这是一个系列文章,建议专栏收藏,肯定分享的都是跟小程序相关的,旨在能提高在小程序中的能力,谢谢~

《微信小程序-进阶篇》Lin-ui组件库源码分析-动画组件Transition(三)

前言

上一篇我们介绍了 Transition组件 的部分源码,包括 内部的动画效果,name属性以及duration是如何实现的,通过学习我们知道 Transition组件的核心动画 实现是 围绕着CSS3的trainsition属性实现的,它在代码中预设好了所有动画,挂载时集合好要实现的动画类名,之后统一添加到DOM上,并执行动画,这篇我们将主要了解剩下的 show属性,程序的 执行逻辑 以及 钩子函数 ,耐心看完,你一定有所收获~

阅读对象与难度

本文难度属于:初中级,通过本文你可以了解到 Lin-ui的Transition组件内部的show属性包括show变化时整个流程如何运转,以及transition的钩子函数是如何实现的,本文主要内容参考以下思维导图:
在这里插入图片描述

show属性的实现

通过之前的用法说明我们知道,show属性 作用就是当其值为true的时候显示组件,false的时候隐藏内容
既然是属性,那么和 name属性duration属性 一样,我们可以 从DOM入手,因为show的作用是 显示/隐藏DOM,这是一个影响DOM的属性,打开html也就是wxml文件

<view
    wx:if="{{ inited }}"
    class="l-transition l-class {{ classes }}"
    style="-webkit-transition-duration:{{ currentDuration }}ms; transition-duration:{{ currentDuration }}ms; {{ display ? '' : 'display: none;' }} {{ customStyle }}"
    bind:transitionend="onTransitionEnd"
>
  <slot />
</view>

不长的代码中我们 没有发现show这个属性,但也发现了wx:if这个控制DOM显示/隐藏的指令,这个指令对应的 变量inited,并不是show,没关系,打开behaviors下的 transition.js文件,追踪inited变量,与inited变量相关的代码有两段,但是没有发现和show有什么关系,不急,换个角度接着找,既然show是作为一个属性传递,那么在properties是一定存在的,否则没有办法传递,果然不出所料,代码如下:

 show: {
  type: Boolean,
  value: showDefaultValue,
  observer: 'observeShow'
},

出乎意料的是 show里面有一个observer属性,对应的值是一个字符串 observeShow,observer是什么呢?

observer

官方的解释如下:

数据监听器,数据监听器可以用于监听和响应任何属性和数据字段的变化。从小程序基础库版本 2.6.1 开始支持

简单的说,就是一个 监听器,值是对应的一个函数,有点像Vue中的watch,一旦监听的值发生变化了,那么会触发对应的函数,拿这边举例,这边给show这个属性添加了一个监听属性observer,对应的值是一个函数,那 必然会有一个名为observeShow的函数存在,找一下

methods:{
  observeShow(value) {
    value ? this.enter() : this.leave();
  },
}

确实存在这个函数,并且还有一个参数value,这个 参数是observer自带的代表的是当前监听属性对应的值,比如:

 show: {
  type: Boolean,
  value: false,
  observer: 'observeShow'
}

 methods:{
  // 当show的值变成true的时候,value就变为true
  observeShow(value) {
    console.log(value)
  },
}

再看 Transition 中show的监听器,简单的就执行了这一段代码

observeShow(value) {
  value ? this.enter() : this.leave();
},

value值为true的时候执行this.enter()这个函数,false的时候执行this.leave()这个函数;到这里,重点就到 enterleave 这两个函数了;

enter函数

到这里可能会有小伙伴问,这个和 DOM中控制 显示/隐藏 的变量inited没有关联起来 啊,因为 从用法上看show是控制Transition组件的显示与隐藏,但代码中却是 变量inited 控制的,不着急,我们往下看不信可以搜一下inited这个变量,这个变量在全文一共出现2次:
第一次,在data中

data: {
  type: '',
  inited: false,
  display: false
},

这段没有太多信息,就是内部的变量集合,有用的在第二次

 enter() {
  const { duration, name } = this.data;
  const classNames = getClassNames(name);
  const currentDuration = isObj(duration) ? duration.enter : duration;
  this.status = 'enter';
   
   // 触发生命周期钩子函数linbeforeenter
  this.triggerEvent('linbeforeenter');
   
  Promise.resolve()
    .then(nextTick)
    .then(() => {
      this.checkStatus('enter');
      this.triggerEvent('linenter');
    
      // 这里将inited设置为 true
      this.setData({
        inited: true,
        display: true,
        classes: classNames.enter,
        currentDuration
      });
    
    })
    .then(nextTick)
    .then(() => {
      this.checkStatus('enter');
      this.transitionEnded = false;
      this.setData({
        classes: classNames['enter-to']
      });
    })
    .catch(() => { });
},

在这一段中,最终会通过 setDatainited 设置为true,再看这个执行的函数就是 enter,这不就关联起来了,从流程上看,当show的值变为true的时候,触发了数据监听器observeShow,并且由于值为true因此触发了enter这个函数,在这个函数的结尾将inited设置为true,由此显示了DOM,并执行预设好的CSS动画
在这里插入图片描述

因此,我们重点来完整的看一下 enter 这个函数的代码,首先是这4行

const { duration, name } = this.data;
const classNames = getClassNames(name);
const currentDuration = isObj(duration) ? duration.enter : duration;
this.status = 'enter';

这4行代码都是在转换参数:

  • 第一行:从 data 从解构出了 durationname属性
  • 第二行:将 name属性 传递给 getClassNames 这个函数,这个函数会根据 name 将对应的CSS动画类名组合返回,具体看一下 getClassNames 这个函数
const getClassNames = (name) => ({
  enter: `l-${name}-enter l-${name}-enter-active l-enter-class l-enter-active-class`,
  'enter-to': `l-${name}-enter-to l-${name}-enter-active l-enter-to-class l-enter-active-class`,
  leave: `l-${name}-leave l-${name}-leave-active l-leave-class l-leave-active-class`,
  'leave-to': `l-${name}-leave-to l-${name}-leave-active l-leave-to-class l-leave-active-class`
});

比如 name 输入的是 fade,那么返回的是就是一个包含 enterenter-toleaveleave-to 的对象

{
 enter: `l-fade-enter l-fade-enter-active l-enter-class l-enter-active-class`,
 'enter-to': `l-fade-enter-to l-fade-enter-active l-enter-to-class l-enter-active-class`,
 leave: `l-fade-leave l-fade-leave-active l-leave-class l-leave-active-class`,
 'leave-to': `l-fade-leave-to l-fade-leave-active l-leave-to-class l-leave-active-class`
}
  • 第三行:获取 duration属性,并根据其类型获得最终的 duration值
  • 第四行:将当前status状态设置成enter;

接着是这一段 (PS: 钩子函数那个我们下面再细看)

  Promise.resolve()
    .then(nextTick)
    .then(() => {
      this.checkStatus('enter');
      this.triggerEvent('linenter');
    
      // 这里将inited设置为 true
      this.setData({
        inited: true,
        display: true,
        classes: classNames.enter,
        currentDuration
      });
    
    })
    .then(nextTick)
    .then(() => {
      this.checkStatus('enter');
      this.transitionEnded = false;
      this.setData({
        classes: classNames['enter-to']
      });
    })
    .catch(() => { });
},

这段中主要的功能是一个 Promise,相当重要,因为 通过Promise实现了将异步转同步的功能,并且还通过 Promisethen 实现了一个 链式操作,但是,这个链式操作里面有两个比较特殊的then,也就是第一个和第三个

.then(nextTick)

都使用了一个 nextTick,翻一下代码,在11行找到后,代码如下

const nextTick = () => new Promise(resolve => setTimeout(resolve, 1000 / 30));

简单的说就是 实现了一个延迟操作,通过返回一个Promise来延续外面的主Promise的then功能,然后在当前Promise里执行了一个1000/30毫秒的延迟;
接着就是第二段then操作

  this.checkStatus('enter');
  this.triggerEvent('linenter');

  // 这里将inited设置为 true
  this.setData({
    inited: true,
    display: true,
    classes: classNames.enter,
    currentDuration
  });
  • 第一行:执行了一个函数 checkStatus(),翻看代码找到checkStatus
checkStatus(status) {
  if (status !== this.status) {
    throw new Error(`incongruent status: ${status}`);
  }
},

简单的说 就是一个校验判断当前组件内部的状态this.status是否与传入的状态status全等,如果不是,则抛出异常终止运行;

  • 第二行:执行了一个生命周期钩子函数linenter,这个后面再说;
  • 5-10行:修改内置的属性,包括显示DOM的 inited属性动画的类名组合动画的持续时间

然后是第四段then

this.checkStatus('enter');
this.transitionEnded = false;
this.setData({
  classes: classNames['enter-to']
});
  • 第一行:同样是校验当前组件的状态,是否为 enter;
  • 第二行:将transitionend属性 设置成false,transitionend 这是一个和动画相关的属性;
  • 3-5行:将类名的集合修改成 enter-to 的集合

到这里,我们大致有点明白enter这个函数了,在这个函数利用Promise的异步转同步作用为DOM添加从隐藏到显示的CSS动画隐藏到显示的CSS动画一共分为两段,第一段在第二个then中,名为enter的集合,第二段在第四个then中,名为enter-to的集合中;
在这里插入图片描述

leave函数

接下来看 leave函数,从功能来看,感觉和 enter 应该是类似的,一下便是完整代码

 leave() {
  if (!this.data.display) {
    return;
  }
  const { duration, name } = this.data;
  const classNames = getClassNames(name);
  const currentDuration = isObj(duration) ? duration.leave : duration;
  this.status = 'leave';
  this.triggerEvent('linbeforeleave');
  Promise.resolve()
    .then(nextTick)
    .then(() => {
      this.checkStatus('leave');
      this.triggerEvent('linleave');
      this.setData({
        classes: classNames.leave,
        currentDuration
      });
    })
    .then(nextTick)
    .then(() => {
      this.checkStatus('leave');
      this.transitionEnded = false;
      setTimeout(() => this.onTransitionEnd(), currentDuration);
      this.setData({
        classes: classNames['leave-to']
      });
    })
    .catch(() => { });
},
  • 2-4行:做了一个判断,判断 display 的状态
  • 5-8行:和 enter函数 一样,做属性的转化,取得隐藏动画时的一些参数;
  • 第9行:触发了一个名为 linbeforeleave 的钩子函数;
  • 10-30行:这一段其实和 enter 那一段非常相似,也是通过 Promise实现了一个异步转同步的效果,第一个和第三个then是 通过nextTick实现了一个延时,第二段触发了一个名为 leave 的类名集合,第四段,触发了一个名为 leave-to 的动画集合;

整个函数看下来,leave函数和enter函数的作用非常接近,主要还是为DOM添加CSS的类名,并通过延时操作来实现分段添加,达到动画的连续性

钩子函数

除了正常的切换与现实,Lin-UI的transition组件还给我们提供了几个钩子函数,有以下6个

钩子函数 说明
linbeforeenter 入场动画开始之前触发
linenter 入场动画开始之后、结束之前触发
linafterenter 入场动画结束之后触发
linbeforeleave 出场动画开始前触发
linleave 出场动画开始之后、结束之前触发
linafterleave 出场动画结束之后触发

什么是钩子函数

在了解之前,先问个问题,什么是钩子函数,个人理解就是 当程序运行到某个步骤时往外界抛出了一个接口,允许使用者在这个程序节点上运行一些自己的代码
放到 Transition组件 里,就是在组件在执行到某个节点时 对外抛出了指定的钩子函数,接下来我们细看一下;

说明

linbeforeenter 为例,官网说它是在入场动画开始之前触发,那么通过上面我们可以知,隐藏到显示的过程是由enter这个函数实现的,那么我们就去enter这个函数里找

enter() {
    const { duration, name } = this.data;
    const classNames = getClassNames(name);
    const currentDuration = isObj(duration) ? duration.enter : duration;
    this.status = 'enter';
  
    this.triggerEvent('linbeforeenter');
  
    Promise.resolve().then()
}
 

看到 linbeforeenter 字段了,在代码中的 实现其实就是对外抛出了一个自定义事件,非常简单;
接着,既然 Lin-UI中有6个钩子函数其实它的实现无非就是在某个程序节点对外抛出自定义事件罢了,再看一个 linleave ,说明是:出场动画开始之后、结束之前触发

Promise.resolve()
.then(nextTick)
.then(() => {
  this.checkStatus('leave');

  // 抛出名为linleave的自定义事件
  this.triggerEvent('linleave');

  this.setData({
    classes: classNames.leave,
    currentDuration
  });
})
.then(nextTick)

小结

本文通过代码我们了解了 Transition组件show属性,或者说 出场入场动画是怎么实现的,简单的说就是通过 Promise,将异步变同步,然后分段执行CSS动画
接着我们 了解了钩子函数是什么,并且了解了 Transition组件 内部的钩子函数是如何实现的,简单的说就是在程序运行的指定时机对外抛出了一个自定义事件,使用者只需要对这个绑定的自定义事件添加对应函数即可~

(PS:都已经看到这里了,点个赞,求个关注吧,万分感谢~)