这是我参与「第五届青训营 」伴学笔记创作活动的第 13 天
一、项目简介:
随着前端技术的发展,业界涌现出了许多的UI组件库。例如我们熟知的ElementUI,Vant,AntDesign,IView等等。但是作为一个前端开发者,学会自己制作属于自己的UI组件库已经是很常见的技能了,此次笔记先介绍如何制作一个简易的下拉框组件,以及组件的样式,封装等等
二、项目文件存放:
在写组件之前,我先介绍一下文件存放的目录,因为这是团队开发的项目,必然少不了项目的存放管理
首先先创建一个vite项目
使用 NPM:
npm init vite@latest
使用 Yarn:
yarn create vite
创建完后把多余的文件删掉
主要的文件目录如图:
docs:存放文档站
examples:开发项目的文件夹(开发时的主要文件夹)
packages:正式发布的项目文件夹(只存放组件的vue文件)
examples的目录简单看一下
在vite.config.ts中修改入口文件:
build: {
rollupOptions: {
// 覆盖默认的 .html 入口
input: 'examples/main.js',
},
},
这样我们就能正常启动项目了
三、所需技术:
本次项目以vue3+ts+vite为框架搭建
css样式:scss/sass
文档站:vitepress
代码规范:eslint,prettier
四、组件实现过程:
实现基础选择下拉框主要代码
封装:
主要目录如图:
在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();
};
最终效果图:
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);
}
},
);
效果:
输入后:
最后在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比较顺滑,此外还添加了其他属性方便修改宽度,高度等,但是类型还不完善,只有默认的下拉框和一个带搜索功能的下拉框,还有很多属性可以添加,比如禁用状态,选项禁用状态,二级选项,多选等功能,而在性能上,类似搜索框的输入可以制作一个防抖进行优化。