React+TypeScript 组件库开发全攻略:集成Storybook可视化与Jest测试,一键发布至npm

news2024/9/17 7:47:39

平时我除了业务需求,偶尔会投入到UI组件的开发中,大多数时候只会负责自己业务场景相关或者一小部分公共组件,极少有从创建项目、集成可视化、测试到发布的整个过程的操作,这篇文章就是记录组件开发全流程,UI组件在此仅作为调试用,重点在于集成项目环境。

组件

我们使用 React + TypeScript 来开发UI组件库,为了简化 webpack 环境和 Typescript 环境配置,这里直接使用 create-react-app通过如下命令来创建一个新项目。

npx create-react-app 项目名称 --template typescript

创建项目后先将无用文件删除,在 scr/components/Button/index.tsx 下定义一个简单的 Button 组件。

import React, { FC, ReactNode } from "react";
import cn from "classnames";
import "./index.scss";

interface BaseButtonProps {
  className?: string;
  size?: "small" | "middle" | "large";
  disabled?: boolean;
  children?: ReactNode;
}

type ButtonProps = BaseButtonProps & React.ButtonHTMLAttributes<HTMLElement>;

const Index: FC<ButtonProps> = (props) => {
  const {
    size = "middle",
    children = "按钮",
    className,
    disabled,
    ...restProps
  } = props;
  
  return (
    <button
      className={cn("btn", `btn-${size}`, className, { disabled })}
      {...restProps}
    >
      {children}
    </button>
  );
};
export default Index;

使用 scss 对其进行样式编写,这里需要注意,create-react-app 中没有自动支持 scss 文件,如果需要使用需要手动安装 sass 资源来处理。

.btn {
    background-color: #fff;
    border: 1px solid #d9d9d9;
    color: rgba(0, 0, 0, 0.88);
    line-height: 1.5;
    &.disabled {
        cursor: not-allowed;
        color: rgba(0, 0, 0, 0.25);
        background-color: rgba(0, 0, 0, 0.04);
    }
}
.btn-large {
    font-size: 16px;
    height: 40px;
    padding: 7px 15px;
    border-radius: 8px;
}
.btn-middle {
    font-size: 14px;
    height: 32px;
    padding: 4px 15px;
    border-radius: 6px
}
.btn-small {
    font-size: 14px;
    height: 24px;
    padding: 0 7px;
    border-radius: 4px
}

在 App.tsx 文件中引入组件并测试,验证其功能是否可用。

import React from "react";
import Button from "./components/Button";
import "./app.scss";
function App() {
  return (
    <div className="container">
      <Button
        size="large"
        onClick={() => {
          console.log("我是一个大按钮");
        }}
      >
        大按钮
      </Button>
      <Button disabled>中等按钮</Button>
      <Button size="small">小按钮</Button>
    </div>
  );
}
export default App;

在浏览器中可以看到组件效果,以及点击【大按钮】会触发对应的事件

在这里插入图片描述

Jest测试

功能简单自测后,我们需要编写测试用例来对组件进行测试,一方面是为了提高代码质量减少bug,另一方面在后期的维护升级或者重构中,只需要执行自动化脚本,便可以确认是否兼容历史版本。

这里选用 jesttesting-library,我们通过 create-react-app 创建的 react 脚手架已经集成了单元测试的能力。

首先查看项目中的 setupTests.ts 有如下 Jest 断言增强的导入语句

import '@testing-library/jest-dom/extend-expect';

然后在 Button 文件夹下增加 index.test.tsx 文件编写测试用例,先判断是否成功渲染一个 Button 组件。

import React from 'react';
import { render, screen } from "@testing-library/react";
import Button from ".";

describe("Button组件", () => {
  it("默认Button", () => {
    render(<Button>查询</Button>); // 渲染一个名为查询的按钮
    const element = screen.getByText("查询");
    expect(element).toBeInTheDocument(); // 判断按钮是否在页面上
  });
});

脚手架 package.json 中已经添加了 test 指令的配置,我们在执行单元测试的时候只需要执行 npm run test,等待几秒便可以看到单元测试的执行结果

在这里插入图片描述

当我们将 const element = screen.getByText("查询");中的字符串 查询 改为 上传 时,会立马给出错误信息。

在这里插入图片描述

另外,还可以进一步的对渲染的组件进行测试,如判断 tagName、判断类名、是否是 disabled 状态、点击事件是否执行。

import React from 'react';
import { fireEvent, render, screen } from "@testing-library/react";
import Button from ".";

const defaultProps = {
  onClick: jest.fn()
}
describe("Button组件", () => {
  it("默认Button", () => {
    render(<Button {...defaultProps}>查询</Button>);
    const element = screen.getByText("查询");
    expect(element).toBeInTheDocument();
    expect(element.tagName).toEqual('BUTTON');
    expect(element.disabled).toBeFalsy();
    expect(element).toHaveClass('btn btn-middle');
    fireEvent.click(element)
    expect(defaultProps.onClick).toHaveBeenCalled()
  });
});

为组件增加新属性或者新特性时,记得为其在原来测试用例的基础上新增用例并执行,这样能在保证兼容历史功能的基础上验证新功能。

Storybook

通过项目入口文件 app.tsx 引入 Button 组件可以在本地服务运行的页面中预览效果,但随着组件的开发,我们需要不断 import 新的组件注释旧的引用来调试。

这样开发起来非常的繁琐,我们期望能有一个地方可以根据对组件进行分类,并且随时预览组件的效果,最好还能展示所有的组件配置,能根据选择配置展示组件效果。

针对以上的诉求,Storybook 就是一个非常好的解决方案。

使用 npm install storybook -d安装,并通过 npx sb init初始化,此时 storybook 会开启个一本地端口来展示默认生成的 stories 文件夹内案例。

在这里插入图片描述

我们在开发组件的 Button 文件夹下新增 index.stories.tsx 文件,来编写我们自己组件描述,一开始如果不知道如何定义,可以直接在案例组件的基础上进行修改。

import type { Meta, StoryObj } from "@storybook/react";
import { fn } from "@storybook/test";
import Button from ".";

const meta = {
  title: "Example/iceButton", // 用于展示组件的目录
  component: Button,
  tags: ["autodocs"], // 是否存在 Docs 页面
  args: { onClick: fn() },
} satisfies Meta<typeof Button>;
export default meta;

type Story = StoryObj<typeof meta>;
export const Primary: Story = {
  args: {
    children: "按钮", // 配置自己组件的属性
  },
};

这样我们自己编写的组件就加入到了页面中,右下方 Control 中是我们为组件添加的
interface BaseButtonProps,storybook 会自动将这部分填充进来,并且部分属性还可以直接在页面上修改并预览效果。

在这里插入图片描述

每新增一个组件,我们为其添加一个对应的 stories 文件,不仅便于预览功能,同时还能为提供详细的配置说明,直接省去编写文档的时间。

编译

在进行发布之前,我们还需要做一些配置,首先是修改入口文件,原本在 React 项目中,index.jsx 文件是找到页面中 id 为 root 的根元素并渲染组件的,现在要修改成将所有定义的组件导出,开发者者通过 import 就能导入使用。

export { default as Button} from "./components/Button";

然后我们使用 typecript 工具来对项目进行编译,react 项目初始化了 tsconfig.json,这个文件和开发相关,编译的配置需要我们自定义 tsconfig.build.json 文件。

{
  "compilerOptions": {
    // 输出文件夹
    "outDir": "dist",
    // 是 esmodule 的形式,还可以选 amd、cmd
    "module": "esnext",
    // 输出的ES版本,ES3-ESNext
    "target": "ES5",
    // typescript 使用库的时候,可以获取类型提示,在 .d.ts 文件,所以这个文件也要导出
    "declaration": true,
    // jsx 是 React.createElement 的语法糖,可选 preserve | react | react-native,编译出来的文件使用 React.createElement 代替 jsx 语法
    "jsx": "react",
    // 加载资源的方案,有classic 和 node 两种,classic 对应的是相对路径的方案,从当前路径一直往上找到 root。但是 node 是去 node_modules 中查找
    "moduleResolution": "node",
    // 支持默认导出的方式,不定义时只支持 import * as React from 'react'
    "allowSyntheticDefaultImports": true
  },
  // 编译src下的文件
  "include": ["src"],
  
// 排除 src 无需编译的文件
  "exclude": ["src/**/*.test.tsx", "src/**/*.stories.tsx", "src/setupTests.ts"]
}

可以看到常用的组件库,样式资源都是单独加载的,比如 antd 👉 import 'antd/dist/antd.css',element 👉 npm install element-theme-default,我们项目也按照这种方式来做一些调整。

去除各组件 scss 文件的引入,统一收口在 src 下的 index.scss 文件中,如 @import './components/Button/index.scss';,然后为以上修改在 package.json 中添加指令。

"scripts": {
     "build-ts": "tsc -p tsconfig.build.json",
     "build-css": "sass ./src/index.scss ./dist/index.css --no-source-map",
     "build": "npm run build-css && npm run build-ts"
}

执行 npm run build后生成如下文件

在这里插入图片描述

发布npm

在发布 npm 之前,我们需要确保用户通过 npm 下载的组件资源是可用的,在本地通过 npm link先验证一下功能。

我的UI组件项目名称为 ice-ts-app,对它执行 npm link,测试项目执行 npm link ice-ts-app,并引入测试代码。

import { Button } from 'ice-ts-app';
import 'ice-ts-app/dist/index.css';

在这里插入图片描述

运行测试项目,如果组件及其功能生效则代表验证成功。

验证完成后,还需要对 package.json 的配置做一些调整,包含项目的入口文件 dist/inde.js,TypeScript 类型定义文件 dist/index.d.ts,发布到 npm 的文件夹 dist ,调整 dependencies 和 devDependencies 的依赖,将 react 和 react-dom 迁移至 peerDependencies 中。

{
  "main": "dist/index.js",
  "module": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": ["dist"],
  "peerDependencies": {
     "react": ">=16.8.0",
     "react-dom": ">=16.8.0"
  },
}

还有一些通用的属性,包括 description、license、author、homepage 等等,开发者按需配置。

另外每次执行 npm run build 都需要手动删除 dist 文件夹,这里可以安装并使用 rimraf自动删除,同时再增加一条 script 指令,用于发布前执行。安装: npm install rimraf --save

"script": {  
   "clean": "rimraf ./dist",
    "build": "npm run clean && npm run build-css && npm run build-ts",
    "prepublishOnly": "npm run build"
}

发布之前先在 npm 仓库 上登录,然后执行 npm publish,可以看到发布日志中有我们提交的文件名称、文件大小,版本号等信息。

在这里插入图片描述

接着我们将用于测试的项目执行 npm unlink ice-ts-app来解除本地的绑定,并通过 npm install app-ts-app安装并验证刚刚发布到 npm 仓库的资源,如果组件能够正常使用就代表成功啦~

完整代码

以上便是 React + TypeScript 组件开发、测试、可视化及发布解析,完整代码我放在了 github 上,戳 ice-ts-app 可查看,欢迎大家点个 star~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1939607.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RabbitMQ学习实践二:MQ的实现

文章是本人在学习springboot实现消息队列功能时所经历的过程的记录&#xff0c;仅供参考&#xff0c;如有侵权请随时指出。 参考文章地址&#xff1a; RabbitMQ安装与入门_rabbitmq win11配置-CSDN博客 RabbitMQ入门到实战一篇文章就够了-CSDN博客 RabbitMQ系列&#xff08…

AI跟踪报道第48期-新加坡内哥谈技术-本周AI新闻:Open AI 和 Mistral的小型模型

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

华为路由器SSH登录实验

概念 SSH全称安全外壳&#xff08;Secure Shell&#xff09;协议&#xff0c;这个协议的目的就是为了取代缺乏机密性保障的远程管理协议&#xff0c;SSH基于TCP协议的加密通道&#xff0c;让客户端使用服务器的RSA公钥来验证SSHv2服务器的身份。 创建密钥对 在充当SSH服务器的…

UE4-获得角色控制权的两种方法

方法一&#xff1a; 方法二&#xff1a; 注意此方法不能有多个玩家出生点&#xff0c;如果有多个玩家出生点&#xff0c;会随机的选择一个玩家出生点进行生成。

C++的map和set介绍

系列文章目录 二叉树搜索树 map和set习题 文章目录 系列文章目录前言一、关联式容器键值对二、树形结构的关联式容器2.1 set2.1.1 set的介绍2.1.3 set的使用删除节点find的不同效率count举例lower_bound 和 upper_bound 2.2 multiset2.2.1 区别&#xff1a;find查找erase删除e…

Deepin系统,中盛科技温湿度模块读温度纯c程序(备份)

#include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <termios.h>int main() {int fd;struct termios options;// 打开串口设备fd open("/dev/ttyMP0", O_RDWR | O_NOCTTY|O_NDELAY); //O_NDELAY:打开设备不阻塞//O_NOCTT…

http请求网址或网页的全流程

客户端通过浏览器请求网址或网页资源的步骤如下&#xff1a; http请求网址或网页的全流程 1.首先&#xff0c;浏览器做的第一步就是解析 URL 得到里面的参数2.浏览器封装 HTTP 请求报文3.DNS 域名解析获取 IP 地址4. 建立 TCP 连接5.浏览器发送请求6.负责传输的 IP 协议7.使用 …

基于Llama Index构建RAG应用(Datawhale AI 夏令营)

前言 Hello&#xff0c;大家好&#xff0c;我是GISer Liu&#x1f601;&#xff0c;一名热爱AI技术的GIS开发者&#xff0c;本文参与活动是2024 DataWhale AI夏令营&#xff1b;&#x1f632; 在本文中作者将通过&#xff1a; Gradio、Streamlit和LlamaIndex介绍 LlamaIndex 构…

【初阶数据结构】5.栈和队列

文章目录 1.栈1.1 概念与结构1.2 栈的实现2.队列2.1 概念与结构2.2 队列的实现3.栈和队列算法题3.1 有效的括号3.2 用队列实现栈3.3 用栈实现队列3.4 设计循环队列 1.栈 1.1 概念与结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操…

从零开始实现大语言模型(八):Layer Normalization

1. 前言 Layer Normalization是深度学习实践中已经被证明非常有效的一种解决梯度消失或梯度爆炸问题,以提升神经网络训练效率及稳定性的方法。OpenAI的GPT系列大语言模型使用Layer Normalization对多头注意力模块,前馈神经网络模块以及最后的输出层的输入张量做变换,使shap…

android13 默认输入法配置分析rom默认配置修改分析

总纲 android13 rom 开发总纲说明 目录 1.前言 2.解决方法 3.方法分析 3.1方法1 3.2方法2 4.彩蛋 1.前言 Android13上需要预装中文输入法, 但是直接预装输入法的话,会出现默认使能的问题,点击TextEdit输入框, 弹出的是默认英文输入法LatinIME, 而不是谷歌拼音输入…

解决GoLand添加GOROOT提示The selected directory is not a valid home for Go Sdk的问题

现象 解决 在Go安装路径下找到zversion.go文件&#xff0c;我的在D:\Program Files\Go1.21.1\src\runtime\internal\sys下面 打开文件&#xff0c;添加如下内容&#xff1a; const TheVersion go1.21.1保存后再重新添加GOROOT即可

2024 杭电多校第一场

目录 目录 树 博弈 传送 树 给一棵根为 1 的有根树&#xff0c;点 i 具有一个权值 Ai 。 定义一个点对的值 f(u,v)max(Au,Av)|Au−Av| 。 你需要对于每个节点 i &#xff0c;计算 ansi∑u∈subtree(i),v∈subtree(i)f(u,v) &#xff0c;其中 subtree(i) 表示 i 的子树。 请…

如何让LabVIEW程序框图的图标简化,从而节省空间?

再点击选项 取消掉箭头所示的√即可。 这样就可以将生成的图标从下面所示&#xff1a; 变成简化的图标&#xff0c;如下所示&#xff1a;

UML的六大关系---泛化、实现、关联、聚合、组合、依赖

文章目录 前言1. 泛化关系(Generalization)2. 实现关系(Realization)3. ‌关联关系(Association)4. 聚合关系(Aggregation)5. 组合关系(Composition)6. 依赖关系(Dependency)总结 前言 讲到设计模式&#xff0c;就会有 U M L UML UML类图这个东西。 一开始就很难理解各种线啥意…

【Spring Boot】网页五子棋项目中遇到的困难及解决方法

目录 一、HikariPool-1 - Starting异常二、Invalid bound statement (not found)异常三、The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary异常四、The server time zone value时区报错异常五、补充知识点…

CSS技巧专栏:一日一例 6 - 纯CSS实现粉红色跳出来的立体按钮特效

纯CSS实现粉红色跳出来的立体按钮特效 今天要介绍的案例,是个相对简单的按钮效果,我们先看图: 案例分析 我说它简单,因为它实际上并没有使用什么特别的动画效果,只是几个简单的动画组合: 利用伪类before和after,制作按钮后面两个透明的粉色填充层,左右移动。给文字层…

代码随想录算法训练营第23天|39. 组合总和、40.组合总和II、131.分割回文串

打卡Day23 1.39. 组合总和2.40.组合总和II3.131.分割回文串 1.39. 组合总和 题目链接&#xff1a;39. 组合总和 文档讲解&#xff1a; 代码随想录 这道题和昨天做的组合之和由两个区别&#xff1a;被选的元素没有数量限制&#xff0c;同时被选的元素可以无限重复&#xff0c;…

区块链技术实现数字电网内数据可信共享 |《超话区块链》直播预告

随着全球电力市场朝着构建“SmartGrid”和“IntelliGrid”的目标发展&#xff0c;国内电力公司也提出了构建“数字电网”的愿景。清大科越推出新型电力系统区块链服务平台&#xff0c;通过便捷的建链、上链、用链及治链能力&#xff0c;有效解决数字电网各主体间数据共享的信任…

QT--网络篇

如果QT头文件找不到QTcpSocket、QTcpSocket、QTcpServer、QtNetwork ,那么可能是pro文件中缺少QT network这行代码 客户端QTcpSocket void QTcpSocket::connectToHost( QString servip, quint16 port ); connectToHost 函数会尝试与指定的服务器建立 TCP 连接。如果连接成…