在Vue中如何开发一个UI组件库

185 阅读6分钟

大家好,今天给大家分享的是如何在Vue中开发一个组件库。我们在开发中经常会用到一些ui 组件库,比如element plus , vant ui 等。 这些ui组件库封装了非常丰富的组件,我们使用起来非常方便。安装,导入,就可以使用了。那么我们能不能开发一个自己的ui组件库呢!

今天,我将带领大家一起探讨如何在 Vue 中开发一个简单的组件库。我们的目标是创建一个包含两个组件的库:UiButton 和 UiDialog。这两个组件将分别支持内容自定义、样式变化、事件处理等功能,并能够通过 app.use 的形式进行全局注册。

需求分析

具体需求如下:

  • 需要开发一个组件库
  • 这个组件库包含两个组件, UiButton 组件和UiDialog 组件
  • UiButton 组件要支持里面的内容自定义,比如自定义文案,甚至可能会加入图标等
  • UiButton 组件还需要支持根据传入type 来决定显示对应颜色的按钮
  • UiDialog 需要支持弹框标题的传入
  • UiDialog 需要支持内容模板自定义
  • UiDialog 底部也需要至支持模板自定义
  • UiDialog 要有一个关闭按钮,点击可以关闭按钮向外界发送事件,外界进行关闭
  • 这个组件库需要支持全部导入和按需导入
  • 这个组件库需要使用app.use的形式使用

我们先来实现一个UiButton 组件。 即第三条和第四条需求开发

UiButton 组件实现

  • 在components 下面新建一个文件夹UiPlus用来存放我们全部的ui组件
  • 在UiPlus 下面新建UiButton 文件夹, 并在该文件夹下新建UiButton.vue
  • 编写UiButton.vue 组件代码
<template>
  <button class="ui-button" :class="'btn-' + type">
    <slot></slot>
  </button>
</template>

<script setup>
const props = defineProps({
  type: {
    type: String,
    validator(value) {
      return ['primary', 'success', 'info', 'warning', 'danger'].includes(value)
    }
  }
})
</script>

<style lang="scss" scoped>
.ui-button {
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  color: #606266;
  padding: 8px 15px;
  background: #fff;
  cursor: pointer;

  &.btn-primary {
    background: #409eff;
    border-color: #409eff;
    color: #fff;

    &:hover{
      background: rgb(121.3, 187.1, 255);
      border-color: rgb(121.3, 187.1, 255);
    }
  }

  &.btn-success {
    background: #67c23a;
    border-color: #67c23a;
    color: #fff;

    &:hover{
      background: rgb(148.6, 212.3, 117.1);
      border-color: rgb(148.6, 212.3, 117.1);
    }
  }
  &.btn-info {
    background: #909399;
    border-color: #909399;
    color: #fff;

    &:hover {
      background: rgb(177.3, 179.4, 183.6);
      border-color: rgb(177.3, 179.4, 183.6);
    }
  }

  &.btn-warning {
    background: #e6a23c;
    border-color: #e6a23c;
    color: #fff;

    &:hover {
      background: rgb(237.5, 189.9, 118.5);
      border-color: rgb(237.5, 189.9, 118.5);
    }
  }

  &.btn-danger {
    background: #f56c6c;
    border-color: #f56c6c;
    color: #fff;

    &:hover {
      background: rgb(248, 152.1, 152.1);
      border-color: rgb(248, 152.1, 152.1);
    }
  }
}
</style>

由上面的组件代码可以看出

  • 我们定义了type 属性,需要外界传入,并且添加了验证,满足第四条需求。
  • 添加了slot 占位符。用户可以自定义button 显示的内容,满足第四条需求

现在我们再来开发UiDialog

UiDialog 组件实现

  • 在UiPlus下面新建UiDialog文件夹, 并在UiDialog 文件夹下面新建UiDialog.vue
  • 编写UiDialog 组件代码
<template>
  <div class="dialog-wrap" v-if="model">
    <div class="dialog">
      <div class="header">
        <h3>{{ title }}</h3>
        <span class="close" @click="close">X</span>
      </div>
      <div class="body">
        <slot></slot>
      </div>
      <div class="footer">
        <slot name="footer"></slot>
      </div>
    </div>
  </div>
</template>

<script setup>
const props = defineProps({
  title: {
    type: String,
    default: 'dialog title'
  }
})

const model = defineModel()

const emit = defineEmits(['close'])
const close = () => {
  emit('close')
}
</script>

<style lang="scss" scoped>
h3{
  margin: 0;
}
.dialog-wrap {
  position: fixed;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  background: rgba(0,0,0,0.5);

  .dialog {
    position: fixed;
    width: 600px;
    height: 320px;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    background: #fff;
    border-radius: 4px;
    box-shadow: 0px 12px 32px 4px rgba(0, 0, 0, .04), 0px 8px 20px rgba(0, 0, 0, .08);
    
    .header {
      padding: 0 20px;
      height: 40px;
      line-height: 40px;
      display: flex;
      justify-content: space-between;

      .close {
        cursor: pointer;
      }
    }
    .body{
      padding: 0 20px;
      height: 200px;
      overflow: auto;
    }

    .footer {
      display: flex;
      justify-content: flex-end;
      padding: 0 20px;
    }
  }
}
</style>

这个UiDialog 组件代码中:

  • 我们定义了title 属性,由外界传入,符合第5条需求
  • 我们定义了close方法, 点击的关闭按钮的时候触发,并向外界发送close 事件。其次我们使用了defineModel 返回的变量来决定UiDialog 的显示,这意味着,外界可以由v-model 绑定一个变量来决定弹框的显示。外界接收到close事件后将v-model绑定的变量值更改为false 即可。 满足第8条需求
  • 我们分别使用了默认插槽和具名插槽来占位弹框的内容,和底部的内容,满足第6,7条需求

现在我们来使用下这两个组件:

<template>
  <div>
    <UiButton class="btn">default</UiButton>
    <UiButton type="primary" class="btn">primary</UiButton>
    <UiButton type="success" class="btn">success</UiButton>
    <UiButton type="info" class="btn">info</UiButton>
    <UiButton type="warning" class="btn">warining</UiButton>
    <UiButton type="danger" class="btn">danger</UiButton>

    <div style="margin-top:30px;">
      <UiButton @click="openDialog">点击显示弹框</UiButton>
    </div>
    <UiDialog  title="删除确认框标题" v-model="showDialog" @close="close">
      <p>确定删除100条记录吗</p>
      <template #footer>
        <UiButton class="btn" @click="close">取消</UiButton>
        <UiButton type="primary" @click="close">确认</UiButton>
      </template>
    </UiDialog>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import UiButton from '@/components/UiPlus/UiButton/UiButton.vue'
import UiDialog from '@/components/UiPlus/UiDialog/UiDialog.vue'

const showDialog = ref(false)
const openDialog = () => {
  showDialog.value = true
}
const close = () => {
  showDialog.value = false
}
</script>

<style scoped>
.btn {
  margin-right: 10px;
}
</style>

来看看运行效果:

image.png

点击 点击弹框按钮

image.png

点击关闭,可以看到弹框也能正常关闭。

现在可以看到大部分需求我们都已经满足了,但是还有最后两条不满足。最后这两条我们该怎么做呢?

按需导入,并能使用app.use 的形式使用组件

在UiButton文件夹下新增index.js 文件 编写index.js 代码如下:

import UiButton from "./UiButton.vue";

export default {
  install(app) {
    app.component("UiButton", UiButton);
  },
};

在UiDialog 文件夹下也新增index.js,编写代码如下

import UiDialog from "./UiDialog.vue";

export default {
  install(app) {
    app.component("UiDialog", UiDialog);
  },
};

修改main.js

import { createApp } from "vue";
import App from "./App.vue";
import UiButton from "./components/UiPlus/UiButton/index";
import UiDialog from "./components/UiPlus/UiDialog/index";

createApp(App).use(UiButton).use(UiDialog).mount("#app");

主要新增了

import UiButton from "./components/UiPlus/UiButton/index";
import UiDialog from "./components/UiPlus/UiDialog/index";

.use(UiButton).use(UiDialog)

修改App.vue, 将UiButton和UiDialog 的导入删除。这时候我们再来看看效果:

image.png

点击 点击显示弹框

image.png

可以看到依然好使。

现在我们完成了按需引入,并且使用use 的方式使用。还差全部导入,一次性use 的方式。

全局一次性导入

在UiPlus 文件夹下新建index.js, 编写代码如下:

import UiButton from "./UiButton/UiButton.vue";
import UiDialog from "./UiDialog/UiDialog.vue";

export default {
  install(app) {
    app.use("UiButton", UiButton);
    app.use("UiDialog", UiDialog);
  },
};

修改main.js 将之前的导入UiButton 下的index.jsUiDialog 文件夹下的index.js 换成了导入UiPlus 下的index导入,将use的内容 换成use 导入的UiPlus 下的index.js

import { createApp } from "vue";
import App from "./App.vue";
import UiPlus from "./components/UiPlus";

createApp(App).use(UiPlus).mount("#app");

查看运行效果:

image.png

点击 点击显示弹框

image.png

可以看到效果依然好使。

从最后这两个需求的实现我们可以发现使用install 方法,进行注册组件。在使用时, 就可以使用app.use 的形式进行使用。

通过这次的实践,我们不仅实现了一个简单的组件库,还掌握了如何在 Vue 中进行插件化开发。这为我们在实际项目中开发自定义组件库提供了很好的参考和借鉴。希望这篇文章能对你有所帮助!