698 lines
23 KiB
Markdown
698 lines
23 KiB
Markdown
# Pika 组件库结构优化分析
|
||
|
||
> 基于当前代码库(`src/components` 约 77 个组件目录、80 个组件 TSX、74 个测试文件)的静态分析。
|
||
> 生成日期:2026-05-30
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [现状概览](#1-现状概览)
|
||
2. [目录与命名规范](#2-目录与命名规范)
|
||
3. [重复代码与可抽取的公共层](#3-重复代码与可抽取的公共层)
|
||
4. [ConfigProvider 与主题系统](#4-configprovider-与主题系统)
|
||
5. [CSS 与 Design Token 策略](#5-css-与-design-token-策略)
|
||
6. [类型系统](#6-类型系统)
|
||
7. [Props 诚实性(接口 vs 实现)](#7-props-诚实性接口-vs-实现)
|
||
8. [共享 Hooks 与工具](#8-共享-hooks-与工具)
|
||
9. [测试覆盖](#9-测试覆盖)
|
||
10. [文档与 DX](#10-文档与-dx)
|
||
11. [构建与导出](#11-构建与导出)
|
||
12. [优先级路线图](#12-优先级路线图)
|
||
13. [附录:文件清单速查](#13-附录文件清单速查)
|
||
|
||
---
|
||
|
||
## 1. 现状概览
|
||
|
||
### 1.1 分层结构(已成型,方向正确)
|
||
|
||
```
|
||
src/
|
||
├── components/
|
||
│ ├── common/ # 基础通用(Button、ConfigProvider、Icon…)
|
||
│ ├── layout/ # 布局(Layout、Flex、Grid、Space…)
|
||
│ ├── nav/ # 导航(Menu、Tabs、Breadcrumb…)
|
||
│ ├── entry/ # 数据录入(Input、Select、Form…)
|
||
│ ├── feedback/ # 反馈(Modal、Message、Alert…)
|
||
│ ├── display/ # 数据展示(Table、Card、Tooltip…)
|
||
│ └── shared/ # 内部共享(hooks、types、utils)
|
||
├── theme/ # JS Token 定义
|
||
└── global.css # CSS 变量(--nv-*)
|
||
```
|
||
|
||
六大分类与 Ant Design 的组件分区思路一致,有利于 AI 生成代码时的语义检索。`shared/` 作为内部基础设施层也已建立。
|
||
|
||
### 1.2 做得好的地方
|
||
|
||
| 方面 | 说明 |
|
||
|------|------|
|
||
| **组件粒度** | 每个组件独立目录,含 `*.tsx` + `*.module.css` + `index.ts` + `*.test.tsx`,结构清晰 |
|
||
| **Barrel 导出** | 分类 `index.ts` → 根 `components/index.ts` → `src/index.ts`,对外 API 集中 |
|
||
| **测试基线** | 主组件几乎都有 Vitest + Testing Library 测试(74 个 test 文件) |
|
||
| **forwardRef** | 布局/表单类组件普遍支持 ref 透传 |
|
||
| **data-* 属性** | 组件用 `data-*` 标记状态,利于测试与 CSS 选择器 |
|
||
| **共享 Hooks** | `useClickOutside`、`useScrollListener` 等已在 overlay 类组件中复用 |
|
||
|
||
### 1.3 主要问题摘要
|
||
|
||
| 类别 | 严重度 | 一句话描述 |
|
||
|------|--------|-----------|
|
||
| 遗留重复路径 | 🔴 高 | `src/components/Button/` 与 `common/Button/` 并存 |
|
||
| ConfigProvider 未接线 | 🔴 高 | 实现了 context,但业务组件几乎不消费 |
|
||
| Token 三套并行 | 🔴 高 | JS tokens / CSS `--nv-*` / 组件 `--_*` 混用 |
|
||
| Overlay 重复实现 | 🟠 中 | Tooltip 与 Popover 各 ~360 行,逻辑高度相似 |
|
||
| 类型别名泛滥 | 🟠 中 | `PikaSize` 几乎无人用,各组件自建 `*Size` |
|
||
| Props 接口超前 | 🟠 中 | 多个 prop 在类型里声明但实现缺失 |
|
||
| 文档空白 | 🟠 中 | 80 个组件仅 1 个 `index.md` |
|
||
| 构建 subpath 可能无效 | 🟡 低 | `package.json exports "./*"` 与源码结构不匹配 |
|
||
|
||
---
|
||
|
||
## 2. 目录与命名规范
|
||
|
||
### 2.1 文件夹命名两套并存
|
||
|
||
| 风格 | 示例 | 数量 |
|
||
|------|------|------|
|
||
| **PascalCase** | `common/Button/`、`common/ConfigProvider/` | ~10 个 |
|
||
| **kebab-case** | `display/avatar/`、`entry/tree-select/` | ~67 个 |
|
||
|
||
**建议:** 统一为 **kebab-case 目录 + PascalCase 文件名**,与绝大多数组件一致:
|
||
|
||
```
|
||
display/avatar/Avatar.tsx ✅ 推荐
|
||
common/Button/Button.tsx ⚠️ 目录应改为 common/button/
|
||
```
|
||
|
||
迁移成本:仅 `common/` 下 10 个目录,可一次性批量 rename + 更新 import。
|
||
|
||
### 2.2 导出模式不一致
|
||
|
||
**现状:**
|
||
|
||
```ts
|
||
// 组件文件 — 普遍 default export
|
||
export default Button
|
||
|
||
// barrel — named export
|
||
export { Button } from './Button'
|
||
```
|
||
|
||
**建议:** 组件 TSX 改为 **named export only**,与 barrel 和 tree-shaking 更一致:
|
||
|
||
```ts
|
||
// Button.tsx
|
||
export const Button = forwardRef(...)
|
||
export type { ButtonProps }
|
||
```
|
||
|
||
### 2.3 复合组件组织方式不统一
|
||
|
||
| 模式 | 示例 |
|
||
|------|------|
|
||
| 同目录多文件 | `display/avatar/Avatar.tsx` + `AvatarGroup.tsx` |
|
||
| 单目录聚合 | `entry/choice/Checkbox.tsx` + `Radio.tsx` + `Switch.tsx` |
|
||
| Object.assign | `Layout.Header`、`Card.Meta` |
|
||
|
||
**建议:** 制定简单规则写入 `CONVENTIONS.md`(当前已删除):
|
||
|
||
- 强关联子组件 → 同目录 + `Object.assign` 或 compound export
|
||
- 独立可选组件 → 同目录分文件
|
||
- 录入类「形态变体」→ 可聚合(如 choice)
|
||
|
||
### 2.4 遗留路径:Button 双份
|
||
|
||
| 路径 | 状态 |
|
||
|------|------|
|
||
| `src/components/common/Button/` | ✅ 现行源码,根 index 导出 |
|
||
| `src/components/Button/` | ⚠️ Git 索引中仍存在,含 `index.md`、测试 |
|
||
|
||
**行动:** 删除 `src/components/Button/`,文档迁移至 `common/Button/index.md`(或 `docs/components/button.md`)。
|
||
|
||
---
|
||
|
||
## 3. 重复代码与可抽取的公共层
|
||
|
||
### 3.1 Tooltip / Popover — 最高优先级抽取
|
||
|
||
两者共享:
|
||
|
||
- `GAP`、`PLACEMENT_POSITION_MAP`、`FLIP_MAP` 常量
|
||
- Portal 渲染 + 定位计算
|
||
- `useScrollListener` + `useClickOutside`
|
||
- 显隐状态机
|
||
|
||
```
|
||
shared/overlay/
|
||
├── Overlay.tsx # 定位 + portal + 显隐
|
||
├── placement.ts # PLACEMENT_MAP, FLIP_MAP
|
||
├── useOverlay.ts # open/close/trigger 逻辑
|
||
└── Overlay.module.css
|
||
```
|
||
|
||
预估可减少 **~300 行**重复代码,后续 Popconfirm、Dropdown 也可复用。
|
||
|
||
### 3.2 `getSemantic` 内联重复(22 处)
|
||
|
||
以下组件各自定义相同模式的 `getSemantic` helper:
|
||
|
||
```
|
||
common/Button/Button.tsx
|
||
display/badge/Badge.tsx
|
||
display/card/Card.tsx
|
||
display/table/Table.tsx
|
||
display/tooltip/Tooltip.tsx
|
||
… 等 22 个文件
|
||
```
|
||
|
||
**建议:** 抽到 `shared/utils/semantic.ts`:
|
||
|
||
```ts
|
||
export function getSemanticClass(
|
||
styles: Record<string, string>,
|
||
semantic?: Record<string, string>,
|
||
part: string,
|
||
): string | undefined
|
||
```
|
||
|
||
### 3.3 Portal 工具已写但未用
|
||
|
||
`shared/utils/portal.ts` 提供 `getPortalContainer` / `renderPortal`,**零引用**。
|
||
Tooltip、Popover、Tour、Modal 均直接 `createPortal(..., document.body)`。
|
||
|
||
**建议:** overlay 重构时统一接入,并读取 ConfigProvider 的 `getPopupContainer`。
|
||
|
||
### 3.4 Table 内联分页 vs nav/Pagination
|
||
|
||
`display/table/Table.tsx` 内置 `PaginationInline`,功能仅为 prev/next + 页码,不支持:
|
||
|
||
- `showSizeChanger`
|
||
- `showQuickJumper`
|
||
- `pageSizeOptions`
|
||
|
||
**建议:** Table 直接组合 `nav/pagination/Pagination`,或抽取 `shared/PaginationMini`。
|
||
|
||
### 3.5 className 拼接模式重复
|
||
|
||
大量组件使用:
|
||
|
||
```ts
|
||
[className, styles.root].filter(Boolean).join(' ')
|
||
```
|
||
|
||
**建议:** `shared/utils/classNames.ts`(或引入轻量 `clsx` 依赖):
|
||
|
||
```ts
|
||
export function cn(...parts: (string | false | undefined | null)[]): string
|
||
```
|
||
|
||
---
|
||
|
||
## 4. ConfigProvider 与主题系统
|
||
|
||
### 4.1 现状:基础设施已建,消费方缺失
|
||
|
||
`ConfigProvider` 提供:
|
||
|
||
| Context 字段 | 是否被组件消费 |
|
||
|-------------|---------------|
|
||
| `prefixCls` | ❌ CSS 硬编码 `nv-*` |
|
||
| `iconPrefixCls` | ❌ Icon 用全局 `.nv-icon` |
|
||
| `colorPrimary` / `theme.token` | ⚠️ 仅注入 wrapper inline style |
|
||
| `theme.components` | ❌ 接口存在,从未应用 |
|
||
| `locale` | ❌ 无组件读取 |
|
||
| `zIndex` | ❌ Modal/Drawer 各自硬编码 |
|
||
| `getPopupContainer` | ❌ 仅 prop 级,不读 context |
|
||
|
||
`useConfig()` 仅出现在 `ConfigProvider.tsx` 及其测试中。
|
||
|
||
### 4.2 建议接线顺序
|
||
|
||
**Phase 1 — 最小可用全局配置:**
|
||
|
||
1. 创建 `shared/hooks/usePikaContext.ts`,封装 `useConfig()` + 默认值合并
|
||
2. Overlay 组件(Tooltip、Popover、Dropdown、Modal)读取 `getPopupContainer`、`zIndex`
|
||
3. 所有 portal 组件 z-index 从 context 叠加(base + offset)
|
||
|
||
**Phase 2 — 前缀与国际化:**
|
||
|
||
4. CSS Module 改为 `[data-nv-prefix]` 或 runtime class prefix(成本较高,可延后)
|
||
5. `locale` 接入 DatePicker、Pagination、Modal 按钮文案
|
||
|
||
**Phase 3 — 组件级主题:**
|
||
|
||
6. 实现 `theme.components.Button` 等 token 覆盖
|
||
|
||
### 4.3 Token 源统一
|
||
|
||
当前三套 token 并行:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ theme/tokens.ts (JS 对象) │
|
||
│ → entry/tokens/index.ts → inline style 注入 │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ global.css (:root --nv-*) │
|
||
│ → feedback 组件 CSS var fallback │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ 组件私有 --_* (TSX inline style → CSS Module) │
|
||
│ → Button, Layout, Progress 等 │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**问题:**
|
||
|
||
- `theme/tokens.ts` 的 `color.primary = '#6C5CE7'` 与 `global.css` 暗色模式值可能不同步
|
||
- Entry 组件 inline style **不响应** ConfigProvider 运行时改色
|
||
- `global.css` 首行 `@import './theme/dumi.css'` 把文档站样式耦合进库
|
||
|
||
**建议目标架构:**
|
||
|
||
```
|
||
theme/
|
||
├── tokens.css # 唯一 token 源(--nv-*)
|
||
├── tokens.ts # 从 CSS 变量读取或生成类型(可选)
|
||
├── dark.css
|
||
└── compact.css
|
||
|
||
组件 CSS:只用 var(--nv-*),组件级 --_* 仅用于 prop 驱动的动态值(width、height)
|
||
ConfigProvider:通过 style 覆盖 --nv-color-primary 等根变量
|
||
```
|
||
|
||
---
|
||
|
||
## 5. CSS 与 Design Token 策略
|
||
|
||
### 5.1 当前模式评估
|
||
|
||
| 模式 | 用途 | 评价 |
|
||
|------|------|------|
|
||
| `*.module.css` | 组件样式隔离 | ✅ 主流,正确 |
|
||
| `--nv-*` + fallback | 设计 token | ✅ 正确方向,但 fallback 硬编码过多 |
|
||
| `--_*` inline 注入 | prop 驱动动态值 | ✅ 适合 Layout/Button 的高度、宽度 |
|
||
| 全局 `Icon/style.css` | 图标字体 | ⚠️ 应改为 CSS Module 或 CSS-in-JS |
|
||
| inline style 色值 | Entry 组件 | ❌ 应迁移到 CSS var |
|
||
|
||
### 5.2 硬编码色值示例(需逐步清理)
|
||
|
||
以下文件存在与 token 并行的硬编码:
|
||
|
||
- `display/popover/Popover.module.css` — `#fff`、`rgba(0,0,0,0.85)`
|
||
- `display/collapse/Collapse.module.css` — 边框/背景硬编码
|
||
- `entry/tokens/index.ts` — 直接从 JS 对象读色,不经过 CSS 变量
|
||
|
||
### 5.3 CSS 变量命名规范建议
|
||
|
||
| 层级 | 前缀 | 示例 | 谁设置 |
|
||
|------|------|------|--------|
|
||
| 全局设计 token | `--nv-` | `--nv-color-primary` | global.css / ConfigProvider |
|
||
| 组件内部变量 | `--_{part}-` | `--_height`、`--_padding` | 组件 TSX inline style |
|
||
| 语义 DOM | `data-*` | `data-collapsed`、`data-size` | 组件 TSX |
|
||
|
||
避免 `--_*` 与 `--nv-*` 语义混淆:前者是**实例级**动态值,后者是**主题级**静态 token。
|
||
|
||
---
|
||
|
||
## 6. 类型系统
|
||
|
||
### 6.1 重复 Size / Status 类型
|
||
|
||
`shared/types/common.ts` 已定义:
|
||
|
||
```ts
|
||
export type PikaSize = 'small' | 'middle' | 'large'
|
||
export type PikaStatus = 'default' | 'success' | 'warning' | 'error' | 'info'
|
||
```
|
||
|
||
但 **几乎无组件引用**。各模块自建:
|
||
|
||
| 类型 | 位置 |
|
||
|------|------|
|
||
| `DataEntrySize` | `entry/common.ts` |
|
||
| `ButtonSize` | `common/Button/Button.tsx` |
|
||
| `AvatarSizeType` | `display/avatar/Avatar.tsx` |
|
||
| `TagSize`, `CardSize`, `TableSize`, `BadgeSize`… | 各 display 组件 |
|
||
|
||
**建议:**
|
||
|
||
```ts
|
||
// shared/types/common.ts — canonical types
|
||
export type PikaSize = 'small' | 'middle' | 'large'
|
||
export type PikaStatus = 'default' | 'success' | 'warning' | 'error' | 'info'
|
||
|
||
// entry/common.ts
|
||
import type { PikaSize, PikaStatus } from '../../shared/types'
|
||
export type DataEntrySize = PikaSize
|
||
export type DataEntryStatus = PikaStatus
|
||
|
||
// 组件 props
|
||
export interface ButtonProps {
|
||
size?: PikaSize
|
||
}
|
||
```
|
||
|
||
### 6.2 公共 Props 基类利用不足
|
||
|
||
`entry/common.ts` 定义了良好的基类:
|
||
|
||
- `DataEntryBaseProps`
|
||
- `DataEntryInputProps`
|
||
- `DataEntryTextualProps`
|
||
- `DataEntrySelectableProps`
|
||
|
||
**建议:** display / feedback 组件也建立类似基类:
|
||
|
||
```ts
|
||
// shared/types/component.ts
|
||
export interface PikaComponentProps {
|
||
className?: string
|
||
style?: CSSProperties
|
||
}
|
||
|
||
export interface PikaInteractiveProps extends PikaComponentProps {
|
||
disabled?: boolean
|
||
}
|
||
```
|
||
|
||
### 6.3 `CSSCustomProperties` 使用率低
|
||
|
||
`shared/types/css.ts` 仅 5 个组件 import,其余用 `Record<string, string>` 或无类型。
|
||
|
||
**建议:** 统一 cssVars 类型:
|
||
|
||
```ts
|
||
type CSSVars = CSSProperties & Record<`--${string}`, string>
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Props 诚实性(接口 vs 实现)
|
||
|
||
> 接口声明了但实现缺失的 prop,会误导 AI 和使用者。应 **实现** 或 **从类型中移除**(或标注 `@deprecated` / 文档说明「即将支持」)。
|
||
|
||
### 7.1 已确认未实现的 Props
|
||
|
||
| 组件 | Prop | 现状 |
|
||
|------|------|------|
|
||
| **Layout.Sider** | `onBreakpoint` | 有 `breakpoint` data 属性,无 resize 监听 |
|
||
| **DatePicker** | `mode`, `format` | 固定 date 模式 + `YYYY-MM-DD` |
|
||
| **TimePicker** | `format`, `hourStep` | 固定格式与步进 |
|
||
| **Slider** | `range` | 仅单值滑块 |
|
||
| **TextArea** | `autoSize`, `onResize` | 未实现自动高度 |
|
||
| **Upload** | `directory`, `onPreview` | 未实现文件夹上传与预览 |
|
||
| **Popconfirm** | `okType` | 确认按钮未区分 danger/primary |
|
||
| **TreeSelect** | `treeCheckable` | 解构为 `_treeCheckable` 或未使用 |
|
||
| **Form** | `labelCol`, `wrapperCol` | 无栅格布局 |
|
||
| **Table** | `filters`, `filteredValue`, `filterMultiple` | Column 类型完整,无 filter UI |
|
||
| **Table** | `showSizeChanger`, `showQuickJumper` | PaginationConfig 有类型,内联分页未支持 |
|
||
| **Mention** | `showSearch`, `showClear` | 继承自基类但未解构 |
|
||
|
||
### 7.2 处理策略
|
||
|
||
```
|
||
优先级 A — 高频 API(Ant Design 对标)
|
||
→ 补实现:DatePicker.format、Slider.range、Layout.onBreakpoint
|
||
|
||
优先级 B — 低频 / 复杂
|
||
→ 从类型移除,CHANGELOG 记录,待迭代再加
|
||
|
||
优先级 C — 计划内
|
||
→ 保留类型,组件 JSDoc 标注 @experimental
|
||
```
|
||
|
||
### 7.3 类型笔误
|
||
|
||
`TooltipPlacement` 含 `'rightRight'`,疑似应为 `'rightBottom'`(`Tooltip.tsx`)。`FLIP_MAP` 映射也需核对。
|
||
|
||
---
|
||
|
||
## 8. 共享 Hooks 与工具
|
||
|
||
### 8.1 已有 Hooks 使用情况
|
||
|
||
| Hook | 路径 | 已使用 | 应使用未使用 |
|
||
|------|------|--------|-------------|
|
||
| `useClickOutside` | `shared/hooks/` | Select, Dropdown, Tooltip, Popover, Popconfirm, AutoComplete, RangePicker | **Cascader, DatePicker, TimePicker, TreeSelect, Mention** |
|
||
| `useScrollListener` | 同上 | Affix, Anchor, BackTop, Tooltip, Popover, Tour | — |
|
||
| `useEscapeKey` | 同上 | Modal, Drawer, Tour, Image | — |
|
||
| `useMatchMedia` | 同上 | Grid | **Layout.Sider**(breakpoint) |
|
||
|
||
### 8.2 重复实现需清理
|
||
|
||
| 组件 | 问题 | 建议 |
|
||
|------|------|------|
|
||
| `FloatButton` | 自定义 `document.addEventListener('click')` | 改用 `useClickOutside` |
|
||
| `Image` | `useEscapeKey` + 额外 `keydown` 监听 | 合并为单一 hook |
|
||
| `Tour` | `useEscapeKey` + 额外 `keydown` 处理方向键 | 扩展 hook 或统一 handler |
|
||
|
||
### 8.3 建议新增 Hooks
|
||
|
||
| Hook | 用途 |
|
||
|------|------|
|
||
| `useControllableState` | 统一 controlled/uncontrolled 模式(大量组件重复 `isControlled` 逻辑) |
|
||
| `useBreakpoint` | Layout.Sider、Grid 共用 responsive 逻辑 |
|
||
| `useMergedRef` | forwardRef + 内部 ref 合并 |
|
||
|
||
`useControllableState` 示例 — 当前 Layout、Sider、Modal、Tabs 等均有类似代码:
|
||
|
||
```ts
|
||
const isControlled = value !== undefined
|
||
const current = isControlled ? value : internal
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 测试覆盖
|
||
|
||
### 9.1 覆盖率概况
|
||
|
||
- **主组件:** 几乎每个一级目录都有 `*.test.tsx` ✅
|
||
- **子组件:** 以下 **7 个** 无独立测试:
|
||
|
||
| 子组件 | 路径 | 风险 |
|
||
|--------|------|------|
|
||
| `AvatarGroup` | `display/avatar/` | maxCount、overflow 逻辑 |
|
||
| `BadgeRibbon` | `display/badge/` | 定位、颜色 |
|
||
| `CardGrid` / `CardMeta` | `display/card/` | 栅格布局 |
|
||
| `ImagePreviewGroup` | `display/image/` | 多图预览切换 |
|
||
| `StatisticTimer` | `display/statistic/` | 倒计时逻辑 |
|
||
| `CheckableTag` | `display/tag/` | 选中态 |
|
||
|
||
### 9.2 测试质量建议
|
||
|
||
| 方向 | 说明 |
|
||
|------|------|
|
||
| **行为测试优先** | 少测 class 名,多测交互(open/close、keyboard、form submit) |
|
||
| **未实现 prop 不测** | 避免测试「接口存在但行为不存在」造成 false positive |
|
||
| **ConfigProvider 集成测试** | 验证 theme/locale 注入后组件行为变化 |
|
||
| **a11y 基线** | overlay 组件补充 aria 属性断言 |
|
||
|
||
### 9.3 重复测试
|
||
|
||
`src/components/Button/Button.test.tsx` 与 `common/Button/Button.test.tsx` 可能重复 — 随遗留目录一并清理。
|
||
|
||
---
|
||
|
||
## 10. 文档与 DX
|
||
|
||
### 10.1 文档现状
|
||
|
||
| 类型 | 数量 | 路径 |
|
||
|------|------|------|
|
||
| 组件 co-located `index.md` | **1** | `components/Button/index.md`(遗留) |
|
||
| Dumi 指南 | 3 | `docs/index.md`, `getting-started.md`, `components/index.md` |
|
||
| 逐组件 API 文档 | **0** | — |
|
||
| 设计规范 | 1 | `DESIGN_SPEC.md`(完整) |
|
||
| 开发约定 | 0 | `CONVENTIONS.md` 已删除 |
|
||
|
||
### 10.2 文档建设建议
|
||
|
||
**短期(可脚本化):**
|
||
|
||
1. 为每个组件目录生成 `index.md` 模板(frontmatter + 基础 demo + Props 表占位)
|
||
2. Dumi 配置 `resolve.atomDirs` 指向 `src/components`
|
||
|
||
**中期:**
|
||
|
||
3. 从 TS 类型自动生成 Props 表(dumi-theme-Pika 或 typedoc)
|
||
4. 每个组件至少 3 个 demo:基础用法、受控模式、禁用/错误态
|
||
|
||
**长期:**
|
||
|
||
5. AI 示例库 — 与 `DESIGN_SPEC.md` 的 AI-First 原则对齐,每组件提供「AI 友好」单行示例
|
||
|
||
### 10.3 开发者体验
|
||
|
||
| 改进项 | 说明 |
|
||
|--------|------|
|
||
| 恢复 `CONVENTIONS.md` | 目录命名、export 规范、CSS 约定、测试要求 |
|
||
| `npm run lint:fix` | 已有 lint-staged,可加 CI workflow |
|
||
| Storybook vs Dumi | 当前 Dumi 足够,保持单文档方案 |
|
||
| 组件生成 CLI | `pnpm gen component Button --category common` 脚手架 |
|
||
|
||
---
|
||
|
||
## 11. 构建与导出
|
||
|
||
### 11.1 当前构建链
|
||
|
||
```
|
||
father build → dist/cjs + dist/esm
|
||
src/index.ts → export * from './components' + tokens
|
||
```
|
||
|
||
### 11.2 问题
|
||
|
||
| 问题 | 详情 |
|
||
|------|------|
|
||
| **Subpath exports 可能无效** | `package.json` 声明 `"./*" → dist/esm/*/index.js`,但源码无对应分包 |
|
||
| **CSS 未作为包入口** | 消费者需自行引入 token CSS;`global.css` 未 export |
|
||
| **global.css 耦合 dumi** | `@import './theme/dumi.css'` 不应出现在库运行时入口 |
|
||
| **shared 不对外** | 第三方无法复用 hooks/utils |
|
||
| **手动维护导出面** | 新组件需改 3 处 index |
|
||
|
||
### 11.3 建议
|
||
|
||
**package.json exports 修正:**
|
||
|
||
```json
|
||
{
|
||
"exports": {
|
||
".": {
|
||
"import": "./dist/esm/index.js",
|
||
"require": "./dist/cjs/index.js"
|
||
},
|
||
"./styles": "./dist/styles/global.css",
|
||
"./tokens": "./dist/esm/theme/tokens.js"
|
||
}
|
||
}
|
||
```
|
||
|
||
**CSS 构建:**
|
||
|
||
- 将 `global.css` 拆为 `styles/tokens.css`(纯 token)+ `styles/dumi.css`(文档专用)
|
||
- father 配置 `extraBabelPlugins` 或 postcss 复制 CSS 到 dist
|
||
|
||
**按需加载(可选):**
|
||
|
||
- 若需要 `@Pika/ui/button`,需 father 多 entry 配置 + 每组件独立 package path
|
||
|
||
---
|
||
|
||
## 12. 优先级路线图
|
||
|
||
### P0 — 基础一致性(1–2 周)
|
||
|
||
| # | 任务 | 影响 |
|
||
|---|------|------|
|
||
| 1 | 删除 `src/components/Button/` 遗留,统一至 `common/Button/` | 消除混淆 |
|
||
| 2 | 统一 `common/` 目录为 kebab-case | 命名一致 |
|
||
| 3 | Props 诚实性清理:移除或标注未实现 prop | AI 生成准确 |
|
||
| 4 | `global.css` 与 dumi.css 解耦 | 库可独立使用 |
|
||
| 5 | 新增 `shared/utils/classNames.ts` | 减少重复 |
|
||
|
||
### P1 — 架构增强(2–4 周)
|
||
|
||
| # | 任务 | 影响 |
|
||
|---|------|------|
|
||
| 6 | 抽取 `shared/overlay/` primitives | Tooltip/Popover 减 300+ 行 |
|
||
| 7 | 抽取 `getSemantic` → `shared/utils/semantic.ts` | 22 处重复消除 |
|
||
| 8 | ConfigProvider Phase 1 接线(zIndex、getPopupContainer) | 全局配置可用 |
|
||
| 9 | 统一 Size/Status 类型为 `PikaSize` / `PikaStatus` | 类型一致 |
|
||
| 10 | Cascader/DatePicker/TreeSelect 接入 `useClickOutside` | 交互完整 |
|
||
| 11 | 新增 `useControllableState` hook | 减少状态逻辑重复 |
|
||
|
||
### P2 — 体验完善(4–8 周)
|
||
|
||
| # | 任务 | 影响 |
|
||
|---|------|------|
|
||
| 12 | Token 统一为 CSS `--nv-*` 单源 | 主题可运行时切换 |
|
||
| 13 | 补全 7 个子组件测试 | 测试完整 |
|
||
| 14 | 每组件 `index.md` + 基础 demo | 文档可用 |
|
||
| 15 | 恢复并完善 `CONVENTIONS.md` | 贡献者友好 |
|
||
| 16 | 实现高频未实现 prop(DatePicker.format、Slider.range) | API 完整 |
|
||
| 17 | package.json exports + CSS 入口修正 | 发布可用 |
|
||
|
||
### P3 — 长期演进
|
||
|
||
| # | 任务 |
|
||
|---|------|
|
||
| 18 | 组件级 theme override(`theme.components`) |
|
||
| 19 | 按需 subpath import 分包构建 |
|
||
| 20 | 组件生成 CLI |
|
||
| 21 | a11y 审计与 WCAG 基线 |
|
||
| 22 | 可视化组件(AntV)与 UI 组件 Token 统一 |
|
||
|
||
---
|
||
|
||
## 13. 附录:文件清单速查
|
||
|
||
### 13.1 应删除/迁移
|
||
|
||
```
|
||
src/components/Button/ → 删除,保留 common/Button/
|
||
src/components/Button/index.md → 迁移至 docs 或 common/Button/
|
||
```
|
||
|
||
### 13.2 应统一命名(common/ PascalCase → kebab-case)
|
||
|
||
```
|
||
common/Button/ → common/button/
|
||
common/ConfigProvider/ → common/config-provider/
|
||
common/FloatButton/ → common/float-button/
|
||
common/BackTop/ → common/back-top/
|
||
… (共 10 个)
|
||
```
|
||
|
||
### 13.3 共享层待建设
|
||
|
||
```
|
||
shared/overlay/Overlay.tsx
|
||
shared/utils/classNames.ts
|
||
shared/utils/semantic.ts
|
||
shared/hooks/useControllableState.ts
|
||
shared/hooks/useBreakpoint.ts
|
||
shared/hooks/usePikaContext.ts
|
||
```
|
||
|
||
### 13.4 应对接 ConfigProvider 的组件(优先)
|
||
|
||
```
|
||
display/tooltip/Tooltip.tsx
|
||
display/popover/Popover.tsx
|
||
nav/dropdown/Dropdown.tsx
|
||
feedback/modal/Modal.tsx
|
||
feedback/drawer/Drawer.tsx
|
||
feedback/message/Message.tsx
|
||
feedback/notification/Notification.tsx
|
||
display/tour/Tour.tsx
|
||
entry/select/Select.tsx
|
||
entry/date-picker/DatePicker.tsx
|
||
```
|
||
|
||
### 13.5 统计
|
||
|
||
| 指标 | 数量 |
|
||
|------|------|
|
||
| 组件目录 | ~77 |
|
||
| 组件 TSX(不含测试) | ~80 |
|
||
| 测试文件 | ~74 |
|
||
| 无独立测试的子组件 | 7 |
|
||
| getSemantic 重复 | 22 处 |
|
||
| 未实现 prop 的组件 | ~12 |
|
||
| co-located 文档 | 1 |
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
Pika 的 **六大分类 + 单组件目录结构** 已经是一个健康的基础,测试覆盖和 forwardRef 等实践也到位。当前主要短板集中在:
|
||
|
||
1. **一致性** — 命名、导出、类型、token 三套并行
|
||
2. **诚实性** — 接口超前于实现,对 AI-First 目标伤害最大
|
||
3. **基础设施未接线** — ConfigProvider、shared utils 写了但没用起来
|
||
4. **重复** — Overlay、getSemantic、className 拼接、受控状态
|
||
|
||
按 P0 → P1 → P2 推进,可以在不推翻现有结构的前提下,显著提升可维护性和 AI 代码生成准确率。
|