为什么要写一个组件库?
因为随着技术的不断提升,慢慢会到技术瓶颈期,很长一段时间不会有大提升。会不想研究新的东西,不想研究新的方法,新的api,不想学习新的技术,从而陷入技术瓶颈,这个时候可以尝试写一个属于自己的组件库,写完之后会有很大的提升。
搭建项目
- 选择框架
可以选择一个自己喜欢或者自己熟悉的框架写一套组件库,我选择的是react。 - 组件库工具
可以根据自己选择的框架选择组件库工具,市面上比较流行的 2 个组件库工具分别的 dumi 和 Storybook。这里使用的是dumi ,是一款为组件开发场景而生的文档工具,与 father 一起为开发者提供一站式的组件开发体验,father 负责构建,而 dumi 负责组件开发及组件文档生成。 - 创建项目
# 先找个地方建个空目录。
$ mkdir myapp && cd myapp
# 通过官方工具创建项目,选择你需要的模板
$ npx create-dumi
# 选择一个模板
$ ? Pick template type › - Use arrow-keys. Return to submit.
$ ❯ Static Site # 用于构建网站
$ React Library # 用于构建组件库,有组件例子
$ Theme Package # 主题包开发脚手架,用于开发主题包
# 安装依赖后启动项目
$ npm start
- 代码规范
我这里使用的是ESLint
是一个广泛使用的 JavaScript 代码检查工具,可用于帮助开发者维持一致的代码风格,并捕获潜在的错误和不良习惯。它可以在开发过程中自动检测和纠正代码规范和错误,以确保高质量的代码,下面是一些基础配置:
// 安装eslint
yarn add eslint eslint-plugin-react eslint-plugin-simple-import-sort eslint-plugin-unused-imports @typescript-eslint/eslint-plugin @typescript-eslint/parser -D
// 添加 .eslintrc.js 配置文件
module.exports = {env: {browser: true,es2021: true,},extends: ["eslint:recommended","plugin:react/recommended","plugin:@typescript-eslint/recommended",],overrides: [],parser: "@typescript-eslint/parser",parserOptions: {ecmaVersion: "latest",sourceType: "module",},plugins: ["react","@typescript-eslint","unused-imports","simple-import-sort",],rules: {"no-unused-vars": "off", // or "@typescript-eslint/no-unused-vars": "off","unused-imports/no-unused-imports": "warn","unused-imports/no-unused-vars": ["warn",{vars: "all",varsIgnorePattern: "^_",args: "after-used",argsIgnorePattern: "^_",},],"simple-import-sort/imports": "warn","simple-import-sort/exports": "warn","react/react-in-jsx-scope": "off","react/prop-types": "off",},
};
// 修改 packages.json
"scripts": {"eslint": "eslint src --fix",}
- git提交规范
如果涉及到多人开发或者项目开源的话,可以规定提交规范,我这里使用的是commit提交规范,下面是一些常用的提交规范:
commitlint 可以使用这个依赖检查提交规范,方便团队开发合作。
feat:新功能
fix:修复 bug
docs:文档更新
style:代码风格调整,如空格、格式等
refactor:代码重构
test:添加或修改测试
chore:构建过程或辅助工具的变更
- 配置路由
.umirc.ts配置文件编写路由配置
可以按照我这样配置路由
import { defineConfig } from 'dumi';
export default defineConfig({
// 网站标题名
title: 'pear-element',
// 网站标题旁边图片
favicon:
'https://user-images.githubusercontent.com/9554297/83762004-a0761b00-a6a9-11ea-83b4-9c8ff721d4b8.png',
// 页面上展示的logo的图片
logo: 'https://user-images.githubusercontent.com/9554297/83762004-a0761b00-a6a9-11ea-83b4-9c8ff721d4b8.png',
// 打包发布上线的 文件包名字
outputPath: 'docs-dist',
// 配置导航条模式 // 默认纵向,
mode: 'site',
apiParser: {
// 自定义属性过滤配置,也可以是一个函数,用法参考:https://github.com/styleguidist/react-docgen-typescript/#propfilter
propFilter: {
// 是否忽略从 node_modules 继承的属性,默认值为 false
skipNodeModules: true,
// 需要忽略的属性名列表,默认为空数组
skipPropsWithName: ['autoFocus', 'form', 'formAction', 'formEncType', 'title'],
// 是否忽略没有文档说明的属性,默认值为 false
skipPropsWithoutDoc: false,
},
},
history: {
type: 'hash',
},
navs: [
// null, // 保留默认配置
{
title: '指南',
path: '/guide',
},
{
title: '组件',
path: '/component',
},
{
title: '作者',
children: [
{
title: 'CSDN',
path: '地址',
},
{
title: '掘金',
path: '地址',
},
],
},
{
title: 'GitHub',
path: '地址',
},
],
});
- 创建组件
可以按照我这种使用ts+tsx+less创建组件
src下创建组件文件,demo下写组件使用方法,测试组件,index,md配置路由和需要显示的组件使用示例,API文档等,index.module.less模块化导入组件样式,防止样式冲突,index.tsx组件源代码,interface.ts创建变量类型供源代码使用,规定变量类型,方便管理。
- 组件具体编写过程
下面是我的一个组件实现过程,源码和使用示例
demo/index1.tsx
import React from 'react';
import Button from '../index';
export default function ButtonDemo1() {
return (
<div style={{ display: 'flex', justifyContent: 'space-around' }}>
<Button type="primary" >基础按钮</Button>
<Button type="success">成功按钮</Button>
<Button type="danger">危险按钮</Button>
<Button type="warning">警告按钮</Button>
<Button type="info">信息按钮</Button>
</div>
);
}
index.md
---
title: Button按钮
nav:
title: Basic 基础组件
path: /component
group:
path: /basic
---
# Buttoon 按钮
> 常用的 button 按钮
### 基础用法
> 基本使用 button 组件用法
<code src="./demo/index1.tsx" />
### 设置宽高
> 可以通过设置 width 和 height 设置按钮宽高
<code src="./demo/index2.tsx" />
### 设置圆角
> 可以通过设置 radius 设置按钮圆角
<code src="./demo/index3.tsx" />
### 文本按钮
> 可以通过设置 radius 设置按钮圆角
<code src="./demo/index4.tsx" />
### 禁用按钮
> 可以通过设置 forbidden 设置按钮状态
<code src="./demo/index5.tsx" />
### 设置事件
> 可以通过设置 handleClick 绑定自定义事件
<code src="./demo/index6.tsx" />
### button 参数说明
<API />
index.mudule.less
.sussecc {
padding: 10px 20px;
background-color: green;
// border-radius: 10px;
}
.button {
display: inline-block;
button {
box-sizing: border-box;
color: #fff;
border: none;
border: 1px solid;
outline-style: none;
cursor: pointer;
}
.danger {
background-color: #fa6060;
border-color: #fa6060;
&:hover {
background-color: #f59b9b;
border-color: #f59b9b;
}
&.disabled {
background-color: #f6b6b6;
border-color: #f6b6b6;
cursor: not-allowed;
}
}
.warning {
background-color: #f3dd15;
border-color: #f3dd15;
&:hover {
background-color: #f8e74b;
border-color: #f8e74b;
}
&.disabled {
background-color: #f7ec90;
border-color: #f7ec90;
cursor: not-allowed;
}
}
.text {
color: #000;
background-color: #fff;
border-color: #fff;
&:hover {
color: #9be4fa;
background-color: #fff;
}
&.disabled {
color: #d6d7d9;
cursor: not-allowed;
}
}
.primary {
background-color: #86e0fb;
border-color: #86e0fb;
&:hover {
background-color: #9be4fa;
border-color: #9be4fa;
}
&.disabled {
background-color: #b9e9f7;
border-color: #b9e9f7;
cursor: not-allowed;
}
}
.info {
background-color: #b1b3b8;
border-color: #b1b3b8;
&:hover {
background-color: #b9babe;
border-color: #b9babe;
}
&.disabled {
background-color: #dee0e4;
border-color: #d6d7d9;
cursor: not-allowed;
}
}
.success {
background-color: #5dd105;
border-color: #5dd105;
&:hover {
background-color: #63df03;
border-color: #63df03;
}
&.disabled {
background-color: #ccf9a9;
border-color: #ccf9a9;
cursor: not-allowed;
}
}
}
index.tsx
import React, { FC, memo } from 'react';
import Css from './index.module.less';
import { ButtonProps } from './interface';
const Button: FC<ButtonProps> = memo(
({ type, children, width, height, radius, handleClick, disabled }) => {
let style = {
width: '',
height: '',
borderRadius: '',
};
if (
!type &&
type !== 'danger' &&
type !== 'warning' &&
type !== 'success' &&
type !== 'text' &&
type !== 'info'
) {
type = 'primary';
}
if (width) {
if (typeof width === 'string') {
if (width.includes('%') || width.includes('px')) {
style.width = width;
}
} else if (width * 1) {
style.width = width + 'px';
}
} else {
style.width = '100px';
}
if (height) {
if (typeof height === 'string') {
if (height.includes('%') || height.includes('px')) {
style.height = height;
}
} else if (height * 1) {
style.height = height + 'px';
}
} else {
style.height = '45px';
}
if (radius) {
if (typeof radius === 'string') {
if (radius.includes('%') || radius.includes('px')) {
style.borderRadius = radius;
}
} else if (radius * 1) {
style.borderRadius = radius + 'px';
}
}
let className = [Css[type], disabled ? Css['disabled'] : ''].join(' ');
return (
<div className={Css.button}>
<button
style={style}
className={className}
onClick={
!disabled
? () => {
handleClick ? handleClick() : null;
}
: () => {}
}
>
<span>{children ? children : '按钮'}</span>
</button>
</div>
);
},
);
export default Button;
interface.ts
export interface ButtonProps {
/**
* @description 按钮类型 可选值 primary / danger / warning / success /info / text
* @default primary
*/
type?: string;
/**
* @description 宽度
* @default 100px
*/
width?: any;
/**
* @description 高度
* @default 45px
*/
height?: any;
/**
* @description 圆角
*/
radius?: any;
/**
* @description 自定义点击事件
*/
handleClick?: Function;
/**
* @description 禁用
* @default false
*/
disabled?: boolean;
children?: any;
}
发布
- npm发布
发布时可能会出现很多文件可以慢慢排查,去npm查看包名是否冲突,更新包时查看版本是否更新,查看入口文件路径是否正确,是否时打包后的文件入口。
//打包文件
# npm run build
// 登录npm账号
# npm logo
// 修改镜像源
# npm config set registry https://registry.npmjs.org
// 关联包
# npm link
// 发布到npm
# npm publish
- 代码托管
我使用的是github代码托管平台,管理代码和部署在线文档。
// 运行这个指令自动上传到github 如果失败,请分步骤上传先打包在上传
# npm run deploy
github部署在线文档方法很简单,在标记处配置部署分支即可,等待配置完成点击上面网站即可跳转到你的在线文档地址。
这个时候我们就从0到1搭建一个属于自己的组件库,中间的难点就一些文档和代码的规范,一些npm发布流程和github部署流程。