15 KiB
20|组件库:如何设计你自己的通用组件库?
你好,我是大圣。上一讲TypeScript加餐学完,你是不是想赶紧巩固一下TypeScript在Vue中的使用呢?那么从今天开始,我们就重点攻克Vue中组件库的实现难点,我会用7讲的篇幅带你进入组件库的世界。
学习路径大致是这样的,首先我会给你拆解一下Element3组件库的代码,其次带你剖析组件库中一些经典的组件,比如表单、表格、弹窗等组件的实现细节,整体使用Vite+TypeScript+Sass的技术栈来实现。而业务中繁多的页面也是由一个个组件拼接而成的,所以我们可以先学习一下不同类型的组件是如何去设计的,借此举一反三。
环境搭建
下面我们直奔主题,开始搭建环境。这个章节的代码我已经推送到了Github 上,由于组件库是模仿Element实现的,所以我为其取名为ailemente。
接下来我们就一步步实现这个组件库吧。首先和开发项目一样,我们要在命令行里使用下面的命令创建Vite项目,模板选择vue-ts,这样我们就拥有了一个Vite+TypeScript的开发环境。
npm init vite@latest
关于ESLint和Sass的相关配置,全家桶实战篇我们已经详细配置了,这里只补充一下husky的内容。husky这个库可以很方便地帮助我们设置Git的钩子函数,可以允许我们在代码提交之前进行代码质量的监测。
下面的代码中,我们首先安装和初始化了husky,然后我们使用 npx husky add命令新增了commit-msg钩子,husky会在我们执行git commit提交代码的时候执行 node scripts/verifyCommit命令来校验commit信息格式。
npm install -D husky # 安装husky
npx husky install # 初始化husky
# 新增commit msg钩子
npx husky add .husky/commit-msg "node scripts/verifyCommit.js"
然后我们来到项目目录下的verifyCommit文件。在下面的代码中,我们先去 .git/COMMIT_EDITMSG文件中读取了commit提交的信息,然后使用了正则去校验提交信息的格式。如果commit的信息不符合要求,会直接报错并且终止代码的提交。
const msg = require('fs')
.readFileSync('.git/COMMIT_EDITMSG', 'utf-8')
.trim()
const commitRE = /^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release)(\(.+\))?: .{1,50}/
const mergeRe = /^(Merge pull request|Merge branch)/
if (!commitRE.test(msg)) {
if(!mergeRe.test(msg)){
console.log('git commit信息校验不通过')
console.error(`git commit的信息格式不对, 需要使用 title(scope): desc的格式
比如 fix: xxbug
feat(test): add new
具体校验逻辑看 scripts/verifyCommit.js
`)
process.exit(1)
}
}else{
console.log('git commit信息校验通过')
}
这样就确保在GitHub中的提交日志都符合type(scope): message 的格式。你可以看下Vue 3的代码提交记录,每个提交涉及的模块,类型和信息都清晰可见,能够很好地帮助我们管理版本日志,校验正则的逻辑。如下图,feat代表新功能,docs代表文档,perf代表性能。下面的提交日志就能告诉我们这次提交的是组件相关的新功能,代码中新增了Button.vue。
feat(component): add Button.vue
commit-msg是代码执行提交的时候执行的,我们还可以使用代码执行之前的钩子pre-commit去执行ESLint代码格式。这样我们在执行git commit的同时,就会首先进行ESLint校验,然后执行commit的log信息格式检查,全部通过后代码才能提交至Git,这也是现在业界通用的解决方案,学完你就快去优化一下手里的项目吧!
npx husky add .husky/pre-commit "npm run lint"
布局组件
好,现在环境我们就搭建好了,接着看看怎么布局组件。
我们可以参考Element3组件列表页面,这里的组件分成了基础组件、表单组件、数据组件、通知组件、导航组件和其他组件几个类型,这些类型基本覆盖了组件库的适用场景,项目中的业务组件也是由这些类型组件拼接而来的。
我们还可以参考项目模块的规范搭建组件库的模板,包括Sass、ESLint等,组件库会在这些规范之上加入单元测试来进一步确保代码的可维护性。
接下来我们逐一讲解下各个组件的负责范围。
首先我们需要设计基础的组件,也就是整个项目中都会用到的组件规范,包括布局、色彩,字体、图标等等。这些组件基本没有JavaScript的参与,实现起来也很简单,负责的就是项目整体的布局和色彩设计。
而表单组件则负责用户的输入数据管理,包括我们常见的输入框、滑块、评分等等,总结来说,需要用户输入的地方就是表单组件的应用场景,其中对用户的输入校验是比较重要的功能点。
数据组件负责显示后台的数据,最重要的就是表格和树形组件。
通知组件负责通知用户操作的状态,包括警告和弹窗,如何用函数动态渲染组件是警告组件的重要功能点。
接下来我们就动手设计一个基础的布局组件,这个组件相对是比较简单的。你可以访问Element3布局容器页面,这里一共有container、header、footer、aside、main五个组件,这个组合可以很方便地实现常见的页面布局。
- el-container组件负责外层容器,当子元素中包含 或 时,全部子元素会垂直上下排列,否则会水平左右排列。
- el-header、el-aside、el-main、el-footer 组件分别负责顶部和侧边栏,页面主体和底部容器组件。这个功能比较简单,只是渲染页面的布局。我们可以在src/components目录下新建文件夹container,新建Container.vue,布局组件没有交互逻辑,只需要通过flex布局就可以实现。
这几个组件只是提供了不同的class,这里就涉及到CSS的设计内容。在Element3中所有的样式前缀都是el开头,每次都重复书写维护太困难,所以我们设计之初就需要涉及Sass的Mixin来提高书写CSS的代码效率。
接着,我们在src/styles下面新建mixin.scss。在下面的代码中,我们定义了namespace变量为el,使用Mixin注册一个可以重复使用的模块b,可以通过传进来的block生成新的变量$B,并且变量会渲染在class上,并且注册了when可以新增class选择器,实现多个class的样式。
// bem
$namespace: 'el';
@mixin b($block) {
$B: $namespace + '-' + $block !global;
.#{$B} {
@content;
}
}
// 添加ben后缀啥的
@mixin when($state) {
@at-root {
&.#{$state-prefix + $state} {
@content;
}
}
}
代码看着有些抽象,不要急,我们再在 container.vue中写上下面的代码。使用@import导入mixin.scss后,就可以用include语法去使用Mixin注册的代码块。
<style lang="scss">
@import '../styles/mixin';
@include b(container) {
display: flex;
flex-direction: row;
flex: 1;
flex-basis: auto;
box-sizing: border-box;
min-width: 0;
@include when(vertical) {
flex-direction: column;
}
}
</style>
在上面的代码中,我们使用b(container)生成.el-container样式类,在内部使用when(vertical)生成.el-container.is-vertical样式类,去修改flex的布局方向。
.el-container {
display: flex;
flex-direction: row;
flex: 1;
flex-basis: auto;
box-sizing: border-box;
min-width: 0;
}
.el-container.is-vertical {
flex-direction: column;
}
container组件如果内部没有header或者footer组件,就是横向布局,否则就是垂直布局。根据上面的CSS代码,我们可以知道,只需要新增is-vertical这个class,就可以实现垂直布局。
我们在Container.vue中写下下面的代码,template中使用el-container容器包裹,通过:class来实现样式控制即可。然后你肯定会疑惑,为什么会有两个script标签?
因为开发组件库的时候,我们要确保每个组件都有自己的名字,script setup中没法返回组件的名字,所以我们需要一个单独的标签,使用options的语法设置组件的name属性。
然后在