【组件库】搭建UI组件库攻略
DEMO体验
一、需要哪些能力
独立的开发环境
组件交互式展示
自动生成文档
支持自动化测试
代码提交规范限制
自动化构建、部署
自动管理包依赖
接下来我们逐个实现
二、新建项目、新增一个组件
新建项目
1 |
|
新建组件
新增一个目录用来存放组件,例如packages,并新建一个Product组件目录,在目录下创建以下3个文件
product.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102import { MouseEventHandler, useCallback, useEffect, useState } from "react";
import "./product.scss";
export interface ProductProps {
/**
* 布局
*/
layout?: "horizontal" | "vertical";
/**
* 标题
*/
title: string;
/**
* 封面图
*/
cover?: string;
/**
* 利益点
*/
interest?: string;
/**
* 价格
*/
price: number;
/**
* 商品标签
*/
categorys?: Array<string>;
/**
* 价格标签
*/
tags?: Array<string>;
/**
* 购买
*/
handleBuy: MouseEventHandler<HTMLDivElement>;
}
/**
* 商品卡片
*/
export const Product: React.FC<ProductProps> = ({
layout = "horizontal",
...props
}) => {
const [active, setActive] = useState(false);
const handleClick = useCallback(() => {
setActive(true);
}, []);
return (
<div
className={`com-product layout_${layout} ${active ? "active" : ""}`}
onClick={handleClick}
>
<div className="com-product-cover">
<img
src={
props.cover
? props.cover
: "https://pic.imgdb.cn/item/65f9367d9f345e8d036c28cf.png"
}
/>
</div>
<div className="com-product-content">
<div className="com-product-title">
<span className="com-product-category">
{Array.isArray(props.categorys) &&
props.categorys.map((category) => {
return (
<span key={category} className="com-product-category-item">
{category}
</span>
);
})}
</span>
<span className="com-product-text">{props.title}</span>
</div>
<div className="com-product-interest">{props.interest}</div>
<div className="com-product-tag">
{Array.isArray(props.tags) &&
props.tags.map((tag) => {
return (
<span key={tag} className="com-product-tag-item">
{tag}
</span>
);
})}
</div>
<div className="com-product-buy">
<div className="com-product-buy-price">
{props.price > 0 ? "¥" + props.price : "免费"}
</div>
<div onClick={props.handleBuy} className="com-product-buy-button">
购买
</div>
</div>
</div>
</div>
);
};product.scss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115.com-product {
display: flex;
padding: 10px;
box-sizing: border-box;
background: #fff;
border-radius: 5px;
font-size: 14px;
font-family: 'Microsoft YaHei', 'Arial', sans-serif;
box-shadow: 0 0 5px 2px #eee;
&.active{
background: #fefefe;
}
&.layout_horizontal {
width: 730px;
height: 200px;
.com-product-cover {
width: 180px;
height: 180px;
}
.com-product-content {
margin-left: 10px;
flex-grow: 1;
}
}
&.layout_vertical {
width: 200px;
height: 350px;
flex-direction: column;
align-items: center;
.com-product-cover {
width: 180px;
height: 180px;
}
.com-product-content {
margin-top: 10px;
flex-grow: 1;
}
}
&-cover {
flex-shrink: 0;
border-radius: 5px;
overflow: hidden;
img {
height: 100%;
width: 100%;
object-fit: contain;
}
}
&-content {
position: relative;
}
&-title {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
}
&-category {
&-item {
font-size: 10px;
background: #ff0000;
color: #fff;
margin-right: 5px;
padding: 1px 2px;
border-radius: 2px;
}
}
&-text {
font-weight: bold;
}
&-interest {
color: #ff0000;
margin-top: 10px;
}
&-tag {
margin-top: 10px;
&-item {
border: 1px solid #999;
font-size: 10px;
margin-right: 5px;
padding: 1px 2px;
border-radius: 2px;
}
}
&-buy {
position: absolute;
left: 0;
bottom: 0;
display: flex;
justify-content: space-between;
width: 100%;
align-items: center;
&-price {
color: #ff0000;
font-weight: bold;
}
&-button {
color: #fff;
background: #ff0000;
padding: 5px 10px;
border-radius: 5px;
text-align: center;
}
}
}index.tsx
1
2
3
4import { Product } from "./product";
export type { ProductProps } from "./product";
export default Product;
此时我们得到了一个这样的UI组件
三、接入jest实现自动化测试
接入后,Product组件目录会多出一个product.test.tsx文件
1 |
|
执行自动化测试
四、接入Storybook提供独立环境、文档自动生成
Storybook 是什么
Storybook 是一个用于开发、测试和文档化 UI 组件的开源工具。它提供了一个交互式的开发环境,可以让开发人员在隔离的环境中构建和展示组件,并提供了丰富的文档生成功能。Storybook 支持多种前端框架,如 React、Vue、Angular 等,可以与各种技术栈无缝集成。
Storybook 的主要功能包括:
组件开发环境:Storybook 提供了一个独立的开发环境,可以让开发人员在隔离的环境中开发和测试组件。开发人员可以通过添加故事(Stories)来展示不同状态下的组件,以及各种使用场景。
交互式展示:Storybook 提供了一个交互式的用户界面,可以让用户在不编写代码的情况下浏览和测试组件。用户可以通过 Storybook 界面中的控件来与组件进行交互,查看不同状态下的组件效果。
文档生成:Storybook 可以自动生成组件文档,包括组件的描述、属性列表、示例代码等。开发人员可以通过 Storybook 生成的文档来快速了解组件的用法和功能,提高开发效率。
插件系统:Storybook 提供了丰富的插件系统,可以通过插件来扩展其功能。开发人员可以根据项目需求选择和定制各种插件,以满足特定的开发和测试需求。
如何使用
安装 Storybook CLI
首先,你需要安装 Storybook 的命令行工具(CLI)。你可以使用 npm 或 yarn 进行安装。在终端中执行以下命令:
1 |
|
选择框架
执行初始化命令后,CLI 会提示你选择要设置的框架。选择你的前端框架(如 React、Vue、Angular 等)。
安装依赖
根据你选择的框架,CLI 会自动下载并安装所需的依赖。这些依赖包括 Storybook 本身以及与所选框架集成的必要插件。
运行 Storybook
安装完成后,执行以下命令启动 Storybook:
1 |
|
访问 Storybook
启动成功后,你可以在浏览器中访问 http://localhost:6006,可以看到官方提供的组件示例。
挂载Product组件
在Product组件目录,在目录下创建以下文件
product.stories.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64import type { Meta, StoryObj } from "@storybook/react";
import { Product } from "./product";
const meta = {
title: "Product",
component: Product,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {},
} satisfies Meta<typeof Product>;
export default meta;
type Story = StoryObj<typeof meta>;
export const 基础: Story = {
args: {
title:
"京鲜生可生食标准鲜鸡蛋30枚礼盒装1.5kg不同产地随机发货京鲜生可生食标准鲜鸡蛋30枚礼盒装1.5kg不同产地随机发货京鲜生可生食标准鲜鸡蛋30枚礼盒装1.5kg不同产地随机发货",
cover:
"https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
price: 68.5,
handleBuy: () => {},
},
};
export const 利益点: Story = {
args: {
title:
"京鲜生可生食标准鲜鸡蛋30枚礼盒装1.5kg不同产地随机发货京鲜生可生食标准鲜鸡蛋30枚礼盒装1.5kg不同产地随机发货京鲜生可生食标准鲜鸡蛋30枚礼盒装1.5kg不同产地随机发货",
cover:
"https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
interest: "直播间专享价,下单立减2元",
price: 68.5,
handleBuy: () => {},
},
};
export const 标签: Story = {
args: {
title:
"京鲜生可生食标准鲜鸡蛋30枚礼盒装1.5kg不同产地随机发货京鲜生可生食标准鲜鸡蛋30枚礼盒装1.5kg不同产地随机发货京鲜生可生食标准鲜鸡蛋30枚礼盒装1.5kg不同产地随机发货",
cover:
"https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
categorys: ["百亿补贴", "新人特惠"],
tags: ["直播间专项", "拖货包运费", "顺丰发货"],
price: 68.5,
handleBuy: () => {},
},
};
export const 垂直布局: Story = {
args: {
layout: "vertical",
title:
"京鲜生可生食标准鲜鸡蛋30枚礼盒装1.5kg不同产地随机发货京鲜生可生食标准鲜鸡蛋30枚礼盒装1.5kg不同产地随机发货京鲜生可生食标准鲜鸡蛋30枚礼盒装1.5kg不同产地随机发货",
cover:
"https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
categorys: ["百亿补贴", "新人特惠"],
tags: ["直播间专项", "拖货包运费", "顺丰发货"],
interest: "直播间专享价,下单立减2元",
price: 68.5,
handleBuy: () => {},
},
};
再次访问
可以看到我们自己写的Product组件了,已经具备了独立环境、交互式、自动化文档
文档
故事书
五、接入husky支持代码提交规范
详见:husky 攻略
六、接入webhook或jenkins支持自动化构建或部署
webhook
详见:webhook 攻略
jenkins
七、接入lerna管理包依赖
详见:使用lerna管理包依赖