前端组件人人都在用,人人都在封装。目前已知的几个大型开源组件库也并没有给出具体的封装规范。但是根据其开放的组件样式、使用规则等我们也可以参考出一些具体的封装要求。延伸到业务和组件的使用场景,其要求可能又不完全一样。
本文章旨在以StoryBook为展示背景条件下,规范出一套基本的前端组件封装要求,提供一套更加适合大众使用的前端组件封装细则,供大家参考。

组件的分类、命名、文件结构

组件的分类

基础组件

 基础组件可以供多个项目使用,可配置项更多,展示形式及功能更灵活、丰富。
 作为基础组件,可以是业务组件封装的基础,默认的UI可能不能够直接用于项目,但是其功能及提供的配置参数,可以满足我们在此基础组件上封装业务组件。
  • 展示组件
    SrollView、盒子模型、Swiper、提示语等

  • 功能组件
    confirm弹窗、toast弹窗、全页面蒙层、滚动至顶部、tips、tab、swiper等

业务组件

业务组件主要是为了能在更少时间更少配置的情况下,快速搭建同一业务条线下相同风格的的页面。减少开发成本,组件的利用率可能不会很高,但是使用成本却大大降低,因为它更贴近业务,包括接口数据格式和字段名称、UI样式等,都可以不单独配置。
  • 后台页面组件
    后台组件一般设计风格都高度统一,对业务条线区分不细致。封装的组件利用率更高。但是,一般而言,目前开源的组件库,例如element-UI就已经很大部分基础组件可以支持后台系统使用。
    封装主要针对具有业务特性的组件
    例如:类目选择、图片上传、视频上传、toast提示、confirm提示等组件

  • ToC页面组件

    首页组件 金刚区、瀑布流、公告位、轮播图等
    单品页组件 图片缩放、楼层盒子、流程图、推荐位、详情、视频、倒计时等
    列表页组件 搜索项、地址、多选、单选、价格滑块、地址回填、卡片列表、分页加载等
    其他页面 信息填写、表单提交、地址选择等
    公共业务组件 搜索框、底部tab、空提示、错误提示d等

组件的命名

  • 凸显作用

    如果作为基础组件,命名可以大众,例如:Button、 Toast、Confirm、 Tips、 srollView等
    如果作为业务组件,命名需要有特定开头,例如 :

    1. 首页组件-IndexConfirm、IndexSwiper等
    2. 单品页组件-ItemSwiper、ItemRecommend、ItemVideo等
    3. 如果组件不通用,又分了特定的业务条线,则需要增加业务前缀;例如:PMItemSwiper、PMItemRecommend、PMItemVideo、FangItemSwiper等
  • 首字母大些
    组件的命名需要统一风格,都以大些英文开头

文件结构

基于组件的分类和功能,文件结构清晰可以大大提高组件的查找及定位成本。所以,基本的文件结构需要构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
├── .storybook                     # 入口文件
├── bin # 安装的依赖
├── storybook-static # 打包后静态资源文件夹
└── src
├── api # 接口
├── assets # 静态资源
├── components # 组件文件夹
├── BaseComponent # 基础组件文件夹
├── PMComponent # 业务组件文件夹1
├── CommonComponent # 业务-公共组件
├── IdexComponent # 业务-首页组件
├── ItemComponent # 业务-单品页组件
├── ListComponent # 业务-列表组件
├── OtherComponent # 业务-其他组件
├── FangComponents # 业务组件文件夹2
├── utils # 工具文件
├── index # 入口文件

ClassName命名

CSS 命名使用 BEM 命名规范,BEM 代表 “块(block),元素(element),修饰符(modifier)”

命名模式

BEM 约定的命名模式为:

namespace-block__element–modifier

  • 中划线: 仅作为连字符使用,表示某个块或者某个子元素的多单词之间的连接记号;
  • __ 双下划线:用来连接块和块的子元素;
  • – 双中划线:用来描述一个块或者块的子元素的一种状态;

    命名空间(namespace)

命名空间用来区分组件类别:

  • yp: yolkpie缩写,表示通用组件;
  • pm: 拍卖业务组件(太长了,要不要换个短点的);
  • fang: 房产业务组件(更长…);

块(block)

一个块就是一个组件。 块的命名规则如下:

块的名称使用类名,不能是 ID;
块的名称与组件类名一致,小写,多个英文单词用小驼峰(如 topSwiper);
块的命名要语义化,如果不是通用的组件,尽量不要占用通用命名(如 list、 item等);

元素(element)

块中的子元素是块的子元素,并且子元素的子元素在 BEM 里也被认为是块的直接子元素
元素的命名规则如下:

使用 js 控制的元素要以 js 开头;
元素的命名也要语义化(比如按钮,要尽量说明功能,推荐使用confirmBtn 而不是 btn)
小写,多个英文单词用小驼峰(如 confirmBtn、jsCountdown)

常见元素命名约定如下:

wrap / container: 包裹容器
header: 头部
footer: 底部
content: 内容
title: 标题
label: 标签
btn: 按钮
text: 文本
img: 图片
icon: 图标
input: 输入框

修饰符(modifier)

一个修饰符可以理解为一个块的特定状态,标识着它持有一个特定的属性,如按钮的大小及选中状态等
修饰符的命名规则如下:

修饰符尽量使用形容词

常见状态命名约定如下:

  • 尺寸:small / normal / big
  • 选中状态:selected
  • 激活状态:active / focus
  • 禁用状态:disabled

书写原则

原则上不要出现两层以上选择器嵌套
出现多层嵌套时,有两种处理办法,一种是用命名来解耦,所有类名都为一层,另一种是检查下是否需要创建新块

(1)用命名来解耦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div class="yp-block">
<div class="yp-block__header">
<h2 class="yp-block__header__title"></h2>
</div>
<div class="yp-block__content">
<img class="yp-block__content__img" src="" />
</div>
</div>

// 推荐(将title和img命名提升到block下)
<div class="yp-block">
<div class="yp-block__header">
<h2 class="yp-block__title"></h2>
</div>
<div class="yp-block__content">
<img class="yp-block__img" src="" />
</div>
</div>

(2)创建新块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<section class="paimai-comments">
<h2 class="paimai-comments__title"></h2>
<article class="paimai-comments__comment">
<h3 class="paimai-comments__comment__title"></h3>
</article>
<article class="paimai-comments__comment">
<h3 class="paimai-comments__comment__title"></h3>
</article>
</section>

// 推荐(将comment提取为新块)
<section class="paimai-comments">
<h2 class="paimai-comments__title"></h2>
<article class="paimai-comment">
<h3 class="paimai-comment__title"></h3>
</article>
<article class="paimai-comment">
<h3 class="paimai-comment__title"></h3>
</article>
</section>

组件与组件嵌套时通过在子组件上增加 BEM 类名控制子组件样式

1
2
3
4
5
6
7
// 推荐
<section class="paimai-comments">
<h2 class="paimai-comments__title"></h2>
<comment class="paimai-comments__comment" />
<comment class="paimai-comments__comment" />
</section>

样式书写

  • 最外层盒子不写宽高,使用自适应 (自适应)
  • 使用父套子的方式,给类名加权重和作用域,以保证绝对的样式‘内部性’
  • 尽可能使用flex布局,不要写死宽高
  • 避免使用!important写死样式
  • 必要时限制max或者min
  • 避免使用标签选择器
  • 有暴露外部的样式书写入口
  • 统一使用scss书写
  • 遵循先盒子主体样式,后渲染样式,即遵循先定位、宽高、display等,再font-size、color、缩进,边框,背景色等书写顺序
  • 可以做适配,例如IPhoneX
  • 避免为0值制定单位,例如用 margin: 0; 而避免使用 margin: 0 px;
  • 对于小于1的值,省略前面的0,例如 .5 代替 0.5
  • 避免过量的使用简写,防止样式覆盖,例如 使用 background-color: red; 代替 background: red;
  • 对于 z-index 的值,在可控范围内,尽量写的小一点,分层级时区分数量级,同一层级,使用同一数量级值,(除弹窗外,不高于100;弹窗类最高999)
  • 所有的声明都要以分号结尾,最后一个是可选项,但是也要书写,避免出错
  • 每条声明之后,都要有一个空格
  • 声明的花括号应该单独成行,单行声明除外
  • 分区块增加注释分割,书写顺序符合文档流顺序
  • 书写托底的默认样式

外部传参数

  • 1. 数据结构标准化
    即数据类型符合一般渲染逻辑;例如:轮播图使用数组结构、基本信息使用对象、图片地址使用字符串等
  • 2. 容错处理
    内部数据结构要紧凑、稳固,接收外部传入参数时,做各种格式化处理,此处包括(托底,容错,类型判断,防止因外部输入格式不对导致组件内部报错),
  • 3. 可扩展
    为后续组件扩展留余地,不把数据结构写的太死。例如二位数组处理,数据分组格式化等
  • 4. props需要指定类型
    视情况添加默认值及必填
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
props: ['name', 'style']

改为



props: {

name: {
type: String,
default: ''
},

style: {
type: Object,
require: true
}

}


  • 5. 避免出现不同属性控制相同功能、样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

props: {
pageCount: Number,
pageSize: Number,
total: Number
}

改为

props: {
pageSize: Number,
total: Number
}

computed: {

pageCount() {
return Math.cell(this.total/this.pageSize) || 0
}

}

  • 6. 以Object代替基础类型传参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14

<my-button color="red" bgColor="green">确定</my-button>

改为

const style = {

color: 'red',
bgColor: 'green'

}

<my-button :style="style">确定</my-button>

内部变量

  • 语意化(例如:size,表示组件的大小)

  • 建议以_开头 变量名用小驼峰命名法; 如_tabList

  • 要有默认值,需要外部必传的变量可以无默认值,但使用前必须做校验,且给出必要提示:如vue的el属性

  • 组件内使用的定义组件级变量,function 内使用变量只定义在function内

  • 常量 以const 定义,可变变量以 let定义,尽量少用var,防止变量提升引起的问题

  • 对同一变量的操作,逻辑尽量放到一起,易读易理解

方法命名

遵从 动-谓-宾 原则

常用动词

单词 含义 单词 含义
get 获取 set 设置,
add 增加 remove 删除
create 创建 destory 移除
start 启动 stop 停止
open 打开 close 关闭,
read 读取 write 写入
load 载入 save 保存,
create 创建 destroy 销毁
begin 开始 end 结束,
backup 备份 restore 恢复
import 导入 export 导出,
split 分割 merge 合并
inject 注入 extract 提取,
attach 附着 detach 脱离
bind 绑定 separate 分离,
view 查看 browse 浏览
edit 编辑 modify 修改,
select 选取 mark 标记
copy 复制 paste 粘贴,
undo 撤销 redo 重做
insert 插入 delete 移除,
add 加入 append 添加
clean 清理 clear 清除,
index 索引 sort 排序
find 查找 search 搜索,
increase 增加 decrease 减少
play 播放 pause 暂停,
launch 启动 run 运行
compile 编译 execute 执行,
debug 调试 trace 跟踪
observe 观察 listen 监听,
build 构建 publish 发布
input 输入 output 输出,
encode 编码 decode 解码
encrypt 加密 decrypt 解密,
compress 压缩 decompress 解压缩
pack 打包 unpack 解包,
parse 解析 emit 生成
connect 连接 disconnect 断开,
send 发送 receive 接收
download 下载 upload 上传,
refresh 刷新 synchronize 同步
update 更新 revert 复原,
lock 锁定 unlock 解锁
check out 签出 check in 签入,
submit 提交 commit 交付
push pull 拉,
expand 展开 collapse 折叠
begin 起始 end 结束,
start 开始 finish 完成
enter 进入 exit 退出,
abort 放弃 quit 离开
obsolete 废弃 depreciate 废旧,
collect 收集 aggregate 聚集
  • 驼峰式命名

  • 使用英文拼写

  • 不要自创缩写,不会可以全拼上去,不怕名字长

  • 避免误导,同一组件中不要使用过于相似的命名

方法暴露

  1. 尽可能多的暴露方法,关于内部的一些方法定义,如果可以使组件更具灵活性,可以考虑将内部方法暴露出来。例如:功能强大的swiper组件,就暴露了很多内部定义的方法,供用户灵活的定制自己的组件

  2. 暴露的方法如果接收多个入参,就将入参以对象的方式进行传入,方便扩展

  3. 暴露的方法名最好不要与公共方法名重复了,以免混淆

  4. readme里面,写明使用方法

公共资源

工具类库的方法,使用统一引入方式,即安装引入

公共样式也使用统一引入,不再单独书写

使用工具类库的两种方案

使用目前已有的 @yolkpie/utils npm包安装到组件库

1
2
3
4
5
// 安装依赖包
npm i @yolkpie/utils
// 在项目中引用
import { addClass } from '@yolkpie/utils'

可能产生的问题是,由于是将各个类型的公共类方法统一打包到utils里面(通过Rollup 将小块代码编译成大块复杂的代码),可读性不高

直接在组件库里建立utils文件,阴影各个类型的公共方法

工具类库方法介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
utils
├── array.js # 数组
├── axios-jsonp.js # axios jsonp跨域
├── bom.js # 浏览器对象
├── cache.js # 缓存
├── cookies.js # cookie
├── dom.js # 文档对象
├── event.js # 事件
├── fixMask.js # 显示弹层时固定背景
├── floatCalculate.js # 浮点数
├── formatDate.js # 格式化日期
├── functional.js # 函数式编程
├── global.js # 全局
├── math.js # 数值
├── rem.js # 移动端适配
├── string.js # 字符串
├── tools.js # 工具
└── url.js # 链接

1)array.js 数组相关处理函数

2)axios-jsonp.js jsonp跨域数据请求方法

3)bom.js浏览器或系统相关处理函数包含isIphoneX、isSupportWebp、getEnv(获取环境信息)等方法

4)cache.js,缓存,本地存储处理操作封装(sessionStorage和localStorage

5)cookies.js,获取cookie、设置cookie和删除cookie

6)dom.js,dom相关处理函数

7)event.js,包括事件监听器、绑定事件等

8)fixMask.js,移动端浮层内滚动窗体不滚动的JS处理

9)floatCalculate.js ,浮点数计算

10)formatDate.js 日期格式化

11)functional.js 函数式编程相关函数

12)global.js 对象相关处理函数,包括根据键值获取键名、深拷贝等

13)math.js 数字相关处理函数

14)rem.js 移动端尺寸适配

15)string.js 字符串相关处理函数

16)tools.js 一些公共方法,包括throttle和debounce

17)url.js 链接相关处理函数,包括获取链接参数方法等

公共样式

1)base.scss,一些基础样式

2)flex.scss,flex布局相关样式

3)border.scss,1px像素问题方案

4)font.scss,字体的引入

5)animation,动画