从零到一:现代化 React 组件库搭建指南

从零到一:现代化 React 组件库搭建指南

基于 TypeScript + PropTypes 双重校验的 React 组件库,采用 Monorepo 架构,配合 ESLint + Prettier 代码规范,使用 Rollup 进行打包构建。

🎯 项目架构

技术栈选型

  • 开发语言: TypeScript + JavaScript
  • UI 框架: React 18+
  • 校验方案: TypeScript + PropTypes 双重校验
  • 架构模式: Monorepo (多包管理)
  • 包管理器: pnpm
  • 构建工具: Rollup
  • 代码规范: ESLint + Prettier
  • 开发工具: Vite

项目结构

bash 复制代码
组件库/
├── packages/
│   ├── components/          # 组件包
│   │   ├── src/
│   │   │   ├── Toast/      # Toast组件
│   │   │   ├── Message/    # Message组件
│   │   │   └── index.ts    # 统一导出
│   │   ├── dist/           # 构建产物
│   │   └── package.json
│   ├── example/            # 示例应用
│   └── utils/              # 工具包
├── package.json            # 根配置
├── pnpm-workspace.yaml     # workspace配置
├── tsconfig.json           # TS配置
├── rollup.config.js        # 构建配置
├── .eslintrc.js           # ESLint配置
└── .prettierrc            # Prettier配置

🚀 项目初始化

1. 创建项目并初始化

bash 复制代码
mkdir react-component-library && cd react-component-library
npm init -y

2. 安装核心依赖

bash 复制代码
# 开发依赖
pnpm add -D typescript @types/react @types/react-dom
pnpm add -D rollup @rollup/plugin-typescript @rollup/plugin-node-resolve
pnpm add -D @rollup/plugin-commonjs @rollup/plugin-babel rollup-plugin-dts
pnpm add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
pnpm add -D eslint-plugin-react eslint-plugin-react-hooks prettier
pnpm add -D @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript

# 生产依赖
pnpm add prop-types
pnpm add -P react react-dom

⚙️ 核心配置文件

1. Monorepo 配置 (pnpm-workspace.yaml)

yaml 复制代码
packages:
  - 'packages/*'

2. TypeScript 配置 (tsconfig.json)

json 复制代码
{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["DOM", "DOM.Iterable", "ES6"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "strict": true,
    "module": "ESNext",
    "moduleResolution": "node",
    "jsx": "react-jsx",
    "declaration": true,
    "outDir": "dist",
    "baseUrl": ".",
    "paths": {
      "@my-ui/*": ["packages/*/src"]
    }
  },
  "include": ["packages/*/src/**/*"],
  "exclude": ["node_modules", "dist"]
}

3. Rollup 打包配置 (rollup.config.js)

javascript 复制代码
import typescript from '@rollup/plugin-typescript';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import dts from 'rollup-plugin-dts';

export default [
  // JS构建
  {
    input: 'packages/components/src/index.ts',
    external: ['react', 'react-dom', 'prop-types'],
    output: [
      {
        file: 'packages/components/dist/index.esm.js',
        format: 'esm',
        sourcemap: true,
      },
      {
        file: 'packages/components/dist/index.cjs.js',
        format: 'cjs',
        sourcemap: true,
        exports: 'named',
      },
    ],
    plugins: [
      nodeResolve({ extensions: ['.js', '.jsx', '.ts', '.tsx'] }),
      commonjs(),
      typescript({ declaration: false }),
      babel({
        babelHelpers: 'bundled',
        exclude: 'node_modules/**',
        presets: [
          '@babel/preset-env',
          '@babel/preset-react',
          '@babel/preset-typescript',
        ],
      }),
    ],
  },
  // 类型声明构建
  {
    input: 'packages/components/src/index.ts',
    external: ['react', 'react-dom', 'prop-types'],
    output: { file: 'packages/components/dist/index.d.ts', format: 'esm' },
    plugins: [dts()],
  },
];

🎨 组件开发实践

1. 组件类型定义

typescript 复制代码
// packages/components/src/Toast/types.ts
export type ToastType = 'success' | 'error' | 'warning' | 'info';

export interface ToastOptions {
  type?: ToastType;
  duration?: number;
  closable?: boolean;
  onClose?: () => void;
}

export interface ToastProps extends ToastOptions {
  message: string;
  id: string;
  visible: boolean;
  onDestroy: (id: string) => void;
}

2. 组件实现(TypeScript + PropTypes 双重校验)

tsx 复制代码
// packages/components/src/Toast/Toast.tsx
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { ToastProps, ToastType } from './types';

const ICONS: Record<ToastType, string> = {
  success: '✓',
  error: '✕',
  warning: '⚠',
  info: 'ⓘ',
};

const Toast: React.FC<ToastProps> = ({
  id,
  message,
  type = 'info',
  duration = 3000,
  visible,
  onClose,
  onDestroy,
}) => {
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    if (visible) {
      setTimeout(() => setIsVisible(true), 50);
      if (duration > 0) {
        setTimeout(() => {
          setIsVisible(false);
          onClose?.();
          setTimeout(() => onDestroy(id), 300);
        }, duration);
      }
    }
  }, [visible, duration]);

  return (
    <div className={`toast toast-${type} ${isVisible ? 'toast-visible' : ''}`}>
      <span className={`toast-icon toast-icon-${type}`}>{ICONS[type]}</span>
      <div className="toast-content">{message}</div>
      <button onClick={() => setIsVisible(false)}>✕</button>
    </div>
  );
};

// PropTypes 运行时校验
Toast.propTypes = {
  id: PropTypes.string.isRequired,
  message: PropTypes.string.isRequired,
  type: PropTypes.oneOf(['success', 'error', 'warning', 'info']),
  duration: PropTypes.number,
  visible: PropTypes.bool.isRequired,
  onDestroy: PropTypes.func.isRequired,
};

export default Toast;

3. 全局管理器

tsx 复制代码
// packages/components/src/Toast/ToastManager.tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import Toast from './Toast';
import { ToastOptions } from './types';

class ToastManager {
  private container: HTMLElement;
  private root: any;
  private toasts = new Map();

  constructor() {
    this.container = document.createElement('div');
    this.container.className = 'toast-container';
    document.body.appendChild(this.container);
    this.root = createRoot(this.container);
  }

  show(message: string, options: ToastOptions = {}) {
    const id = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    const toast = { id, message, visible: true, ...options };

    this.toasts.set(id, toast);
    this.render();
    return id;
  }

  success = (message: string, options?: Omit<ToastOptions, 'type'>) =>
    this.show(message, { ...options, type: 'success' });

  error = (message: string, options?: Omit<ToastOptions, 'type'>) =>
    this.show(message, { ...options, type: 'error' });

  private render() {
    const elements = Array.from(this.toasts.values()).map((toast) => (
      <Toast key={toast.id} {...toast} onDestroy={this.remove} />
    ));
    this.root.render(<>{elements}</>);
  }

  private remove = (id: string) => {
    this.toasts.delete(id);
    this.render();
  };
}

export const toast = new ToastManager();

4. 统一导出

typescript 复制代码
// packages/components/src/index.ts
export { default as Toast, toast } from './Toast';
export type { ToastType, ToastOptions, ToastProps } from './Toast';
export const version = '1.0.0';

📦 构建与发布

1. 构建脚本配置

json 复制代码
{
  "scripts": {
    "build": "rollup -c",
    "build:watch": "rollup -c -w",
    "lint": "eslint packages/ --ext .ts,.tsx",
    "format": "prettier --write packages/**/*.{ts,tsx}",
    "dev": "pnpm --filter example dev"
  }
}

2. 组件包配置

json 复制代码
// packages/components/package.json
{
  "name": "your-react-design",
  "main": "dist/index.cjs.js",
  "module": "dist/index.esm.js",
  "types": "dist/index.d.ts",
  "files": ["dist"],
  "peerDependencies": {
    "react": ">=16.8.0",
    "react-dom": ">=16.8.0"
  }
}

3. 发布流程

bash 复制代码
# 1. 构建项目
pnpm build

# 2. 更新版本
cd packages/components && npm version patch

# 3. 发布到npm
npm publish

🛠️ 开发体验优化

1. 示例应用配置

typescript 复制代码
// packages/example/vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@my-ui/components': resolve(__dirname, '../components/src'),
    },
  },
});

2. 代码规范

javascript 复制代码
// .eslintrc.js
module.exports = {
  extends: ['eslint:recommended', 'plugin:react/recommended'],
  parser: '@typescript-eslint/parser',
  rules: {
    'react/react-in-jsx-scope': 'off',
    'react/prop-types': 'warn',
  },
};

🎉 总结

这个组件库的核心特点:

  1. 双重校验: TypeScript 编译时 + PropTypes 运行时
  2. 现代化工具链: pnpm + Rollup + ESLint + Prettier
  3. Monorepo 架构: 多包管理,便于维护
  4. 完整的开发体验: 热更新、类型提示、代码规范
  5. 可扩展性: 易于添加新组件和功能

通过这套架构,可以快速搭建一个现代化、类型安全、开发体验良好的 React 组件库。

相关推荐
无羡仙2 小时前
虚拟列表:怎么显示大量数据不卡
前端·react.js
萌萌哒草头将军3 小时前
字节也在用的 @tanstack/react-query 到底有多好用!🔥🔥🔥
前端·javascript·react.js
Python私教4 小时前
yggjs_react使用教程 v0.1.1
前端·react.js·前端框架
小鸡脚来咯4 小时前
react速成
前端·javascript·react.js
剽悍一小兔4 小时前
React15.x版本 子组件调用父组件的方法,从props中拿的,这个方法里面有个setState,结果调用报错
前端·javascript·react.js
柯南二号4 小时前
【前端】React回调函数解析
前端·react.js·前端框架
北海几经夏7 小时前
React响应式链路
前端·react.js
晴空雨8 小时前
React Media 深度解析:从使用到 window.matchMedia API 详解
前端·react.js
江城开朗的豌豆10 小时前
React key的隐藏技能:key改变时究竟发生了什么?
前端·javascript·react.js