基于Vite+Ts制作组件库——下拉选择框|青训营笔记

201 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 13 天

一、项目简介:

随着前端技术的发展,业界涌现出了许多的UI组件库。例如我们熟知的ElementUI,Vant,AntDesign,IView等等。但是作为一个前端开发者,学会自己制作属于自己的UI组件库已经是很常见的技能了,此次笔记先介绍如何制作一个简易的下拉框组件,以及组件的样式,封装等等

二、项目文件存放:

在写组件之前,我先介绍一下文件存放的目录,因为这是团队开发的项目,必然少不了项目的存放管理

首先先创建一个vite项目

使用 NPM:

npm init vite@latest

使用 Yarn:

yarn create vite

创建完后把多余的文件删掉

主要的文件目录如图:

image.png

docs:存放文档站

examples:开发项目的文件夹(开发时的主要文件夹)

packages:正式发布的项目文件夹(只存放组件的vue文件)

examples的目录简单看一下

image.png

在vite.config.ts中修改入口文件:

build: {
    rollupOptions: {
      // 覆盖默认的 .html 入口
      input: 'examples/main.js',
    },
  },

这样我们就能正常启动项目了

三、所需技术:

本次项目以vue3+ts+vite为框架搭建

css样式:scss/sass

文档站:vitepress

代码规范:eslint,prettier

四、组件实现过程:

实现基础选择下拉框主要代码

封装:

主要目录如图:

image.png

在select.ts文件,放入props和emit需要传的值与事件

export const selectEvent = {
  selectProps: {
    // 被选中的选项
    selected: {
      type: Object as any,
      default: {
        label: {
          type: String,
          default: '',
        },
        value: {
          type: [String, Number],
          default: '',
        },
      },
      required: true,
    },
    // 所有选项
    options: {
      type: Array as any,
      default: [
        {
          label: {
            type: String,
            default: '',
          },
          value: {
            type: [String, Number],
            default: null,
          },
        },
      ],
    },
  },
  selectEmit: ['change-select'],
};

在vue文件中引用

 import { selectEvent } from './select';
  const props = defineProps(selectEvent.selectProps);
  const emit = defineEmits(selectEvent.selectEmit);

然后就可以通过props和emit进行传值了

html代码主要两部分构成,一个是输入框,一个是选择框

输入框

      <input
          type="text"
          v-model="props.selected.label"
          disabled
          :placeholder="props.placeholder"
        />

选项框

当被选中的选中label值和选项中能匹配时,字体的颜色会不一样

      <div
        :style="{color: props.selected.label === item.label ? '#21A0FF' : '',}"
        class="options-item"
        v-for="(item, index) in data.itemList"
        :key="index"
        @click="selectValue(item)"
        >{{ item.label }}
      </div>
      <div v-show="data.itemList.length == 0" class="options-null">无匹配数据</div>

切换选项事件

 // 选择选项
  const selectValue = (item: any) => {
    emit('change-select', item.label, item.value);
    openOptions();
  };

最终效果图:

image.png

css样式的细节有很多这边不再展示,可以在源码中观看:

(xielinchang/demo-ui: a project for UI (github.com))

其中有一个小难点是如何点击其他区域能够使选项框关闭,在实现之前我是用@blur(因为这边设置了disabled,所以需要添加一个opacity为0的同样的输入框)去实现的,但因为css设置的动画有延迟,选择的时候选项框会移动,容易选择到其他的选项。看看是怎么解决的

首先在父元素中声明

      ref="box"
      :class="openFlag.valueOf() ? 'selecting' : 'select'"
      @click="openOptions()"

ref用于抓取元素

openFlag用于判断选择框打开的状态

首先引入必要的方法,创建一个data存在itemList初始化数据,因为还没挂载,所以需要用nextTick延迟使用,不然获取不到元素,通过box.value.contains(e.target)的方法获取除输入框以外的区域,最后实现点击任何区域也能关闭选项框

  import { ref, nextTick, watch, onMounted, reactive } from 'vue';
  // 初始化数据
  const data = reactive({
    itemList: [] as any,
  });
  onMounted(() => {
    data.itemList = props.options;
  });
  // 打开或关闭选项框
  const openOptions = () => {
    openFlag.value = !openFlag.value;
  };
   const box = ref();
  // 点击其他地方时关闭选项框
  // 因为还没挂载,所以需要延迟使用,不然获取不到元素
  nextTick(() => {
    document.addEventListener('click', (e) => {
      if (!box.value.contains(e.target) && openFlag.value == true) {
        openOptions();
      }
    });
  });

再提一下搜索功能,如何实现搜索功能呢

一个是过滤选项,我们要在输入框输入时监听输入的数据,通过输入的数据与选项框中的所有选项对比,过滤掉不符合的选项。

具体的实现方法是用filterData方法,以及用watch进行监听

 // 输入时改变被选中的值
  const searchOptions = (e: any) => {
    openFlag.value = true;
    emit('change-select', e.target.value);
  };
  const filterData = (value: string) => {
    data.itemList = props.options.filter((item: any) => {
      return item.label.toLowerCase().includes(value.toLowerCase());
    });
  };
  // 监听被选中的值,过滤掉不符合的选项
  watch(
    () => {
      return props.selected.label;
    },
    (value: any) => {
      if (props.type == 'search') {
        filterData(value);
      }
    },
  );

效果:

image.png

输入后:

image.png

最后在index.ts中声明组件

import { withInstall } from '../../assets/utils/install';

import select from './src/select.vue';

export const JSelect = withInstall(select); // 增加类型

export default JSelect;

然后就可以在App.vue中去测试了

五、总结思考:

这个组件总体难度不高,适合新手去尝试,让我感到比较满意的一个是动画的处理,用的是纯css比较顺滑,此外还添加了其他属性方便修改宽度,高度等,但是类型还不完善,只有默认的下拉框和一个带搜索功能的下拉框,还有很多属性可以添加,比如禁用状态,选项禁用状态,二级选项,多选等功能,而在性能上,类似搜索框的输入可以制作一个防抖进行优化。