基于vue的ui组件库开发—— 01

前言

随着重复项目的增多,通用组件在前端开发中越来越重要了,开发者更细分、聚焦于组件层面的开发,然后像搭积木一样完成应用功能。组件库可以统一管理组件,输出文档,能提升组件复用性、避免重复造轮子。

背景

为什么要搭建属于团队自己的组件库?

每个公司或者细化到团队都有属于自己的ui特性,例如(华为习惯用红色,平安习惯用橙色)。我们在利用开源的ui库的时候,免不了需要二次开发。正好团队最近在一些通用的机器人会话相关的产品,需要提供给集团下各个系列公司统一的样式、布局规范以接入,还需要统一扩展基础组件的能力。于是组件库的需求的就这么出来了!目前是以 vue2+webpack 的为基本框架来开发,后期我们将升级到vue3+vite的框架

1. 项目基础架构搭建

刚开始开发组件库,我们肯定是要建起项目的基础架构,既然是vue2 为基本框架,那自然是通过vue-cli 来构建项目了。

// 全局安装@vue/cli
npm install -g @vue/cli

// 创建基础项目,并选择vue2模板
vue create llz-ui

此时停笔思考:基础的vue脚手架搭建完成,接下来我应该干什么?我们的目的是要构建一个组件库,并且这个组件库需要提供给我们自己的前端工程项目引用,并且可以全局生效,此时脑海中大概有了一个思路:通过一个目录文件管理所有的ui组件,然后将这些组件统一提供一个出口,使之可以全局注册到前端项目中。这不就是我们平时引用第三方组件库时候的做法吗?OK,可以先看下我的项目目录结构:

llz-ui
├─ .browserslistrc
├─ .eslintignore
├─ .eslintrc.js
├─ .gitignore
├─ README.md
├─ babel.config.js
├─ components
│  ├─ button
│  │  ├─ button.vue
│  │  ├─ index.js
│  │  └─ index.less
│  ├─ icon
│  │  ├─ fonts
│  │  │  ├─ wbs-icon.svg
│  │  │  ├─ wbs-icon.ttf
│  │  │  ├─ wbs-icon.woff
│  │  │  └─ wbs-icon.woff2
│  │  ├─ icon.vue
│  │  ├─ index.js
│  │  └─ index.less
│  ├─ index.js
│  ├─ index.less
│  ├─ nav-bar
│  │  ├─ index.js
│  │  ├─ index.less
│  │  └─ nav-bar.vue
│  └─ style
│     ├─ base.less
│     ├─ hairline.less
│     ├─ theme.less
│     └─ utils.less
├─ demo
│  ├─ App.vue
│  ├─ assets
│  │  └─ logo.png
│  ├─ main.js
│  └─ modifiyStyle.less
├─ jsconfig.json
├─ package-lock.json
├─ package.json
├─ public
│  ├─ favicon.ico
│  └─ index.html
└─ vue.config.js

我们做了以下几个修改:

  1. 在根目录下新增了一个 components目录,这个目录用来存放所有的即将开发的ui组件,例如 buttonnav-bar 等等。
  2. src修改为 demo,其实此处修改意义不太大,但我的目的是让你更清楚的知道后面将通过demo里面的的业务代码验证引入组件后的效果,这里将相当于是一个验证的demo入口。

既然我们将 src 修改了名称,再启动项目必定是会报错,因为vue脚手架的默认入口是src路径下的。因此我们需要修改一下vue.config.js 的配置,相应的修改点如下:

  1. 修改 entry 入口配置;
  2. 配置路径别名,路径别名指向 components的路径下;
  3. 修改 css.loaderOptions,可以向webpack的预处理器loader 传递选项,本项目我们采用的less样式。
const { defineConfig } = require('@vue/cli-service')
const path = require("path");

module.exports = defineConfig({
  outputDir: "dist",
  publicPath: "./",
  css: {
    extract: true,
    sourceMap: false,
    loaderOptions: {
      less: {
        modifyVars: {
          // 直接覆盖变量
          // "color-primary": "orange",
          // "color-success": "green",
          // 或者可以通过 less 文件覆盖(文件路径为绝对路径)
          hack: `true; @import "/demo/modifiyStyle.less";`,
        },
      },
    },
  },
  pages: {
    index: {
      // page 的入口
      entry: "demo/main.js",
      // 模板来源
      template: "public/index.html",
      // 在 dist index.html 的输出
      filename: "index.html",
      // 当使用 title 选项时,
      title: "Askbob Ui",
    },
  },
  configureWebpack: {
    // 路径配置
    resolve: {
      extensions: [".js", ".vue", ".jsx", ".css", ".less"],
      alias: {
        '@': path.resolve(__dirname, './components') // 路径别名
      }
    },
  }
})
2. ui组件的开发 —— Button组件

我们知道需要在 components 目录下开发所有的ui组件,我们首先先实现一个简单的 button 组件。OK,那么我们新建一个 button 目录,并且在该目录下新建一个 button.vue组件,我们在引用一个按钮组件的时候,是可以配置按钮的各种属性特征的,比如:按钮形状、按钮大小、是否禁用等等。业务代码中引用这个组件,再通过透传这些属性变量值,可以做到控制按钮的ui,那么我们就需要通过 props 来接受传入的属性值,这也是开发ui组件的最核心的地方: 通过 props来接受变量控制当前组件的ui效果。话不多说,上代码:

// button.vue
<template>
  <div
    :disabled="disabled"
    @click="handleClick"
    :class="[
      `askbob-button`,
      `askbob-button--${size}`,
      {
        'is-disabled': disabled,
        'is-round': round,
        'is-border': border,
      },      
    ]"
  >
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: `askbob-button`,
  props: {
    size: {
      type:String,
      default:'normal'
    },
    round: Boolean,
    disabled: Boolean,
    border:Boolean
  },
  methods: {
    handleClick(event) {
      if (!this.disabled) this.$emit("click", event);
    },
  },
};
</script>

上述代码中可以看到:

  1. 我们预留了一个插槽 <slot></slot>,用来在引用组件的时候输入button的名称;
  2. 通过 :class中定义的不同类名来一起控制按钮的样式,askbob-button--${size} 接受size 参数,比如samllnormal 等控制按钮大小,is-disabled控制禁用效果,is-round 控制按钮的形状。

css 的代码如下。在如下代码中有几个细节:

  1. 头部通过 @import 引入一些公共样式;
  2. css代码中有包括例如 @disabled-opacity@color-primary 的一些公共参数属性,这个也是定义在公共样式中的;
@import '../style/base.less';
@import '../style/theme.less';
@import '../style/utils.less';

.askbob-button {
  position: relative;
  width: 100%;
  height: 36px;
  background: @color-primary;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  .font-title2();
  color: @color-white;
  cursor: pointer;

  &.is-disabled {
    opacity: @disabled-opacity;
    cursor: not-allowed;

    &:active::after {
      content: none !important;
    }
  }

  &.is-round {
    border-radius: 19px;

    &:active::after {
      border-radius: 19px;
    }
  }

  &:active {
    

    &::after {
      content: '';
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      background: rgba(red(@color-active), green(@color-active), blue(@color-active), @active-opacity);
    }
  }

  &--big {
    height: 40px;
    .font-title1();
    font-weight: 600;
  }

  &--small {
    height: 32px;
    width: auto;
    min-width:88px;
    display: inline-block;
    line-height: 32px;
    padding: 0 @spacing-padding-base;
    font-weight: 400;

  }

  &.is-border {
    background: none;
    border: 1px solid @color-primary;
    color: @color-primary;

    &:active {
      background: @color-select-bg;

      &::after {
        content: none
      }
    }

    &.is-disabled {
      border: 1px solid #ccc;
      color: #999999;
      opacity: 1;

      &:active {
        background: none;

      }
    }
  }
}
3. 组件的导出和全局注册

开发完一个简单的 button 组件之后,我们要面临下面的问题:我们开发的的 button 组件如何导出?如何能被项目引用并且生效?首先在button目录下新建index.js 文件,作用是为了导出 button 组件:

// index.js
import Button from './button';
export default Button;

components中,我们开发的组件除了button以外会越来越多,这些组件汇总在components 目录中,我们需要提供一个公共的出口,使这些组件可以注册成功。我们在 components 根目录中新建一个公共出口文件 index.js,通过动态导入的方式寻找 components 下所有的组件的路径,定义注册函数install(Vue),该方法可以将所有的组件通过全局注册的方式 Vue.component依次注册成功。

// 通过require.context动态导入模块,且不需要显示导入

const components = [];
const routesContext = require.context("./", true, /index.js/);

routesContext.keys().forEach((modulePath) => {
  const route = routesContext(modulePath);
  if (route?.default?.name) {
    components.push(route.default || route); // 获取路径
  }
});

// 定义 install 方法注册组件
const install = function (Vue) {
  if (install.installed) return;
  install.installed = true;
  // 遍历并注册全局组件
  components.map((component) => {
    Vue.component(component.name, component);
  });
};
if (typeof window !== "undefined" && window.Vue) {
  install(window.Vue);
}

export default {
  install
};
4. 组件在demo中的使用

在demo项目中引用上述开发的组件,操作和正常的第三方ui组件库的饮用方式一样,在 main.js引用:

import Vue from 'vue'
import App from './App.vue'
import AskbobUi from '@'
import '@/index.less'

Vue.use(AskbobUi)
Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

在 App.vue 组件中使用:

<askbob-button size="small">
   点我试试
</askbob-button>

发现是可以正常使用的,以此类推,其他的组件也采用类似的开发方式进行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值