2
0

016.002.modularize-utils.md 18 KB

Story 016.002: 搬迁配置和工具函数到独立模块并添加类型定义

Status

Approved

Story

作为 图表库开发者, 我想要 将 u-charts 核心库中的配置和工具函数搬迁到独立模块并添加 TypeScript 类型定义, 以便 提高代码的可维护性、可测试性和类型安全性,为后续 React 组件封装打下基础。

Acceptance Criteria

  1. config.ts 创建完成,包含 config、assign、util 并有完整类型注解
  2. utils/ 目录下所有文件创建完成,每个函数都有类型注解
  3. 类型检查通过(pnpm typecheck),无 any 类型(除非必要)
  4. 每个文件控制在 500 行以内
  5. 代码逻辑与原始 u-charts.ts 完全一致

Tasks / Subtasks

  • [ ] Task 1: 分析配置和工具函数 (AC: 1, 2, 5)

    • 1.1 识别 config、assign、util 等配置对象
    • 1.2 识别颜色工具(hexToRgb等)
    • 1.3 识别数学工具(findRange、normalInt等)
    • 1.4 识别坐标转换工具(convertCoordinateOrigin等)
    • 1.5 识别文本测量工具(measureText等)
    • 1.6 识别碰撞检测工具(avoidCollision等)
    • 1.7 识别其他工具函数(getH5Offset、isInAngleRange等)
  • [ ] Task 2: 创建模块化目录结构 (AC: 1, 2)

    • 2.1 创建 src/lib/config.ts
    • 2.2 创建 src/lib/utils/ 目录
    • 2.3 创建 src/lib/utils/color.ts
    • 2.4 创建 src/lib/utils/math.ts
    • 2.5 创建 src/lib/utils/coordinate.ts
    • 2.6 创建 src/lib/utils/text.ts
    • 2.7 创建 src/lib/utils/collision.ts
    • 2.8 创建 src/lib/utils/misc.ts(其他工具函数)
    • 2.9 创建 src/lib/utils/index.ts 统一导出
  • [ ] Task 3: 搬迁配置对象并添加类型注解 (AC: 1, 3, 4, 5)

    • 3.1 将 config 对象搬迁到 config.ts,添加类型注解
    • 3.2 将 assign 函数搬迁到 config.ts,添加类型注解
    • 3.3 将 util 对象搬迁到 config.ts,添加类型注解
    • 3.4 为 config、assign、util 添加完整的 TypeScript 类型定义
  • [ ] Task 4: 搬迁颜色工具函数并添加类型注解 (AC: 2, 3, 4, 5)

    • 4.1 将 hexToRgb 函数搬迁到 utils/color.ts
    • 4.2 为 hexToRgb 添加参数和返回值类型注解
    • 4.3 导出函数并验证类型正确性
  • [ ] Task 5: 搬迁数学工具函数并添加类型注解 (AC: 2, 3, 4, 5)

    • 5.1 将 findRange 函数搬迁到 utils/math.ts
    • 5.2 将 calCandleMA 函数搬迁到 utils/math.ts
    • 5.3 为数学函数添加参数和返回值类型注解
    • 5.4 导出函数并验证类型正确性
  • [ ] Task 6: 搬迁坐标转换工具函数并添加类型注解 (AC: 2, 3, 4, 5)

    • 6.1 将 convertCoordinateOrigin 函数搬迁到 utils/coordinate.ts
    • 6.2 将 isInAngleRange 函数搬迁到 utils/coordinate.ts
    • 6.3 为坐标转换函数添加参数和返回值类型注解
    • 6.4 定义坐标点、中心点等类型接口
    • 6.5 导出函数和类型并验证类型正确性
  • [ ] Task 7: 搬迁文本测量工具函数并添加类型注解 (AC: 2, 3, 4, 5)

    • 7.1 将 measureText 函数搬迁到 utils/text.ts
    • 7.2 为 measureText 添加参数和返回值类型注解
    • 7.3 定义文本度量相关类型接口
    • 7.4 导出函数和类型并验证类型正确性
  • [ ] Task 8: 搬迁碰撞检测工具函数并添加类型注解 (AC: 2, 3, 4, 5)

    • 8.1 将 avoidCollision 函数搬迁到 utils/collision.ts
    • 8.2 将 util.isCollision 函数搬迁到 utils/collision.ts
    • 8.3 为碰撞检测函数添加参数和返回值类型注解
    • 8.4 定义碰撞对象类型接口
    • 8.5 导出函数和类型并验证类型正确性
  • [ ] Task 9: 搬迁其他工具函数并添加类型注解 (AC: 2, 3, 4, 5)

    • 9.1 将 getH5Offset 函数搬迁到 utils/misc.ts
    • 9.2 将 createCurveControlPoints 函数搬迁到 utils/misc.ts
    • 9.3 将 calValidDistance 函数搬迁到 utils/misc.ts
    • 9.4 将 getTouches 函数搬迁到 utils/misc.ts
    • 9.5 为其他工具函数添加参数和返回值类型注解
    • 9.6 定义相关类型接口
    • 9.7 导出函数和类型并验证类型正确性
  • [ ] Task 10: 更新导出和验证搬迁结果 (AC: 1, 2, 3, 5)

    • 10.1 更新 src/lib/utils/index.ts 统一导出所有工具函数
    • 10.2 更新 src/index.ts 导出新模块
    • 10.3 运行类型检查验证所有类型注解正确
    • 10.4 确保所有工具函数正确导出
    • 10.5 确认代码逻辑与原始文件完全一致
    • 10.6 验证每个文件控制在 500 行以内

Dev Notes

前置故事见解

故事 016.001 完成状态:

  • ✅ 已创建 mini-charts 包基础结构
  • ✅ 已迁移 u-charts 核心库到 src/lib/u-charts.ts(7680行代码)
  • ✅ 包可以成功构建和类型检查通过
  • ⚠️ 使用 @ts-nocheck 绕过原始库中的类型问题
  • ⚠️ 存在变量重复声明问题(将在本故事中通过模块化解决)

故事 016.001 关键技术决策:

  • 使用 @ts-nocheck 绕过原始 u-charts 库的类型问题
  • 修复了 process 变量与 Node.js 全局对象的冲突(重命名为 chartProcess
  • 修复了 uChartsEvent 类语法问题
  • 保留了原始 u-charts 库的所有功能逻辑

迁移策略: 保持 u-charts 核心绘制逻辑不变,渐进式模块化并添加类型定义

技术栈要求

来源: tech-stack.md

  • TypeScript: 5.4.5 [来源: tech-stack.md]
  • React: 18.0.0(mini 包使用 React 18,不是 React 19)[来源: 故事 016.001 Dev Notes]
  • Taro: 4.1.4 [来源: tech-stack.md]
  • Node.js: 20.18.3(运行时环境)[来源: tech-stack.md]
  • 包管理器: pnpm workspace [来源: source-tree.md]

项目结构指南

来源: source-tree.md

mini-ui-packages/
└── mini-charts/              # mini-charts 包
    ├── src/
    │   ├── index.ts          # 主入口文件
    │   └── lib/
    │       ├── u-charts.ts   # u-charts 核心库(7680行)
    │       ├── config.ts     # [新增] 配置对象
    │       └── utils/        # [新增] 工具函数目录
    │           ├── index.ts  # 工具函数统一导出
    │           ├── color.ts  # 颜色工具函数
    │           ├── math.ts   # 数学工具函数
    │           ├── coordinate.ts  # 坐标转换工具
    │           ├── text.ts   # 文本测量工具
    │           ├── collision.ts   # 碰撞检测工具
    │           └── misc.ts   # 其他工具函数
    ├── tests/
    │   └── u-charts.test.ts
    ├── package.json
    ├── tsconfig.json
    └── jest.config.cjs

TypeScript 配置规范

来源: ui-package-standards.md

{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["DOM", "DOM.Iterable", "ES2020"],
    "module": "ESNext",
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src"]
}

本故事重要: 需要移除 @ts-nocheck,为所有函数添加完整的类型注解,确保 strict: true 模式下无类型错误。

u-charts 源文件分析

来源: mini-charts/src/lib/u-charts.ts

文件统计:

  • 总行数: 7680 行
  • 导出函数: 约 60+ 个工具/数据处理函数

配置对象:

  1. config: 默认配置对象,包含版本、颜色、字体等配置
  2. assign: 深度合并对象的函数
  3. util: 工具函数集合(toFixed、isFloat、approximatelyEqual、isSameSign、isSameXCoordinateArea、isCollision)

颜色工具函数:

  • hexToRgb(hexValue, opc): 十六进制颜色转 RGBA

数学工具函数:

  • findRange(num, type, limit): 计算数值范围
  • calCandleMA(dayArr, nameArr, colorArr, kdata): 计算 K 线图移动平均线
  • createCurveControlPoints(points, i): 创建曲线控制点

坐标转换工具函数:

  • convertCoordinateOrigin(x, y, center): 坐标原点转换
  • isInAngleRange(angle, startAngle, endAngle): 判断角度是否在范围内
  • calValidDistance(self, distance, chartData, config, opts): 计算有效滚动距离

文本测量工具函数:

  • measureText(text, fontSize, context): 测量文本宽度

碰撞检测工具函数:

  • avoidCollision(obj, target): 避免碰撞
  • util.isCollision(obj1, obj2): 检测两个对象是否碰撞

其他工具函数:

  • getH5Offset(e): 兼容 H5 点击事件
  • getTouches(touches, opts, e): 获取触摸点坐标
  • dataCombine(series): 数据合并
  • dataCombineStack(series, len): 堆叠数据合并
  • fixPieSeries(series, opts, config): 修复饼图系列数据
  • fillSeries(series, opts, config): 填充系列数据
  • fillCustomColor(linearType, customColor, series, config): 填充自定义颜色
  • getDataRange(minData, maxData): 获取数据范围

类型定义规范

来源: ui-package-standards.md

本故事重要: 必须为所有函数添加完整的类型注解,避免使用 any 类型(除非必要)。

类型定义示例:

// config.ts 类型定义示例
export interface UChartsConfig {
  version: string;
  yAxisWidth: number;
  xAxisHeight: number;
  padding: [number, number, number, number];
  rotate: boolean;
  fontSize: number;
  fontColor: string;
  dataPointShape: string[];
  color: string[];
  linearColor: string[];
  pieChartLinePadding: number;
  pieChartTextPadding: number;
  titleFontSize: number;
  subtitleFontSize: number;
  radarLabelTextMargin: number;
}

export const config: UChartsConfig = {
  version: 'v2.5.0-20230101',
  yAxisWidth: 15,
  // ... 其他配置
};

export function assign<T>(target: T, ...varArgs: Partial<T>[]): T {
  // 实现逻辑
}

export interface Util {
  toFixed(num: number, limit?: number): number | string;
  isFloat(num: number): boolean;
  approximatelyEqual(num1: number, num2: number): boolean;
  isSameSign(num1: number, num2: number): boolean;
  isSameXCoordinateArea(p1: Point, p2: Point): boolean;
  isCollision(obj1: CollisionObject, obj2: CollisionObject): boolean;
}

export const util: Util = {
  toFixed: function toFixed(num, limit = 2) {
    // 实现逻辑
  },
  // ... 其他工具函数
};
// utils/color.ts 类型定义示例
export interface RgbColor {
  r: number;
  g: number;
  b: number;
}

export function hexToRgb(hexValue: string, opc: number): string {
  // 实现逻辑
}
// utils/math.ts 类型定义示例
export function findRange(num: number, type?: 'upper' | 'lower', limit?: number): number {
  // 实现逻辑
}

export interface CandleDataPoint {
  [key: string]: number;
}

export interface MASeriesItem {
  data: (number | null)[];
  name: string;
  color: string;
}

export function calCandleMA(
  dayArr: number[],
  nameArr: string[],
  colorArr: string[],
  kdata: CandleDataPoint[]
): MASeriesItem[] {
  // 实现逻辑
}
// utils/coordinate.ts 类型定义示例
export interface Point {
  x: number;
  y: number;
}

export interface Center {
  x: number;
  y: number;
}

export function convertCoordinateOrigin(x: number, y: number, center: Center): Point {
  // 实现逻辑
}

export function isInAngleRange(
  angle: number,
  startAngle: number,
  endAngle: number
): boolean {
  // 实现逻辑
}
// utils/text.ts 类型定义示例
export interface TextMeasure {
  width: number;
}

export function measureText(
  text: string,
  fontSize: number,
  context: CanvasRenderingContext2D
): TextMeasure {
  // 实现逻辑
}
// utils/collision.ts 类型定义示例
export interface CollisionObject {
  start: Point;
  end?: Point;
  width: number;
  height: number;
  center?: Point;
  area?: {
    start: Point;
    end: Point;
    width: number;
    height: number;
  };
}

export interface Point {
  x: number;
  y: number;
}

export function avoidCollision(
  obj: CollisionObject,
  target: CollisionObject[]
): CollisionObject[] {
  // 实现逻辑
}

export function isCollision(obj1: CollisionObject, obj2: CollisionObject): boolean {
  // 实现逻辑(来自 util.isCollision)
}
// utils/misc.ts 类型定义示例
export interface H5Event {
  offsetX: number;
  offsetY: number;
  mp: {
    changedTouches: Array<{ x: number; y: number }>;
  };
}

export function getH5Offset(e: { offsetX: number; offsetY: number }): H5Event {
  // 实现逻辑
}

export interface TouchPoint {
  x: number;
  y: number;
}

export function getTouches(
  touches: TouchPoint[],
  opts: { width: number; area: number[] },
  e: any
): TouchPoint[] {
  // 实现逻辑
}

编码标准

来源: coding-standards.md

  • 代码风格: TypeScript 严格模式,一致的缩进和命名
  • 类型注解: 必须为所有函数参数和返回值添加类型注解
  • 接口定义: 为复杂数据结构定义 TypeScript 接口
  • 导出规范: 使用 ES6 export 语法导出函数和类型

测试策略

来源: testing-strategy.md

测试框架: Jest(mini 包使用 Jest,不是 Vitest)

测试位置: tests/ 目录(与源码并列)

测试类型:

  • 单元测试: 测试工具函数的正确性
  • 不要求集成测试和 E2E 测试(这些在后续故事中)

测试要求:

  1. 为每个工具函数编写单元测试
  2. 测试覆盖率目标: 基础测试即可,不要求高覆盖率
  3. Mock Canvas 上下文以支持依赖 Canvas 的函数测试

测试命令:

# 进入包目录
cd mini-ui-packages/mini-charts

# 运行所有测试
pnpm test

# 运行特定测试
pnpm test --testNamePattern "工具函数测试"

# 类型检查
pnpm typecheck

构建和发布规范

来源: ui-package-standards.md

mini-charts 包在 PNPM 工作空间中:

  • 开发模式: 直接使用 TypeScript 源码
  • 构建: 使用 tsc 编译到 dist/ 目录
  • 类型检查: 使用 tsc --noEmit 验证类型
  • 发布: 本故事不要求发布,仅验证本地构建成功

重要: 本故事完成后,应该可以:

  1. ✅ 运行 pnpm build 成功生成 dist/ 目录
  2. ✅ 运行 pnpm typecheck 无类型错误
  3. ✅ 所有新模块正确导出,可以被主入口文件引用

开发流程

来源: development-workflow.md

开发命令:

# 进入包目录
cd mini-ui-packages/mini-charts

# 安装依赖(在根目录)
pnpm install

# 开发模式(监听文件变化)
pnpm dev

# 类型检查
pnpm typecheck

# 运行测试
pnpm test

# 构建
pnpm build

环境配置

来源: CLAUDE.md

  • Node.js: 20.19.2
  • 包管理器: pnpm
  • 测试调试: 使用 pnpm test --testNamePattern "测试名称" 运行特定测试
  • Mini 测试: 需要 Jest,不是 Vitest
  • 类型检查: 使用 pnpm typecheck 加 grep 来过滤要检查的指定文件

文件位置规范

来源: source-tree.md

所有新文件应创建在:

  • 配置文件: mini-ui-packages/mini-charts/src/lib/config.ts
  • 工具函数目录: mini-ui-packages/mini-charts/src/lib/utils/
  • 工具函数文件:
    • color.ts: 颜色工具函数
    • math.ts: 数学工具函数
    • coordinate.ts: 坐标转换工具
    • text.ts: 文本测量工具
    • collision.ts: 碰撞检测工具
    • misc.ts: 其他工具函数
    • index.ts: 工具函数统一导出
  • 主入口: mini-ui-packages/mini-charts/src/index.ts(需要更新导出)
  • 测试文件: mini-ui-packages/mini-charts/tests/(可选,本故事不强制要求)

技术约束

  1. 搬迁原则: 保持代码逻辑完全不变,只改变文件组织方式
  2. 类型注解: 只在搬迁过程中添加 TypeScript 类型注解,不修改代码的实现逻辑
  3. 文件大小: 每个文件控制在 500 行以内
  4. 类型安全: 避免使用 any 类型(除非必要)
  5. 向后兼容: 保持与原始 u-charts.ts 的功能兼容性

验证标准

完成本故事后,应该满足:

  1. src/lib/config.ts 文件存在,包含 config、assign、util 并有完整类型注解
  2. src/lib/utils/ 目录下所有文件存在,每个函数都有类型注解
  3. ✅ 运行 pnpm typecheck 无类型错误
  4. ✅ 每个文件控制在 500 行以内
  5. ✅ 代码逻辑与原始 u-charts.ts 完全一致
  6. ✅ 所有新模块正确导出,可以被主入口文件引用

不包含在本故事中的工作

来源: epic-016 故事列表

以下工作不在本故事范围内:

  • ❌ 数据处理函数模块化(故事 016.003)
  • ❌ 图表数据点计算函数模块化(故事 016.004)
  • ❌ 绘制函数模块化(故事 016.005)
  • ❌ 核心类模块化(故事 016.006)
  • ❌ React 组件封装(故事 016.007)

Testing

测试框架: Jest(不是 Vitest)

测试位置: tests/ 目录(与源码并列)

测试要求:

  1. 创建基础测试验证工具函数的正确导入和导出
  2. 测试工具函数的基本功能(可选,本故事不强制要求)
  3. Mock Canvas 上下文以支持依赖 Canvas 的函数测试(如需要)
  4. 测试覆盖率目标: 基础测试即可,不要求高覆盖率

测试命令:

# 运行所有测试
pnpm test

# 运行特定测试
pnpm test --testNamePattern "配置和工具函数测试"

参考: testing-strategy.md

Change Log

Date Version Description Author
2025-12-24 1.0 创建故事文档 Bob (Scrum Master)

Dev Agent Record

此部分由开发代理在实施过程中填写

Agent Model Used

Debug Log References

Completion Notes List

File List

QA Results

此部分由 QA 代理在审查完成后填写