概述

  • 从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
    

五、进阶优化

  1. 主题系统:通过 Context 实现主题切换(如亮色/暗色模式)
  2. 按需加载:结合 babel-plugin-import 支持组件按需引入
  3. 国际化:使用 react-i18next 支持多语言
  4. CI/CD:通过 GitHub Actions 自动化测试、构建、发布
  5. 性能优化:使用 React.memouseMemo 减少不必要的重渲染

通过以上步骤,你可以构建一个类型安全、文档完善、可复用的 React 组件库。核心是保持组件的独立性、可扩展性,并通过 TypeScript 和测试保障质量。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐