使用ts+react实现一个“组件库”

写在前面

组件库源码地址:https://github.com/Kylin93CN/kylin-design 求star✨~

文档地址:https://kylin93cn.github.io/kylin-design/

npm地址:https://www.npmjs.com/package/kylin-design

设计与色彩学

设计

关于设计,可以参考antd中的文档:https://ant.design/docs/spec/overview-cn

色彩

既然要开发组件库,首先我们得知道开发的组件长什么样,是什么颜色。一个优秀的组件库肯定离不开她的色彩搭配与设计。

antd官方文档中单独有一个头部导航 来介绍设计,可想而知这块也是组件开发的一个头等大事。

我参考了国内主流的UI组件库:

p.s. 可以打开网站,对应的就是组件库中关于色彩描述的文档

总结下来就是我们要有以下几个色板:

  1. 基础色板
  2. 中性色板
  1. 功能色板

关于基础色板,就是根据一个主题核心的颜色,通过算法得到一组渐进的颜色组。

我们可以通过antd的色板生成工具 帮我们计算得出

可以通过这个网站计算得到:https://mycolor.space/

关于中性色板,就是#fff#000的一个过程,中间的渐变颜色可以自己定义。

关于功能色板,就是一些功能性组件所使用的的颜色。比如,警告⚠️类型的,一般都是黄色;提示类型的,一般都是蓝色;危险或者错误❌,一般都是红色。

当然,除了颜色之外,还有字体、阴影、布局中,本文不做过多探讨。

P.S.关于色彩的详情,可参考:https://www.yuque.com/kylin93/uayuig/qypb6u

开发

起名

首先,给我们的组件起一个响亮的名字!!

那就叫做:kylin-design

创建项目

通过cra脚手架创建项目:

npx create-react-app kylin-design --template typescript
# or
yarn create react-app kylin-design --template typescript

配置色彩颜色

step1

本项目使用的scss,涉及一些scss的特性,比如:文件模块化、mixin、变量等。

首先我们在src下创建一个styles文件夹,用于存放样式文件。

创建第一个scss文件:_variables.scss 文件名前添加了一个下划线“_”表示这个只是部分文件,打包时,不会生成对应的css文件。

在这个文件中,我们会定义各种颜色,包含中性色、基本色、功能色以及各种预设样式,比如字体大小、行高、按钮的border-radius等。详情可见:https://raw.githubusercontent.com/Kylin93CN/kylin-design/main/src/styles/_variables.scss

下面代码为文件的部分:

/*********   中性色--start   *********/
$gray-1: #fff !default;
$gray-2: #f5f5f5 !default;
$gray-3: #f0f0f0 !default;
$gray-4: #d9d9d9 !default;
$gray-5: #8c8c8c !default;
$gray-6: #595959 !default;
$gray-7: #434343 !default;
$gray-8: #262626 !default;
$gray-9: #1f1f1f !default;
$gray-10: #000 !default;
/*********   中性色--end   ***********/

/*********   基础色--start   *********/
// 使用antd-Dust Red / 薄暮
$red-1: #fff1f0 !default;
$red-2: #ffccc7 !default;
$red-3: #ffa39e !default;
$red-4: #ff7875 !default;
$red-5: #ff4d4f !default;
$red-6: #f5222d !default;
$red-7: #cf1322 !default;
$red-8: #a8071a !default;
$red-9: #820014 !default;
$red-10: #5c0011 !default;
$transparent: transparent !default;

关于组件的基础色,那肯定跟我们的组件名称有关系,麒麟属火,我就选择了antd提供的一套:

然后组件的设计风格也是根据这个主题仿照的:

 

p.s. 可在antd官网底部选择主题修改查看

step2

引入normalize.css文件,帮我们抹平里浏览器之间的差异。

github地址:https://github.com/necolas/normalize.css/

它的作用:

  • reset.css不同,normalize会保留有用的默认值。
  • 标准化各种元素的样式。
  • 通过细微的修改提高可用性。

对主流浏览器也是支持的。

这里我会基于normalize.css,修改为_normalize.scss,并将一些变量加载进去。

step3

创建_mixin.scss, 用于复用

step4

创建index.scss(注意这个没有下划线),将上述3个文件导入。

开发组件--Button

src下创建components的文件夹,然后再创建一个Button的文件夹,接下来创建下面几个文件:

  • _button.scss——文件用于button组件的样式;
  • button.test.tsx——用于组件测试
  • button.tsx——是组件的主体
  • index.tsx——是组件的导出文件,对于Menu这种组件,我们会统一把MenuItem等挂在Menu上,方便导出。

对于组件的开发,我们首先要提前定义好或者设计好组件到底有哪些属性。对于经常使用antd-button的小伙伴们来说,常用的属性就是type、size、disabled等。

那么我们以实现最小MVP来说,就先定义几个常用的:

export type ButtonSize = "lg" | "sm";
export type ButtonType = "primary" | "default" | "danger" | "link" | "ghost";
interface BaseButtonProps {
  /** 设置 Button 的classname */
  className?: string;
  /** 设置 Button 的尺寸 */
  size?: ButtonSize;
  /** 设置 Button 是否禁用 */
  disabled?: boolean;
  /**设 置 Button 类型 */
  btnType?: ButtonType;
  /** 设置 Button 文案 */
  children?: React.ReactNode;
  /** 设置 Button 类型为link时的跳转地址 */
  href?: string;
}

按钮的实现我们用到了button跟a标签(用于link类型),除了我们制定的属性,还需要标签的原生属性,比如button的onClick,因此我们需要把a标签的原生属性、button的原生属性还有我们定义的按钮属性联合起来。使用的ts类型定义代码如下:

type NativeButtonProps = BaseButtonProps &
  React.ButtonHTMLAttributes<HTMLElement>;
type NativeAnchorProps = BaseButtonProps &
  React.AnchorHTMLAttributes<HTMLElement>;
export type ButtonProps = Partial<NativeButtonProps & NativeAnchorProps>;

p.s. 涉及到ts的 & 用法与Partial的用法

按钮的不同尺寸、类型,其实就是css不同,我们借助classnames库,帮我们处理这个逻辑:

// 按钮默认有btn的类  
const classes = classnames("btn", className, {
    [`btn-${btnType}`]: btnType, // 根据类型追加类
    [`btn-${size}`]: size, // 同上
    disabled: btnType === "link" && disabled, // link使用css处理,btn使用原生属性处理
  });

渲染组件就通过type判断下,如果是link就使用a标签,其他就使用button

 if (btnType === "link" && href) {
    return (
      <a className={classes} href={href} {...restProps}>
        {children}
      </a>
    );
  }
  return (
    <button className={classes} disabled={disabled} {...restProps}>
      {children}
    </button>
  );

接下来我们开始去编写样式文件,默认的btn,不同类型的btn-primary、btn-danger、btn-lg

这些根据我们之前定义的变量去使用就好了。

.btn-danger {
  // 使用mixin,在styles/_mixin.scss中定义的
  @include btn-style($gray-1, $red-5,$red-5);
}

.btn-default {
  @include btn-style($gray-1, $gray-4,$gray-10,);
}

.btn-ghost {
  @include btn-style($transparent, $gray-1,$gray-1,);
} 

测试

react推荐的testing-library,cra(貌似是3.3+)也默认集成的库。yyds!

关于测试,会用到2个库:

提供了一些match,比如toBeEmptyDOMElement、toBeInTheDocument、toHaveClass

帮助我们将jsx转化为真实dom,并提供一些辅助的事件模拟方法等。

比如我们可以写一个简单的测试用例:

import React from "react";
import { render, fireEvent } from "@testing-library/react";
import Button from "./button";
// 组件的默认属性
const defaultBtnProps: ButtonProps = {
  btnType: "default",
  className: "test-cls",
  onClick: jest.fn(),
};
// 测试默认button
it("test default button", () => {
  // 通过render将button渲染为真实dom
    const btnDom = render(
      <Button {...defaultProps}>
        Default Button
      </Button>
    );
		// 通过getByText方法获取btn的实例
    const ele = btnDom.getByText("Default Button");
    // button是否存在--@testing-library/jest-dom提供的match
    expect(ele).toBeInTheDocument();
    // dom属性是否是BUTTON
    expect(ele.tagName).toEqual("BUTTON");
		// --@testing-library/jest-dom提供的match
    expect(ele).toHaveClass("btn btn-default test-cls");
    // 模拟点击
    fireEvent.click(ele);
		// --@testing-library/jest-dom提供的match
    expect(defaultProps.onClick).toHaveBeenCalled();
  });

文档生成

组件库肯定是写给人用的,但是使用者肯定不能通过看源码来查看btn的type属性有哪些、设置大小的枚举值有哪些吧,这里我们使用的storybook 来生成文档库。

直接运行npx sb init

During its install process, Storybook will look into your project's dependencies and provide you with the best configuration available.

我们尽管安装,sb会根据我们项目的依赖帮我们安装最合适的配置,比如我们项目是ts的,它就会生成ts的文件。

新版本的sb(6.x)新增了control的功能,简单的配置,就可以帮我们在页面上动态修改组件的属性,实时渲染。可到 此link 体验尝鲜。

sb默认集成的addon(插件)已满足我目前的需求,如有其它需求,也可到插件市场逛逛,地址是:

https://storybook.js.org/addons/

.storybook/main.js 中,追加了一行代码,用于把css文件导入。

  "stories": [
    "../src/**/*.stories.mdx",
    "../src/**/*.stories.@(js|jsx|ts|tsx)",
  + "../src/styles/index.scss"
  ],

sb默认会在src下创建一个stories文件夹,里面会有一些示例,我们可以运行npm run storybook启动本地服务,查看生成的文档库。

Introduction.stories.mdx mdx就是markdown + jsx,这里我当做了介绍页,把文件中没用的都删掉了。

sb会根据文件名规则约束,以文件名格式为:XXX.stories.XXX的文件当做一个story,删除无用的文件,然后我们保留Button.stories.tsx这个文件,将button组件改为我们写的组件,然后根据示例补充我们的属性即可。

    btnType: {
      name: "btnType",
      description: "button type",
      type: "radio",
      options: ["primary", "default", "danger", "link", "ghost"],
      table: {
        type: { summary: "string" },
        defaultValue: { summary: "default" },
      },
    },

生成的结果如下,我们可以在单选框中选择,实时修改组件的类型等属性

点击docs,可以查看属性的文档详情,比如默认值、类型、详情介绍等

组件发布

项目配置

1. 配置scripts

  • "build": "npm run clear && npm run build-css && npm run build-ts",
  • "build-ts": "tsc -p tsconfig.build.json",
  • "build-css": "node-sass ./src/styles/index.scss ./dist/index.css",
  • "clear": "rimraf ./dist",
  • "prepublishOnly": "npm run build",
  • "test-ci": "cross-env CI=true react-scripts test",

clear命令,我们安装rimraf库,帮我们抹平操作系统的命令差异,该命令执行删除./dist文件夹下面的内容;

build-css命令,通过node-sass打包css文件;

build-ts命令,这里我们新增了一个tsconfig.build.json文件用于build时使用,用于打包组件库js文件;

build命令,首先清空./dist下的内容,然后打包css、js

test-ci命令执行测试用例

prepublishOnly命令,发布前执行打包操作

2.添加husky

用于我们提交commit时,强执行测试用例,保证组件库的稳定性。

目前husky是7.x版本,会使用到set-script,因此需要保证你的Npm也是7.x版本。

npm -v 可查看当前npm版本,升级的话执行:

npm i npm -g

详细安装可参考官方文档:https://github.com/typicode/husky#usage

然后我们在pre-commit 中添加需要执行的脚本为:

npm run test-ci && npm run lint

3. 完善readme.md

必不可少

4. 修改依赖

目前dependencies我只保留了一个classnames,其他的依赖库都塞到了devDependencies中,

一些是因为都是开发依赖,还有一些比如react、react-dom,这些的话,如果组件使用的话,会使用安装组件库项目中的react,而不是组件库的react。

特此,加了peerDependencies ,如果使用的项目没有这个库或者版本不对,会有warning提示。

"peerDependencies": {
    "react": ">=16.8.0",
    "react-dom": ">=16.8.0"
  },

5.修改package.json

  • 修改版本号
  • 添加一些字段,比如author、desc、keywords、license、homepage等
  • 修改private为false
  • 添加入口信息字段
  • main
  • modules
  • types
  • 添加files字段
  • 内容为['dist']

发布NPM

首先需要登录npm,执行命令 npm whoami ,如果没有,则需要登录,没有账号的自行去npm官网注册,有账号的执行npm login进行登录。

p.s. 登录时不能把源改为淘宝的。

万事俱备,执行:

npm publish

大功告成!!!

部署

项目部署CI/CD & 文档库部署

本项目使用的github actions

创建文件:.github/workflows/ci.yml

name: KYLIN-DESIGN CI   # 名称
on:
  push:
    branches:
      - main   # main分支push时触发
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest  # 运行环境为乌班图
    steps:  # actions/checkout@v2 是一个官方的actions,用于下载项目代码
    - name: Checkout
      uses: actions/checkout@v2 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
      with:
        persist-credentials: false
    - name: Install and Build # step的名称
      run: |    # 需要执行的脚本命令
        npm install  # 安装依赖
        npm run test-ci # 运行测试
        npm run-script build # 打包组件库的静态资源
        npm run build-storybook # 打包文档库资源
    - name: Deploy # 这是一个别人写的actions,用于静态资源的部署
      uses: JamesIves/github-pages-deploy-action@releases/v3
      with:
        ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} # 环境变量
        BRANCH: gh-pages # 部署的分支
        FOLDER: storybook-static # sb的dist目录

如何生成ACCESS_TOKEN

打开https://github.com/settings/tokens,点击生成新的token

使用ACCESS_TOKEN

在项目的Settings中,点Scerets


 

配置好之后,我们下次提交的时候就会发出ci/cd,然后执行命令部署sb。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值