微信小程序:有赞小程序UI( vant-weapp ) actionsheet组件源码窥探

前言

还是微信小程序项目,虽然有一些优秀的第三方组件,但是秉持高度还原UI设计稿的原则,没有直接在项目中使用。遇到一些类似的逻辑时,可以参考一下这些开源项目的实现方式。难的往往只是某一个点,有时候我们需要的只是一张地图(PS:来自硬派脱口秀节目《知识就是力量》)。

开始

为什么研究actionsheet组件?

因为项目中设计较多的交互动画,想要自己同一封装,可多处使用。一方面可以减少代码量,另一方面可以将动画与逻辑分离,更易于管理。

正文

  1. 首先,需要在github上下载vant-weapp的源码;
  2. 把项目clone或下载到本地,参照官方说明,编译出可运行的小程序代码;
  3. 使用微信编辑器打开项目,并把编译出的小程序源码拖进你喜欢的编辑器。

actionsheet示例页面:

actionsheet示例页面

actionsheet页面代码:

wxml:

<demo-block title="基础用法" padding>
  <van-button bind:click="toggleActionsheet1">弹出 Actionsheet</van-button>
  <!-- 监听了close事件 show控制显示和隐藏 -->
  <van-actionsheet
    show="{{ show1 }}"
    actions="{{ actions }}"
    bind:close="toggleActionsheet1"
    bind:select="toggleActionsheet1"
  />
</demo-block>

<demo-block title="带取消按钮的 Actionsheet" padding>
  <van-button bind:click="toggleActionsheet2">弹出带取消按钮的 Actionsheet</van-button>
  <van-actionsheet
    show="{{ show2 }}"
    actions="{{ actions }}"
    cancel-text="取消"
    bind:close="toggleActionsheet2"
    bind:cancel="toggleActionsheet2"
    bind:select="toggleActionsheet2"
  />
</demo-block>

<demo-block title="带标题的 Actionsheet" padding>
  <van-button bind:click="toggleActionsheet3">弹出带标题的 Actionsheet</van-button>
  <van-actionsheet
    show="{{ show3 }}"
    title="标题"
    bind:close="toggleActionsheet3"
  >
    <view class="content">内容</view>
  </van-actionsheet>
</demo-block>

js:

import Page from '../../common/page';

Page({
  data: {
    show1: false,
    show2: false,
    show3: false
  },

  onLoad() {
    this.setData({
      actions: [
        { name: '选项' },
        { name: '选项', subname: '禁用' },
        { name: '选项', loading: true },
        { name: '禁用选项', disabled: true }
      ]
    });
  },

  // 设置隐藏  在最外层page里改变了show的值,触发transition.js中show的observer函数,导致所有的动画执行
  toggle(type) {
    this.setData({
      [type]: !this.data[type]
    });
  },

  // 调用方法
  toggleActionsheet1() {
    this.toggle('show1');
  },

  toggleActionsheet2() {
    this.toggle('show2');
  },

  toggleActionsheet3() {
    this.toggle('show3');
  }
});

在示例页面可以看出,是使用了van-actionsheet组件,那我们再去看看van-actionsheet组件的实现。

van-actionsheet组件代码:

<!-- 引入了van-popup组件 -->
<van-popup
  show="{{ show }}"
  overlay="{{ overlay }}"
  close-on-click-overlay="{{ closeOnClickOverlay }}"
  custom-class="van-actionsheet {{ title ? 'van-actionsheet--withtitle' : '' }}"
  position="bottom"
  bind:close="onClose"
>
  <!-- 这部分是作为van-popup组件的slot内容插入的 -->
  <view wx:if="{{ title }}" class="van-hairline--top-bottom van-actionsheet__header">
    <view>{{ title }}</view>
    <van-icon custom-class="van-actionsheet__close" name="close" bind:click="onClose" />
  </view>
  <view wx:else class="van-hairline--bottom">
    <view
      wx:for="{{ actions }}"
      wx:key="index"
      class="van-actionsheet__item van-hairline--top {{ item.disabled || item.loading ? 'van-actionsheet__item--disabled' : '' }} {{ item.className || '' }}"
      data-index="{{ index }}"
      bind:tap="onSelect"
    >
      <block wx:if="{{ !item.loading }}">
        <view class="van-actionsheet__name">{{ item.name }}</view>
        <view class="van-actionsheet__subname" wx:if="{{ item.subname }}">{{ item.subname }}</view>
      </block>
      <van-loading wx:else custom-class="van-actionsheet__loading" size="20px" />
    </view>
  </view>
  <view
    wx:if="{{ cancelText }}"
    class="van-actionsheet__cancel van-hairline--top"
    bind:tap="onCancel"
  >
    {{ cancelText }}
  </view>
  <view wx:else class="van-actionsheet__content">
    <!-- slot:可以自定义actionsheet内容 -->
    <slot />
  </view>
</van-popup>

在van-actionsheet组件源码中可以看出,组件内引入了van-popup组件,那我们再去看看van-popup组件的实现。

van-popup组件源码:

<!-- 引入了van-overlay组件 -->
<van-overlay
  mask
  show="{{ overlay && show }}"
  custom-style="{{ overlayStyle }}"
  bind:click="onClickOverlay"
/>

<!-- 真正的弹窗内容 -->
<view
  wx:if="{{ inited }}"
  class="custom-class van-popup {{ position ? 'van-popup--' + position : '' }}"
  style="animation-name: van-{{ position }}-{{ type }}; animation-duration: {{ duration }}ms; {{ display ? '' : 'display: none;' }}"
  bind:animationend="onAnimationEnd"
>
  <!-- slot: 可以自定义弹窗的内容 -->
  <slot />
</view>

组件van-popup内又引入了van-overlay组件,那再去看看van-overlay组件的实现。

van-overlay组件源码:

<!-- 好家伙,又是一个组件 -->
<van-transition
  show="{{ show }}"
  custom-class="van-overlay"
  custom-style="z-index: {{ zIndex }}; {{ mask ? 'background-color: rgba(0, 0, 0, .7);' : '' }}; {{ customStyle }}"
  bind:tap="onClick"
/>

组件中还是组件。。。再去看看van-transition组件的实现吧。

van-transition组件源码:

wxml:

<!-- name值默认为fade, type值是取transition中的type值(enter/leave) -->

<!-- 终于到头了~ -->
<view
  wx:if="{{ inited }}"
  class="van-transition custom-class"
  style="animation-name: van-{{ name }}-{{ type }}; animation-duration: {{ duration }}ms; {{ display ? '' : 'display: none;' }} {{ customStyle }}"
  bind:animationend="onAnimationEnd"
>
<!-- 显示和隐藏通过控制display属性实现,绑定了动画结束的处理函数 -->
  <slot />
</view>

wxss: 该部分代码定义的是动画,代码过长,此处就不深入探究CSS动画的实现啦。

js:

// 引入behaviors
import transitionBehaviors from '../behaviors/transition';

Component({
  options: {
    addGlobalClass: true
  },

  externalClasses: ['custom-class'],

  // 类似于mixins和traits的组件间代码复用机制
  // 每个behavior一组属性、数据、生命周期函数和方法,组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。每个组件可以引用多个 behavior。behavior也可以引用其他behavior 。
  behaviors: [transitionBehaviors(true)],

  properties: {
    name: {
      type: String,
      value: 'fade'
    }
  }
});

transition.js:

export default function(showDefaultValue) {
  return Behavior({
    properties: {
      customStyle: String,
      // 根据show的值判断是动画type是enter还是leave
      show: {
        value: showDefaultValue,
        type: Boolean,
        // 属性值被更改时的响应函数
        observer(value) {
          if (value) {
            this.show();
          } else {
            this.setData({
              type: 'leave'
            });
          }
        }
      },
      duration: {
        type: Number,
        value: 300
      }
    },

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

    attached() {
      if (this.data.show) {
        this.show();
      }
    },

    methods: {
      show() {
        this.setData({
          inited: true,
          display: true,
          type: 'enter'
        });
      },
      // 动画结束,只修改了display
      onAnimationEnd() {
        // 如果false, 就隐藏
        if (!this.data.show) {
          this.setData({
            display: false
          });
        }
      }
    }
  });
}

总结

很细的组件化开发,分工明确,耦合度低。对behavior进行了同一的管理,组件behaviors+observer结合使用,完美~

之前参考vant-weapp slider组件的实现,自己做了一个双向滑动的slider,收获颇多。致敬这些优秀的开源项目,致敬那些优秀的程序员~路还长

笔记笔记笔记 

如果你有什么疑问或想法,欢迎留言评论,或者扫描下方二维码,与我取得联系~  (记得备注:CSND喔~)

猜你喜欢

转载自blog.csdn.net/weixin_39015132/article/details/82459405