从0到1 开发一个自己的ui库
背景:由于最近业务开发,发现有很多业务场景相似,所以想搞一个自己的ui组件库。在此记录一下。使用技术框架:vite + ts + vue3。
可以从我的项目里把代码拖出来跑跑看看,我的示例里面写了3个组件,按钮、自动滚动列表、自动滚动表格。
仓库地址:https://github.com/lishengqin/standard-vue-cockpit-ui
ui库的文档地址:https://lishengqin.github.io/standard-vue-cockpit-ui/
下面图片是项目中install这个库后,查看这个库的目录格式,以及引入组件使用的效果。
1. 首先执行命令行生成vue项目,然后把sass sass-loader naive-ui等依赖都安装上
npm create vite@latest standard-vue-cockpit-ui -- --template vue
2. 由于是用ts写的,所有相关的操作要加上:
1)加上【tsconfig.json】文件
2)启动文件里的plugin要加上【vueJsx】,配置【esbuild】
3. 确定书写的格式:
创建一个【packages】文件夹,与src在同一级,packages里面就是放置组件的,一个组件对应一个文件夹,举例,我现在开发一个【LsqButton】按钮组件,那我创建文件的目录为:
4. 文件内容
index.scss---样式文件,就不展开说了;
LsqButton.tsx
1 import { defineComponent, PropType } from "vue"; 2 import { ButtonType } from "./index" 3 import "./index.scss" 4 export const lsqButtonProps = { 5 /* 按钮类型 */ 6 type: { 7 type: String as PropType<ButtonType>, 8 default: "primary", 9 validator(value: string) { 10 return ["primary", "text"].includes(value) 11 } 12 }, 13 /* 文字颜色 */ 14 color: { 15 type: String, 16 default: "fff" 17 }, 18 bottomLine: { 19 type: Boolean, 20 default: false 21 } 22 } 23 export const lsqButtonEmits = { 24 /* 点击触发 */ 25 click: () => { } 26 } 27 export const lsqButtonSlots = { 28 /* 自定义按钮文字插槽 */ 29 default: () => true 30 } 31 export default defineComponent({ 32 name: "lsq-button", 33 props: lsqButtonProps, 34 emits: lsqButtonEmits, 35 setup(props, { emit, slots }) { 36 let className = { 37 "lsq-button": true, 38 ['lsq_button_' + props.type]: true, 39 'lsq_button_text_border': props.type === 'text' && props.bottomLine 40 } 41 let style = { 42 color: props.color 43 } 44 const onClick = () => { 45 emit("click"); 46 } 47 return () => (<div class={className} style={style} onClick={onClick}>{slots.default && slots.default()}</div>) 48 } 49 })
index.ts -- 关键的文件
1 export type ButtonType = "primary" | "text" 2 import _lsqButton from "./LsqButton" 3 import { withInstall } from "../withInstall" 4 export const LsqButton = withInstall(_lsqButton) 5 export default LsqButton 6 declare module "vue" { 7 export interface GlobalComponents { 8 LsqButton: typeof LsqButton 9 } 10 }
同时还需要在 【packages】文件夹中写2个文件,【withInstall.ts】【index.ts】
【withInstall.ts】文件是为了install我们的组件,【index.ts】是将组件汇集,然后可以抛出去方便引入。
withInstall.ts
1 import { App } from "vue/dist/vue"; 2 3 type EventShim = { 4 new(...args: any[]): { 5 $props: { 6 onClick?: (...args: any[]) => void; 7 }; 8 }; 9 }; 10 11 export type Index<T> = T & { 12 install(app: App): void; 13 } & EventShim; 14 15 const camelizeRE = /-(\w)/g; 16 17 export function withInstall<T>(options: T) { 18 (options as Record<string, unknown>).install = (app: App) => { 19 const { name } = options as unknown as { name: string }; 20 //@ts-ignore 21 app.component(`-${name}`.replace(camelizeRE, (_, c) => c.toUpperCase()), options); 22 }; 23 return options as Index<T>; 24 }
index.ts
import { App } from 'vue' import LsqButton from "./lsq-button/index"; const components = [ LsqButton ] const install = (app: App): void => { components.map(one => one.install(app)) } // 按需加载 export { LsqButton } // 完整加载 export default { install }
index.ts 文件对于组件 2种抛出方式,这样便于用户按需使用或全量引用。其实vue.use(),它的本质就是在执行参数中的install方法,里面的按需加载抛出方法不说了,那个完整加载的思路,就是我们自己写一个installAllComponents方法,这个方法其实就是将所有的组件都install执行,然后我再抛出一个对象:{ install : 封装的installAllComponents },然后用户再全量引用的时候,vue.use(allComponents),本质就是再执行 allComponents.install(),也就是执行我们上面定义的【installAllComponents】方法。