当前位置: 首页 > news >正文

vue3+TS实现简易组件库

vue3+TS搭建一个自己的组件库。

前置

首先下载vue-cli,搭建我们的环境,vue-create-luckyUi,选择vue3和TypeScript 。在src目录下创建package作为组件目录。再安装bootstrap,用bootstrap里面的样式来完成我们的组件。

组件编写

dropdown

首先查看boorstrap文档,是这样用的

<div class="dropdown"><button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-expanded="false">Dropdown button</button><div class="dropdown-menu" aria-labelledby="dropdownMenuButton"><a class="dropdown-item" href="#">Action</a><a class="dropdown-item" href="#">Another action</a><a class="dropdown-item" href="#">Something else here</a></div>
</div> 

首先那个button按钮就是我们dropdown按钮的内容,将这部分作为属性传入,而dropdown-menu的内容是作为dropdown-item的,明显这里不能固定写三个,这里就用插槽占位,再封装一个dropdown-item组件。

首先dropdown组件内容如下:

<template><div class="dropdown" ref="dropdownRef"><ahref="#"class="btn btn-outline-light my-2 dropdown-toggle"@click.prevent="toggleOpen">{{ title }}</a><ul class="dropdown-menu" :style="{ display: 'block' }" v-if="isOpen"><slot></slot></ul></div>
</template> 

dropdown-item的内容就是:

<template><liclass="dropdown-option":class="{'is-disabled': disabled}"><slot></slot></li>
</template>

<script lang="ts"> import { defineComponent } from 'vue'
export default defineComponent({name: "DropdownItem",props: {disabled: {type: Boolean,default: false}}
}) </script>

<style> .dropdown-option.is-disabled * {color: #6c757d;pointer-events: none;background-color: transparent;
} </style> 

还要实现一个点击dropdown,dropdown-item会随之收起来的功能,这个比较简单,在dropdown上绑定一个点击事件来控制变量isOpen为true或者false,在加上v-if即可实现功能。接下来还要实现一个点击页面的其他地方也能实现dropdown-item收缩,这里有两个思路:

  • 首先在document上添加一个click事件,一旦触发就设置isOpen为false,给dropdown也添加一个点击事件,加上一个事件修饰符stop来阻止事件冒泡,这样除了点击dropdown意外的任何地方,document都会触发点击事件。
  • 第二个思路就是让事件冒泡到document,通过判断事件对象包不包括我们的目标对象,如果不包括说明点击的是页面的其他地方,就设置isOpen为false。这里用了到了组合式api,新建文件package/hooks/useClickOutside.ts
import { ref, onMounted, onUnmounted, Ref } from 'vue'

const useClickOutside = (elementRef: Ref<null | HTMLElement>) => {const isClickOutside = ref(false)const handler = (e: MouseEvent) => {if (elementRef.value) {if (elementRef.value.contains(e.target as HTMLElement)) {isClickOutside.value = false} else {isClickOutside.value = true}}}onMounted(() => {document.addEventListener('click', handler)})onUnmounted(() => {document.removeEventListener('click', handler)})return isClickOutside
}

export default useClickOutside 

然后直接导入即可使用定义的useClickOutside函数。这里监听isClickOutside的状态来更改isOpen的状态。

import useClickOutside from "../hooks/useClickOutside";
...
const isClickOutside = useClickOutside(dropdownRef);

watch(isClickOutside, () => {if (isOpen.value && isClickOutside.value) {isOpen.value = false;}
}); 
form

首先看下文档用法

<form><div class="form-group"><label for="exampleInputEmail1">Email address</label><input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp"><small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small></div><div class="form-group"><label for="exampleInputPassword1">Password</label><input type="password" class="form-control" id="exampleInputPassword1"></div><div class="form-group form-check"><input type="checkbox" class="form-check-input" id="exampleCheck1"><label class="form-check-label" for="exampleCheck1">Check me out</label></div><button type="submit" class="btn btn-primary">Submit</button>
</form> 

首先编写ValidateForm组件:

<template><form class="validate-form-container"><slot name="default"></slot><div class="submit-area" @click.prevent="submitForm"><slot name="submit"><button type="submit" class="btn btn-primary">提交</button></slot></div></form>
</template>

<script lang="ts"> import { defineComponent, onUnmounted } from 'vue'
import mitt from 'mitt'
type ValidateFunc = () => boolean
export const emitter = mitt()
export default defineComponent({emits: ['form-submit'],setup(props, context) {let funcArr: ValidateFunc[] = []const submitForm = () => {const result = funcArr.map(func => func()).every(result => result)context.emit('form-submit', result)}const callback = (func?: ValidateFunc) => {if (func) {funcArr.push(func)}}emitter.on('form-item-created', callback)onUnmounted(() => {emitter.off('form-item-created', callback)funcArr = []})return {submitForm}}
}) </script> 

接着编写ValidateInput.vue组件:

<template><div class="validate-input-container pb-3"><inputclass="form-control":class="{'is-invalid': inputRef.error}"@blur="validateInput"v-model="inputRef.val"v-bind="$attrs"><span v-if="inputRef.error" class="invalid-feedback">{{inputRef.message}}</span></div>
</template>

<script lang="ts">
import { defineComponent, reactive, PropType, onMounted, computed } from 'vue'
import { emitter } from './ValidateForm.vue'
const emailReg = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
interface RuleProp {type: 'required' | 'email' | 'custom';message: string;validator?: () => boolean;
}
export type RulesProp = RuleProp[]
export type TagType = 'input'
export default defineComponent({props: {rules: Array as PropType<RulesProp>,modelValue: String,tag: {type: String as PropType<TagType>,default: 'input'}},inheritAttrs: false,setup(props, context) {const inputRef = reactive({val: computed({get: () => props.modelValue || '',set: val => {context.emit('update:modelValue', val)}}),error: false,message: ''})const validateInput = () => {if (props.rules) {const allPassed = props.rules.every(rule => {let passed = trueinputRef.message = rule.messageswitch (rule.type) {case 'required':passed = (inputRef.val.trim() !== '')breakcase 'email':passed = emailReg.test(inputRef.val)breakcase 'custom':passed = rule.validator ? rule.validator() : truebreakdefault:break}return passed})inputRef.error = !allPassedreturn allPassed}return true}onMounted(() => {emitter.emit('form-item-created', validateInput)})return {inputRef,validateInput}}
})
</script> 

这里核心的地方有两点:

  • 自定义组件实现v-model,vue2中自定义组件实现v-mdel必须要绑定一个value属性和input事件,在input事件中将输入的值传递给value。在vue3中就需要绑定一个modelValueupdate:modelValue事件
  • 还有就是父子组件之间的传值问题,因为有插槽,没办法使用常规的属性传值,这里使用的事件传值采用了一个第三方库mitt。在父组件中通过emitter.on('form-item-created', callback)来注册事件,在子组件中通过emitter.emit('form-item-created', validateInput)触发事件。

验证

新建文件package/index.ts

import 'bootstrap/dist/css/bootstrap.min.css'

//导入组件
import Dropdown from "./Dropdown/Dropdown.vue";
import DropdownItem from "./Dropdown/DropdownItem.vue";

const components = [Dropdown,DropdownItem
]

const install = (Vue: any) => {components.forEach((_: any) => {Vue.component(_.name, _);});
};

export default {install
}; 

将写的组件依次导入,然后定义一个install函数,该函数有一个Vue实例的参数,在函数中依次遍历我们的导入组件数组,然后将组件挂载到vue实例上,导出install函数。

在根目录下的main.ts上使用我们的新组件:

import { createApp } from 'vue'
import App from './App.vue'

import luckyUi from './package/index';

const app = createApp(App)

app.use(luckyUi);

app.mount('#app') 

在app.vue中进行测试:

<template><div><div class="dropdown"><!-- 测试dropdown --><dropdown :title="`你好啊`"><dropdown-item><a href="#">王大</a> </dropdown-item><dropdown-item><a href="#">王二</a></dropdown-item><dropdown-item disabled><a href="#" class="dropdown-item">王三</a></dropdown-item><dropdown-item><a href="#" class="dropdown-item">王四</a></dropdown-item></dropdown></div></div>
</template> 

最后使用vue自带的脚手架进行打包,详细可看文档。

在package中配置打包命令:

"lib": "vue-cli-service build --target lib --name lucky-ui ./src/package/index.ts" 

运行npm run lib即可在dist目录下查看。

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

相关文章:

  • 【深度学习100例】—— Python+OpenCV+MediaPipe实时人流检测 | 第3例
  • Mysql和ES数据同步方案汇总
  • Java / Tensorflow - API 调用 pb 模型使用 GPU 推理
  • 【CSS】精灵图 背景图 阴影 过渡
  • 【设计模式】【第五章】【开具增值税发票】【建造者模式 + 原型模式】
  • 【关于Linux中权限管理】
  • Opencv项目实战:11 使用Opencv高亮显示文本检测
  • 零基础转行,入职军工类测试方向,月薪10K | 既然选择了,就要全力以赴
  • python字典与集合还有数据类型转换
  • CH559L单片机ADC多通道采样数据串口打印案例
  • 2022保研夏令营/预推免记录:浙大计院直博/西湖电子直博/南大软院/厦大信院
  • windows域KCC知识点
  • 优化树莓派上的网站:免费申请SSL证书 3/4
  • 深度学习梯度下降优化算法(AdaGrad、RMSProp、AdaDelta、Adam)(MXNet)
  • 2022 最新的 Java 八股文合集来了,彻底解决各大大厂面试难题
  • 《网管员必读——网络组建》(第2版)电子课件下载
  • 【跃迁之路】【669天】程序员高效学习方法论探索系列(实验阶段426-2018.12.13)...
  • FineReport中如何实现自动滚屏效果
  • JS进阶 - JS 、JS-Web-API与DOM、BOM
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • nginx 负载服务器优化
  • Otto开发初探——微服务依赖管理新利器
  • Python爬虫--- 1.3 BS4库的解析器
  • Python十分钟制作属于你自己的个性logo
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • 阿里云前端周刊 - 第 26 期
  • 给第三方使用接口的 URL 签名实现
  • 机器学习学习笔记一
  • 每个JavaScript开发人员应阅读的书【1】 - JavaScript: The Good Parts
  • 面试遇到的一些题
  • 小程序01:wepy框架整合iview webapp UI
  • 小试R空间处理新库sf
  • 你学不懂C语言,是因为不懂编写C程序的7个步骤 ...
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • $.ajax,axios,fetch三种ajax请求的区别
  • (2)Java 简介
  • (4) PIVOT 和 UPIVOT 的使用
  • (libusb) usb口自动刷新
  • (NSDate) 时间 (time )比较
  • (python)数据结构---字典
  • (六)激光线扫描-三维重建
  • (转)详解PHP处理密码的几种方式
  • **PyTorch月学习计划 - 第一周;第6-7天: 自动梯度(Autograd)**
  • .cn根服务器被攻击之后
  • .net 8 发布了,试下微软最近强推的MAUI
  • .NET 的静态构造函数是否线程安全?答案是肯定的!
  • .net 写了一个支持重试、熔断和超时策略的 HttpClient 实例池
  • .net 重复调用webservice_Java RMI 远程调用详解,优劣势说明
  • .NET导入Excel数据
  • @property python知乎_Python3基础之:property
  • @Validated和@Valid校验参数区别
  • [BZOJ3211]:花神游历各国(小清新线段树)
  • [cb]UIGrid+UIStretch的自适应
  • [C语言]——分支和循环(4)
  • [ESP32] 编码旋钮驱动