Dropdown 组件实现思路

概述:Dropdown 下拉菜单与 Select 的区别在于它是包含了一部分操做,因此它的 Option 下拉项要对其进行支持。javascript

publish:2019-03-31css

自从我写了关于中后台系统组件的第一篇文章以来,已经三个月了。因为业务场景的较为简单,因此它没有其余组件库的功能那么强大,可是总体结构较为相似,代码结构清晰,容易扩展。html

将 Dropdown 分为两个模块,父组件 Dropdown,子组件 Dropdown-option。其中Dropdown 负责控制总体的显示,Drop-option 负责下拉菜单的每个选项。java

Dropdown - 组件结构

├── Dropdownide

├── Dropdown-optionpost

Dropdown

父组件主要负责的是选中项的显示以及下拉菜单状态(开、合)的控制。ui

具体的代码以下this

<template>
  <div :class="['dropdown', { 'is-hover': trigger === 'hover' && isOpen }]" tabindex="0" @click.stop="isOpen = !isOpen" @blur="trigger !== 'hover' && (isOpen = false)" >
    <div :class="['dropdown__label']">
      <slot name="label">
        <span class="c-color-success">{{ placeholder }}</span>
      </slot>
      <fat-icon name="expand_more" class="c-color-success" />
    </div>

    <div class="dropdown__menu" v-if="trigger === 'hover' || isOpen">
      <slot name="menu"></slot>
    </div>
  </div>
</template>

<script> export default { name: "dropdown", provide() { return { Dropdown: this }; }, props: { placeholder: { type: String, default: "下拉菜单" }, trigger: { type: String, default: "hover" }, selectValue: { type: [String, Number] }, optionKey: { type: String, default: "value" } }, data() { return { isOpen: this.trigger === "hover", selectItem: {} }; }, model: { prop: "selectValue", event: "select" } }; 复制代码

首先对处理下拉菜单开关状态的控制,依据 trigger 也就是触发方式的不一样,能够分为两类 hover || clickspa

  • trigger = 'hover' 时,对最外层的 div 添加 is-hover 的类名,它主要是负责添加 :hover 伪类来显示下拉菜单
&.is-hover {
        &:hover {
            .dropdown__menu {
                display: block;
            }
        }
        .dropdown__menu {
            display: none;
        }
    }
复制代码

​ 同时依据 trigger 初始化 isOpen 状态为 true双向绑定

  • trigger = 'click' 时,利用 isOpen 的状态来控制下拉菜单的开、合。主要是依据事件来触发,利用 @click.stop="isOpen = !isOpen",来完成下来菜单的展开操做。以后,对最外层的 div 添加 tabindex="0" 属性使得它可以触发失焦事件 blur,同时添加 @blur="trigger !== 'hover' && (isOpen = false)",意味着当它失效的时候,自动关闭下拉菜单。

以上完成了 Dropdown 对下拉菜单控制的功能,利用 provide ,完成它与 Dropdown-option 的通信,传递 selectValueselectItemoptionKey

Dropdown-option

Dropdown-option 是下拉菜单的每一个选项,其基本结构

<template>
  <div :class="[ 'dorpdown-option', { 'is-disabled': disabled }, { 'is-selected': isSelected } ]" @mousedown="handleClick" >
    <slot>
      {{ label }}
    </slot>
  </div>
</template>
复制代码

主要是利用默认插槽,和 label 属性来构建每一项,而且包含着两种状态,是否 disabled 或 selected。disabled 状态是依据 props: disabled 来改变的,而 selected 则是由 computed 来完成的

<script>
export default {
  inject: {
    Dropdown: { default: "Dropdown" }
  },
  computed: {
    isSelected() {
      const {
        Dropdown: { optionKey, selectValue }
      } = this;
      const key = this[optionKey] || this.$attrs[optionKey];

      return key === selectValue;
    }
  },
  ...
};
</script>
复制代码

首先利用 inject 将父组件 Dropdown 注入,这样能够经过 this.Dropdown 来访问它的状态、属性。

而后在 isSelected() 中获取 selectValue,与当前 Dropdown-option 的 key 值进行比对,查看是否为选中项。

为了要引入 optionKey,是由于在实际的业务中,有的场景会以 label 做为去区分项,有的则是以 value,故引入,方便自定义。

每一个 Dropdown-option 具有选中功能,可是从 @mousedown="handleClick" 能够看出,利用 mousedown 来代替 click 事件

因为咱们利用 Dropdown 的 blur 事件来控制下拉列表的展开与关闭,此时若是利用 click 事件,则会在 blur 以后触发,因此没法选中。故采用 mousedown 来完成该功能。

methods: {
    handleSelect(key) {
        let {
            Dropdown: { multiple, trigger },
            value,
            label
        } = this;

        this.Dropdown.$emit("change", key);
        this.Dropdown.$emit("select", key);
        if (trigger !== "hover") {
            this.Dropdown.isOpen = false;
        }
    },
        handleClick() {
            let {
                Dropdown: { optionKey },
                disabled
            } = this;
            const key = this[optionKey] || this.$attrs[optionKey];

            if (!disabled) {
                this.$slots.default[0].elm.click && this.$slots.default[0].elm.click();
                key && this.handleSelect(key);
            }
        }
}
复制代码

这一份部分的逻辑就比较简单了,只有一处须要解释下

this.$slots.default[0].elm.click && this.$slots.default[0].elm.click();
复制代码

因为咱们利用 mousedown 来代替原来的 click 事件,但咱们利用 slot 插槽来完成下拉菜单的开发时,就没法触发 slot 的点击时间,因此利用上述代码来手动触发。

因为 Dropdown 组件中,使用了 v-model 来完成数据的双向绑定

model: {
    prop: "selectValue",
    event: "select"
}
复制代码

因此在 Dropdown-option 中则须要利用 this.Dropdown.$emit("select", key); 来完成双向绑定。

总结

代码地址:Dropdown Github

实例:Fat-UI lib

相关文章
相关标签/搜索