TypeScript、React从零到一打造组件库(视频教程)
·
概述
- 从0到1打造基于 TypeScript + React 的组件库是一个系统性工程,涉及架构设计、开发规范、构建部署等多个环节。以下是详细的实施步骤和最佳实践
- 视频教程:
https://pan.quark.cn/s/c4cddd68c495
一、前期准备:项目初始化与架构设计
1. 技术栈选型
- 核心框架:React 18+(函数组件 + Hooks)
- 类型系统:TypeScript 5.0+(强类型约束)
- 样式方案:
- 基础:CSS Modules(避免样式冲突)
- 预处理:Sass/SCSS(嵌套、变量、混入)
- 可选:Styled Components(CSS-in-JS,适合动态样式)
- 构建工具:Vite(开发热更新快,支持库模式打包)
- 文档工具:Storybook(组件开发、文档、交互测试)
- 测试工具:Jest + React Testing Library(单元测试)
- 代码规范:ESLint + Prettier + Husky(提交校验)
2. 项目初始化
# 创建项目(使用 Vite 模板)
npm create vite@latest my-component-library -- --template react-ts
# 进入项目
cd my-component-library
# 安装依赖
npm install
# 安装核心工具
npm install sass storybook -D
npx storybook init # 初始化 Storybook(选择 React + Vite)
3. 项目目录设计
my-component-library/
├── src/
│ ├── components/ # 组件源码
│ │ ├── Button/ # 单个组件(示例)
│ │ │ ├── index.ts # 组件导出
│ │ │ ├── Button.tsx # 组件实现
│ │ │ ├── Button.module.scss # 样式
│ │ │ ├── Button.types.ts # 类型定义
│ │ │ └── Button.stories.tsx # Storybook 文档
│ ├── utils/ # 工具函数(如 className 合并)
│ ├── styles/ # 全局样式(主题、变量)
│ │ ├── variables.scss # 主题变量(颜色、字体)
│ │ └── reset.scss # 样式重置
│ └── index.ts # 库入口(导出所有组件)
├── .storybook/ # Storybook 配置
├── package.json # 依赖与打包配置
└── tsconfig.json # TypeScript 配置
二、核心开发:组件设计与实现
1. 类型定义(TypeScript)
为组件编写清晰的类型定义,以 Button 为例(Button.types.ts):
import { ReactNode, ButtonHTMLAttributes } from 'react';
export type ButtonVariant = 'primary' | 'secondary' | 'danger';
export type ButtonSize = 'sm' | 'md' | 'lg';
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
/** 按钮文本或子元素 */
children?: ReactNode;
/** 按钮变体 */
variant?: ButtonVariant;
/** 按钮尺寸 */
size?: ButtonSize;
/** 是否禁用 */
disabled?: boolean;
}
2. 组件实现(React + 样式)
Button.tsx 实现:
import { ButtonProps } from './Button.types';
import styles from './Button.module.scss';
import { cn } from '../../utils/classNames'; // 自定义 className 合并工具
const Button = ({
children,
variant = 'primary',
size = 'md',
disabled = false,
className,
...props
}: ButtonProps) => {
// 合并基础样式、变体样式、尺寸样式和外部 className
const btnClass = cn(
styles.button,
styles[`variant-${variant}`],
styles[`size-${size}`],
{ [styles.disabled]: disabled },
className
);
return (
<button className={btnClass} disabled={disabled} {...props}>
{children}
</button>
);
};
export default Button;
Button.module.scss 样式:
@import '../../styles/variables'; // 引入主题变量
.button {
border: none;
border-radius: $border-radius;
cursor: pointer;
font-family: $font-family;
&.variant-primary {
background: $primary-color;
color: white;
}
&.variant-secondary {
background: $secondary-color;
color: $text-color;
}
&.size-sm {
padding: 4px 8px;
font-size: 12px;
}
&.size-md {
padding: 6px 12px;
font-size: 14px;
}
&.disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
3. 工具函数封装
例如 utils/classNames.ts(合并 className):
export function cn(...classes: (string | boolean | undefined)[]) {
return classes.filter(Boolean).join(' ');
}
三、文档与测试:Storybook 与单元测试
1. Storybook 文档(组件示例)
Button.stories.tsx:
import { Meta, StoryObj } from '@storybook/react';
import Button from './Button';
import { ButtonVariant, ButtonSize } from './Button.types';
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
argTypes: {
variant: {
control: { type: 'select' },
options: ['primary', 'secondary', 'danger'] as ButtonVariant[],
},
size: {
control: { type: 'select' },
options: ['sm', 'md', 'lg'] as ButtonSize[],
},
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {
args: {
variant: 'primary',
children: 'Primary Button',
},
};
export const Disabled: Story = {
args: {
variant: 'secondary',
disabled: true,
children: 'Disabled Button',
},
};
启动 Storybook 预览:
npm run storybook
2. 单元测试(Jest)
安装测试依赖:
npm install jest @testing-library/react @testing-library/jest-dom @types/jest ts-jest -D
配置 jest.config.js 后,编写测试用例(Button.test.tsx):
import { render, screen } from '@testing-library/react';
import Button from './Button';
test('renders button text', () => {
render(<Button>Test</Button>);
expect(screen.getByText('Test')).toBeInTheDocument();
});
test('applies variant class', () => {
const { container } = render(<Button variant="danger">Danger</Button>);
expect(container.firstChild).toHaveClass('variant-danger');
});
四、构建与发布:打包为可复用库
1. 配置 Vite 打包
修改 vite.config.ts 为库模式:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
build: {
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
name: 'MyComponentLibrary',
fileName: (format) => `my-components.${format}.js`,
},
rollupOptions: {
// 排除 React 作为依赖(避免用户重复引入)
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
},
},
},
});
在 src/index.ts 导出组件:
export { default as Button } from './components/Button';
// 后续添加更多组件...
2. 配置 package.json
{
"name": "my-component-library",
"version": "0.1.0",
"main": "./dist/my-components.umd.js",
"module": "./dist/my-components.es.js",
"types": "./dist/index.d.ts", // TypeScript 类型入口
"files": ["dist"], // 发布时包含的文件
"scripts": {
"build": "vite build && tsc --emitDeclarationOnly" // 打包 + 生成类型声明
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
}
3. 本地测试与发布
- 本地测试:使用
npm link将库链接到其他项目验证 - 发布到 npm:
npm login npm publish --access public
五、进阶优化
- 主题系统:通过
Context实现主题切换(如亮色/暗色模式) - 按需加载:结合
babel-plugin-import支持组件按需引入 - 国际化:使用
react-i18next支持多语言 - CI/CD:通过 GitHub Actions 自动化测试、构建、发布
- 性能优化:使用
React.memo、useMemo减少不必要的重渲染
通过以上步骤,你可以构建一个类型安全、文档完善、可复用的 React 组件库。核心是保持组件的独立性、可扩展性,并通过 TypeScript 和测试保障质量。
更多推荐
所有评论(0)