以 elementUI 的回到顶部组件为例,浅谈如何封装一个 vue 组件

963 阅读4分钟

前言

本文所列封装组件的步骤,是以 Element UI 的回到顶部组件为例,但不完全局限于它(至少可以做个参考)。所展示的 demo 也做了一点修改。

封装步骤

以下回答仅是个人对 vue 组件封装的一些见解,不一定全对,若有不妥之处,还请谅解。

1. 明确将要封装的组件是装饰性的组件还是功能性的组件?

这个问题很好理解,说白了就是你将要封装的组件是用来做啥的。那么关于本文章的回到顶部按钮是一个什么组件呢?答案很明显,它是一个功能性的组件,因为设计它的目的:是为了让用户通过点击来使滚动条从页面从底部回到顶部。

2. 场景分析

就是明确你所封装的组件需要用在什么地方?以回到顶部按钮为例,那么用到它的主要场景:就是页面内容较长,需要向下滚动浏览,且回到顶部时,可以一步到位(不用费手指地滑鼠标向上滚)。

3. 状态分析

这一步骤,一般多用于封装装饰性组件。例如,按钮组件,它一般有如下几个状态:

  • 默认状态
  • 成功状态
  • 信息状态
  • 警告状态
  • 危险状态

就回到顶部这种按钮形式的组件而言,我们只需一个默认状态(也就是初始化)。例如,距离页面底部或右边距的距离是多少,滚动高度为多少时显示按钮等。

4. 形态分析

主要指你封装的组件展示出来是什么样子(也就是 css 样式)。一般而言,按钮组件都会有一些形状样式和动画效果以供选择,例如:

  • 圆形
  • 正方形
  • 淡入淡出过渡动画

而我们的回到顶部按钮组件,则打算通过 slot(插槽)的方式让开发者自行设计,这样的组件样式就不会只固定为有限的几种,而是姹紫嫣红。

5. 尺寸大小

通常情况下,按钮形态的组件都会有几个不同的尺寸以供选择。

  • small —— 小
  • medium —— 中等
  • large —— 大

由于打算让开发者能够自定义,所以这里也就不必刻意设计可选的大小。

6. 事件监听函数

在封装 vue 组件时,一般情况下,都会给其设置一些事件监听函数以便实现某些操作。

7. 暴露对外属性和事件

通过上述的一系列分析,我们可能会暴露出以下属性和事件监听函数。

属性

参数说明类型默认值
target触发滚动的对象string
visibility-height滚动高度达到此参数值才出现number200
right控制其显示位置, 距离页面右边距number40
bottom控制其显示位置, 距离页面底部距离number40
transition-name过渡动画效果stringel-fade-in

事件

参数说明回调参数
click点击按钮触发的事件点击事件

代码实现

注意,整个组件除了为其增添了一个 transition-name 过渡动画属性以外,再无其它更改。

HTML 结构布局

<template>
  <transition :name="transitionName">
    <div v-if="visible"
      :style="{
        'right': styleRight,
        'bottom': styleBottom
      }"
      class="el-backtop"
      @click.stop="handleClick"
    >
      <!--  插槽中的默认样式,可以根据个人喜好更换,这需要在使用组件时传入 -->
      <slot>
        <el-icon name="caret-top" />
      </slot>
    </div>
  </transition>
</template>

js 代码

代码中引入的 throttle(节流)函数,主要是防止用户不断地重复点击按钮触发滚动函数。大家可以通过 npm install throttle-debounce --save 在项目中安装引入,也可以像我在代码中写的一样:通过文件方式引入,前提是你得先下载(本人准备的 demo 中就有,需要的可以自取)。若是,同学们对节流防抖感兴趣的话,不妨看看我写的相关文章。

<script>
// 节流,确保一定时间段内只会调用一次事件处理函数。防止用户重复性点击。
import throttle from './js/throttle'

// 设置滚动动画效果
const cubic = value => Math.pow(value, 3)
const easeInOutCubic = value => value < 0.5
  ? cubic(value * 2) / 2
  : 1 - cubic((1 - value) * 2) / 2

export default {
  name: 'ElBacktop',
  props: {
    // 滚动高度
    visibilityHeight: {
      type: Number,
      default: 200
    },
    // 触发滚动的对象
    target: [String],
    // 页面右边距距离
    right: {
      type: Number,
      default: 40
    },
    // 页面底部距离
    bottom: {
      type: Number,
      default: 40
    },
    // 过渡动画
    transitionName: {
      type: String,
      default: 'el-fade-in'
    }
  },

  data() {
    return {
      el: null, // 触发滚动的对象
      container: null,
      visible: false // 控制组件显示
    }
  },
  computed: {
    // 利用计算属性,监听 bottom 和 right 的值
    styleBottom() {
      return `${this.bottom}px`
    },
    styleRight() {
      return `${this.right}px`
    }
  },
  mounted() {
    // 初始化
    this.init()
    // 生成节流函数并返回
    this.throttledScrollHandler = throttle(300, this.onScroll)
    // 监听 scroll 事件
    this.container.addEventListener('scroll', this.throttledScrollHandler)
  },
  // 销毁钩子
  beforeDestroy() {
    // 移除 scroll 事件
    this.container.removeEventListener('scroll', this.throttledScrollHandler)
  },

  methods: {
    // 初始化,主要是获取滚动的对象
    init() {
      this.container = document
      this.el = document.documentElement
      if (this.target) {
        this.el = document.querySelector(this.target)
        if (!this.el) {
          throw new Error(`target is not existed: ${this.target}`)
        }
        this.container = this.el
      }
    },
    // 监听滚动值,用于显示和隐藏回到顶部按钮组件
    onScroll() {
      const scrollTop = this.el.scrollTop
      this.visible = scrollTop >= this.visibilityHeight
    },
    // 点击事件
    handleClick(e) {
      this.scrollToTop()
      this.$emit('click', e) // 触发父级监听事件函数 click
    },
    // 滚动函数
    scrollToTop() {
      const el = this.el // 滚动对象
      const beginTime = Date.now() // 开始滚动的时间
      const beginValue = el.scrollTop // 滚动距离
      // 执行动画
      const rAF = window.requestAnimationFrame || (func => setTimeout(func, 16))
      // 更新动画的回调函数
      const frameFunc = () => {
        // 控制动画进度
        const progress = (Date.now() - beginTime) / 500
        // progress 大于 1,则滚动动画执行完成,滚动条回到顶部
        if (progress < 1) {
          el.scrollTop = beginValue * (1 - easeInOutCubic(progress))
          rAF(frameFunc)
        } else {
          el.scrollTop = 0
        }
      }
      // 回调,持续执行滚动动画
      rAF(frameFunc)
    }
  }
}
</script>

使用方式

elementui 的回到顶部组件是没有 transition-name 过渡动画属性的,这里我稍作更改,为其添加了上去(完全照搬,太不像话,就加了一个,嘿嘿...)。

// 方式一
<BackTop :bottom="100" transition-name="el-fade-in-linear">
    <div
    style="{
    height: 100%;
    width: 100%;
    background-color: #f2f5f6;
    box-shadow: 0 0 6px rgba(0,0,0, .12);
    text-align: center;
    line-height: 40px;
    color: #1989fa;
    }"
    >
        UP
    </div>
</BackTop>

// 方式二
<BackTop :bottom="100" transition-name="el-fade-in-linear"></BackTop>

最后

文章并没有什么深邃的东西值得探讨,主要是为和同学们分享我个人封装 vue 组件的一些感悟。若有不妥之处,还请担待。