likes
comments
collection
share

Vue3组件库设计核心要素

作者站长头像
站长
· 阅读数 13

专栏前几篇文章让我们重新认识了Vue3基础知识,从这篇文章开始,我们将以 Vue3 组件库的开发为线索,梳理设计一个企业级组件库需要考虑哪些核心要素,并实现常见组件。

在我们动手实现组件库之前,我们需要了解一下组件库设计有哪些常见的设计要素,我把它列举了下来: 

  1. monorepo 管理组件代码 
  2. 设计规范 
  3. 响应式的栅格系统

monorepo 管理组件代码

首先我们来了解下什么是monorepo?

简单来说monorepo是一种项目架构,一个仓库管理多个子项目(模块,包),前端项目诸如vue3、element-plus都是采用这种架构模式,一个简单的monorepo的项目结构类似如下:

├── packages
|   ├── examples
|   |   ├── package.json
|   ├── components
|   |   ├── package.json
|   ├── utils
|   |   ├── package.json
├── package.json
├── pnpm-workspace.yaml

monorepo 管理仓库多项目的方案有很多种,例如传统的 Lerna 技术方案、不过最近比较流行 pnpm 管理方案,这也是vue3element-plus采用的方案,那我们就基于上述项目结构,采用pnpm的方式来实现一个案例:

实现步骤具体如下:

  1. 初始化项目和配置文件
  2. 配置 monorepo 项目代码
  3. 安装所有子项目依赖

初始化项目和配置文件

目录初始化就是对根项目和子项目目录下的 package.json 依赖初始化,首先初始化项目根目录package.json 文件,执行npm init -y即可,接着我们对两个子项目的 package.json 文件进行初始化。

初始化packages/utils/package.json 文件,如下所示:

{
  "name": "@element-plus-demo/utils",
  "version": "1.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "lodash": "^4.17.21"
  }
}

上述代码中,我们给utils子项目name属性加上了@element-plus-demo这个私有前缀名称。这个是可以自定义的,一般是自己所在企业 npm 源站使用的前缀名称进行定义,方便统一命名子项目,后续的其他子项目也可以加上同样的 “@xxx/”的前缀进行统一命名。

注意,这里使用 “@xxx/”私有命名前缀不是必要的操作,只是为了方便管理子项目 npm 模块名称,一般都要加上这类命名前缀。 

此外,项目中我们添加了lodash依赖也只是为了更全面的演示各个子项目之前的依赖关系,也不是必须的。

然后我们初始化packages/components/package.json 文件,如下所示:

{
  "name": "@element-plus-demo/components",
  "version": "1.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@element-plus-demo/utils": "1.0.1",
    "vue": "3.3.4"
  }
}

上述代码中,我们将子项目name属性改为@element-plus-demo/components,并添加上项目依赖的@element-plus-demo/utils和vue。

接着在根目录pnpm-workspace.yaml 这个文件里,进行 monorepo 的项目配置,具体代码如下所示:

packages:
  - 'packages/*'

声明 packages/* 目录是用来管理所有子项目的。详细配置可以参考pnpm官方文档

配置 monorepo 项目代码

前面我们完成了配置文件的配置,不过我们想要检查配置文件是否正确,最好的方式就是通过代码进行校验。

首先我们添加上packages/utils/index.js 文件,如下所示:

import _ from "lodash";

//返回大写开头的字符串。
export const capitalize = (str) => {
  return _.capitalize(str);
};

这里我们借用lodash库,封装了capitalize函数对外暴露。

接着我们完善packages/components目录,封装一个button.vue组件并引用子目录@element-plus-demo/utils下的capitalize函数,代码如下:

<template>
  <button class="button">{{ btn }}</button>
</template>

<script setup>
import { capitalize } from "@element-plus-demo/utils";
import { ref } from "vue";
const btn = ref(capitalize("FABCDEFG22"));
</script>

<style scoped>
.button {
  color: #fff;
  background-color: #409eff;
  border-color: #409eff;
  display: inline-block;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  text-align: center;
  box-sizing: border-box;
  outline: none;
  margin: 0;
  font-weight: 500;
  padding: 12px 20px;
  font-size: 14px;
  border-radius: 4px;
}
</style>

注意这里我们还需要创建packages/components/index.js 文件,引用上面封装的button.vue文件对外暴露,具体请查看完整的项目代码

接着我们在packages目录下创建一个examples文件夹,基于vite搭建一个简单的vue脚手架工具引用前面封装的button.vue组件,项目packages/examples/package.json 文件,如下所示:

{
  "name": "@element-plus-demo/examples",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@element-plus-demo/components": "1.0.1",
    "vue": "^3.3.4"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.2.3",
    "vite": "^4.3.9"
  }
}

注意这里需要我们引用@element-plus-demo/components子项目,代码如下:

<script setup>
import { Button } from "@element-plus-demo/components";
</script>

<template>
  <main>
    <Button></Button>
  </main>
</template>

安装所有子项目依赖

最后我们安装所有子项目依赖。这里我们要先保证本地电脑有全局的 pnpm 命令,可以通过以下脚本进行安装:

npm i -g pnpm

最后再在项目的根目录下执行:

pnpm i 

到这里就可以实现 monorepo 项目的依赖安装和管理了。

我们启动examples项目后,会发现它可以调用我们前面封装的@element-plus-demo/components和@element-plus-demo/utils等模块的代码, 可以看到最终效果如图所示:

Vue3组件库设计核心要素

设计规范

在前端开发中,颜色设计规范和CSS开发规范都是非常重要的一部分。在开发具体的组件库之前,了解它们是很有必要的。

颜色设计规范

每个组件库一般都会有一个主题色,比如说Element plus就是蓝色系的,主题色的选择是根据业务需要定制的,优秀的组件库甚至支持自定义主题。所以,我们主题方案设计的第一步就是需要设计好组件库的颜色规范。

颜色规范设计通常是UI设计师的工作,不是前端开发者的职责工作。但是前端需要根据设计稿,用代码来实现原型,甚至于说参与设计稿的讨论,所以我们有必要简单的了解这些颜色规范设计的过程。

第一步是颜色种类的选择。一般设计师会选择几种大类型的颜色,例如红色、蓝色和绿色等。然后根据业务需要挑选这几类型中的一个基准色号。

第二步是基于上一步选择好的基准色号,进行颜色梯度的处理。举个例子,蓝色的颜色梯度处理如下所示:

Vue3组件库设计核心要素

第三步是辅助色的选择,比如Element Plus支持success,info,warning,danger等多种辅助主题风格,如图所示:

Vue3组件库设计核心要素

目前前端业界也有很多优秀的开源组件库都提供了颜色设计规范。例如Element Plus 官方团队的颜色规范,可以直接参考。

CSS开发规范

css开发规范主要指的是我们在开发组件样式的时候,要围绕css的两个特性附加重写来设计,不同的样式可以叠加在一起使用;同名的样式,后面的会覆盖前面的,从而达到组合应用的效果。

接下来我们总结出常见的类型样式,然后根据每个组件的特性,来组装这些类型的特性,从而达到丰富多彩的配置效果。下面以Element Plus为例,来看看这些类型样式。

1、基础样式

任何一个CSS组件在刚开始都要先定义基本样式,一般都是定义该样式下的字体(font-),内外边距(padding,margin),显示方式(display),边框(border)等其它相关内容。

比如按钮组件基本样式:

.el-button {
    display: inline-flex;
    justify-content: center;
    align-items: center;
    line-height: 1;
    height: 32px;
    white-space: nowrap;
    cursor: pointer;
    color: var(--el-button-text-color);
    text-align: center;
    box-sizing: border-box;
    outline: none;
    transition: .1s;
    font-weight: var(--el-button-font-weight);
    user-select: none;
    vertical-align: middle;
    -webkit-appearance: none;
    background-color: var(--el-button-bg-color);
    border: var(--el-border);
    border-color: var(--el-button-border-color);
    padding: 8px 15px;
    font-size: var(--el-font-size-base);
    border-radius: var(--el-border-radius-base);
}

2、颜色样式

以按钮为例,Element Plus官方提供了5种颜色的样式,分别是primary(重点蓝),success(成功绿),info(信息灰),warning(警告橙),danger(危险红)。定义规则是:命名空间-组件名称-颜色类型,如el-button--primary,el-button--success。

在定义不同颜色样式的时候,主要是定义文本颜色,边框颜色,背景颜色等等,具体定义什么颜色和该组件的特性有关。比如按钮在定义颜色样式时的代码如下:

Vue3组件库设计核心要素

这里由于Element Plus代码用了var变量,为了让大家看到颜色变化,用图片进行的展示。需要注意的是对于一些可点击元素,比如按钮,还要进行特殊处理,在hover、focus、状态的颜色需要保持同样的风格。

3、尺寸样式

很多组件诸如按钮都提供了尺寸的快捷设置。提供了large,Default,small等尺寸。

调整尺寸主要是调整所对应元素的padding、圆角设置、行间距和字体大小。如图所示:

Vue3组件库设计核心要素

需要注意的是同一个组件的不同类型的样式可以组合在一起使用,不同颜色样式和尺寸样式一起使用完全没有问题,不会引起冲突。如下例所示:

<el-button type="success" size="large">Large</el-button>

4、状态样式

有一些可点击元素,比如说按钮需要根据状态显示其效果,高亮的时候用active样式,禁用的时候用disabled样式。

这类样式一般是处理元素的阴影,鼠标形状、透明度等方面的内容。如图所示:

Vue3组件库设计核心要素

5、并列元素样式

在很多情况下可能有多个相同元素并列在一起,又或者一个组件内有多个子元素,这种情况下为了避免元素挤在一起,会增加一点间距。

这里主要考虑两种情况,其一是水平并列的时候,其二是垂直的时候,一般都是通过增加内边距或者外边距来处理,如下所示:

.el-button+.el-button {
    margin-left: 12px;
}

6、其它特殊样式

企业级的组件可能还会有一些特殊场景,诸如动画效果,多个不同的组件嵌套在一起的效果等等,这些需要具体情况具体分析,相对比较复杂。

总而言之,思路就是利用CSS的重载覆盖的概念,也因此需要注意样式定义的顺序,以免出现重载顺序错误。

栅格系统

栅格系统听着有点高大上,那什么是栅格系统?

其实就是一种页面布局方式,将一个平面划分成行(Row)和列(Col)来管理,每一行分成一个个等分的格子,通常是24份,开发人员可以自由按份组合,以便开发出简洁方便的程序。

那栅格系统有什么好处呢?

答案是栅格系统是响应式设计的核心,具体使用时可参考Element Plus布局

那如何实现栅格系统呢?

主要工作原理如下:

  1. 一行数据必须包含在元素中,以便为其赋予合适的对齐方式和内外边距。
  2. 使用行Row在在水平方向创建一组列Col。
  3. 具体内容应当放置于列内,而且只有列可以作为行的直接子元素。
  4. 行Row提供 gutter 属性来指定列之间的间距,其默认值为0。通过设置padding从而创建列之间的间距,然后通过为第一列和最后一列设置负值的margin从而抵消padding的影响。
  5. 栅格系统中国的列是通过制定1到24的值来表示跨度的范围。例如,要让屏幕分为3个等宽的部分,可以使用3个:span="8"来创建。

首先,我们需要实现一个栅格的行组件,具体代码如下:

<template>
  <div class="el-row">
    <slot></slot>
  </div>
</template>

<style scoped lang="scss">
.el-row {
  display: flex;
  flex-wrap: wrap;
  position: relative;
  box-sizing: border-box;
}
</style>

这里我们以div元素作为了容器,选择flex方式布局,接下来就是实现 Row 组件里的列组件 Col,具体代码如下:

<template>
  <div :class="{ [baseClassName]: true, [spanClassName]: true }">
    <slot></slot>
  </div>
</template>
<script setup>
const props = defineProps(["span"]);

function getSpan(propSpan) {
  if (typeof propSpan === "number") {
    const span = Math.ceil(Number(propSpan));
    if (span >= 1 && span <= 24) {
      return span;
    }
  }
  return 1;
}

const baseClassName = `baseClassName`; //基础样式
const spanClassName = `col-${getSpan(props.span)}`; //
</script>

<style scoped lang="scss">
$total-columns: 24; //列数
$column-width: 100% / $total-columns; //每列的宽度

.baseClassName {
  box-sizing: border-box;
}

@for $i from 1 through $total-columns {
  .col-#{$i} {
    width: ($column-width * $i);
    flex: 0 0 percentage((1 / $total-columns * $i));
  }
}
</style>

上述代码把一行分成了24等份,每一份宽度为100% / $total-columns,通过span属性接收来自父容器指定的宽度。这里样式代码我们使用了sass框架,如果使用css代码将会变成如下格式:

.col-1{
	4.1666666667%;
}

.col-1{
	width: 8.3333333333%;
}

...

.col-23{
	95.8333333333%;
}

正如你所看到的上面css代码,所以开源框架都使用了less或者sass框架。最后我们引用一下我们设计的栅格系统,代码如下:

<script setup>
import { Button, Row, Col } from "@element-plus-demo/components";
</script>

<template>
  <div class="container">
    <Button style="margin-bottom: 24px"></Button>
    <Row>
      <Col class="gird" :span="24">col-24</Col>
    </Row>
    <Row>
      <Col class="gird" :span="12">col-12</Col>
      <Col class="gird-dark" :span="12">col-12</Col>
    </Row>
    <Row>
      <Col class="gird" :span="8">col-8</Col>
      <Col class="gird-dark" :span="8">col-8</Col>
      <Col class="gird" :span="8">col-8</Col>
    </Row>
    <Row>
      <Col class="gird" :span="6">col-6</Col>
      <Col class="gird-dark" :span="6">col-6</Col>
      <Col class="gird" :span="6">col-6</Col>
      <Col class="gird-dark" :span="6">col-6</Col>
    </Row>
  </div>
</template>

<style scoped lang="scss">
.container {
  width: 960px;
  margin: 0 auto;
}
.gird {
  background: #8cbfe6;
  border: 1px #8cbfe6 solid;
  margin-bottom: 10px;
  height: 50px;
  color: #ffffff;
  font-size: 18px;
  text-align: center;
  line-height: 50px;
}
.gird-dark {
  background: #068aed;
  border: 1px #068aed solid;
  margin-bottom: 10px;
  height: 50px;
  color: #ffffff;
  font-size: 18px;
  text-align: center;
  line-height: 50px;
}
</style>

最终的效果如图所示:

Vue3组件库设计核心要素

完善的栅格系统还需要支持分栏间隔,响应式布局等核心功能,如果大家想知道如何实现,欢迎留言,后续补上。

总结

本篇文章正式进入专栏的组件篇,在正式开发具体组件之前,简单了解了开发一个Vue3组件库需要考虑哪些要素:

1、我们认识并且搭建了monorepo项目架构,后续组件将基于该架构进行开发。

2、我们认识了开发一个组件库是需要UI支持的,需要与他们沟通颜色规范并且了解了CSS开发设计思想。

3、我们知道了响应式设计的核心:栅格系统。

最后专栏的下一篇我们将正式的进入组件开发:如何设计一个表单组件