Ready for Review
作为 小程序开发者, 我想要 创建现代 React 函数式组件封装 u-charts 核心库, 以便 简化图表使用方式,无需手动管理 Canvas 上下文和事件处理,提高开发效率。
u-charts 原库需要手动管理 Canvas 上下文和事件处理,使用类组件的方式较为繁琐。现代小程序开发使用 React 函数式组件和 Hooks,需要创建易于使用的图表组件。
参考实现: docs/小程序图表库示例/使用示例.md 提供了完整的 React + Taro 使用示例,使用的是类组件方式。
前置故事完成状态:
@d8d/mini-charts 导入使用故事 016.001: ✅ 已完成 - 创建 mini-charts 包基础结构 故事 016.002: ✅ 已完成 - 模块化 config 和 utils 故事 016.003: ✅ 已完成 - 模块化 data-processing 函数 故事 016.004: ✅ 已完成 - 模块化 charts-data 函数 故事 016.005: ✅ 已完成 - 模块化 renderers 函数 故事 016.006: ✅ 已完成 - 搬迁核心类并完成模块化 故事 016.007: ✅ 已完成 - 搬迁遗漏的辅助函数 故事 016.008: ✅ 已完成 - 搬迁核心绘制函数完成模块化
pnpm typecheck)pnpm build),自动生成 .d.ts 声明文件[x] Task 1: 创建 BaseChart 基础组件 (AC: 1, 5, 6)
src/components/BaseChart.tsx 基础组件[x] Task 2: 创建 ColumnChart 柱状图组件 (AC: 2, 3, 4, 6)
src/components/ColumnChart.tsx[x] Task 3: 创建 LineChart 折线图组件 (AC: 2, 3, 4, 6)
src/components/LineChart.tsx[x] Task 4: 创建 CandleChart K线图组件 (AC: 2, 3, 4, 6)
src/components/CandleChart.tsx[x] Task 5: 创建 PieChart 饼图组件 (AC: 2, 3, 4, 6)
src/components/PieChart.tsx[x] Task 6: 创建 RadarChart 雷达图组件 (AC: 2, 3, 4, 6)
src/components/RadarChart.tsx[x] Task 7: 创建组件导出文件 (AC: 3, 6)
src/components/index.ts[x] Task 8: 更新 src/index.ts 主入口 (AC: 3, 6, 7)
[x] Task 9: 验证组件实现 (AC: 6, 7)
pnpm typecheck)pnpm build)[x] Task 10: 创建基础测试(可选) (AC: 3, 4)
故事 016.001-016.008 完成状态总结:
src/lib/charts/u-charts.ts 导出 uCharts 主类src/lib/charts/u-charts-event.ts 导出 uChartsEvent 事件类pnpm typecheck)pnpm build),自动生成 .d.ts 声明文件关键技术决策:
export 语法导出函数和类型重要: mini 包使用 React 18,不是主项目使用的 React 19。
来源: source-tree.md
mini-ui-packages/
└── mini-charts/ # mini-charts 包
├── src/
│ ├── index.ts # 主入口文件(需要更新)
│ ├── components/ # [本故事创建] React 图表组件
│ │ ├── BaseChart.tsx # 基础图表组件
│ │ ├── ColumnChart.tsx # 柱状图组件
│ │ ├── LineChart.tsx # 折线图组件
│ │ ├── CandleChart.tsx # K线图组件
│ │ ├── PieChart.tsx # 饼图组件
│ │ ├── RadarChart.tsx # 雷达图组件
│ │ └── index.ts # 组件导出
│ └── lib/ # u-charts 核心库(已完成)
│ ├── config.ts
│ ├── utils/
│ ├── data-processing/
│ ├── charts-data/
│ ├── renderers/
│ ├── helper-functions/
│ ├── draw-controllers/
│ └── charts/
│ ├── u-charts-event.ts
│ ├── u-charts.ts
│ └── index.ts
├── tests/ # 测试目录
├── package.json
├── tsconfig.json
└── jest.config.cjs
{
"compilerOptions": {
"target": "ES2020",
"lib": ["DOM", "DOM.Iterable", "ES2020"],
"module": "ESNext",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
}
}
本故事重要: 需要为所有组件Props接口添加完整的类型注解,确保 strict: true 模式下无类型错误。
import React, { Component } from 'react';
import Taro from '@tarojs/taro';
import { View, Canvas } from '@tarojs/components';
import uCharts from '../../js_sdk/u-charts/u-charts.js';
export default class Index extends Component {
constructor() {
super(...arguments)
this.state = {
cWidth: '',
cHeight: '',
pixelRatio: 1,
}
}
componentDidMount() {
const sysInfo = Taro.getSystemInfoSync();
let pixelRatio = 1;
if (Taro.getEnv() === Taro.ENV_TYPE.ALIPAY) {
pixelRatio = sysInfo.pixelRatio;
}
const cWidth = pixelRatio * sysInfo.windowWidth;
const cHeight = 500 / 750 * cWidth;
this.setState({ cWidth, cHeight, pixelRatio }, () => this.getServerData());
}
showColumn = (canvasId, chartData) => {
const { cWidth, cHeight, pixelRatio } = this.state;
let ctx = Taro.createCanvasContext(canvasId);
canvaColumn = new uCharts({
type: 'column',
context: ctx,
legend: true,
fontSize: 11,
background: '#FFFFFF',
pixelRatio,
animation: true,
categories: chartData.categories,
series: chartData.series,
xAxis: { disableGrid: true },
yAxis: {},
dataLabel: true,
width: cWidth,
height: cHeight,
extra: {
column: {
type: 'group',
width: cWidth * 0.45 / chartData.categories.length
}
}
});
}
touchColumn = (e) => {
canvaColumn.showToolTip(e, {
formatter: function (item, category) {
return category + ' ' + item.name + ':' + item.data
}
});
}
render() {
const { cWidth, cHeight } = this.state;
return (
<View>
<Canvas
canvas-id="canvasColumn"
id="canvasColumn"
onTouchStart={this.touchColumn}
/>
</View>
)
}
}
| API | 说明 |
|---|---|
Taro.getSystemInfoSync() |
获取系统信息(屏幕宽度、像素比等) |
Taro.createCanvasContext(canvasId) |
创建Canvas上下文 |
Taro.getEnv() |
获取当前环境(微信/支付宝/H5等) |
new uCharts(config) |
创建图表实例 |
chart.showToolTip(e, options) |
显示tooltip |
chart.scrollStart(e) / chart.scroll(e) / chart.scrollEnd(e) |
滚动事件处理 |
// src/components/BaseChart.tsx
import React, { useEffect, useRef, useMemo } from 'react';
import Taro from '@tarojs/taro';
import { Canvas } from '@tarojs/components';
import { uCharts } from '../lib/charts';
export interface BaseChartProps {
canvasId: string;
width?: number;
height?: number;
pixelRatio?: number;
type: string;
categories: string[];
series: any[];
config?: Record<string, any>;
onTouchStart?: (e: any) => void;
onTouchMove?: (e: any) => void;
onTouchEnd?: (e: any) => void;
}
export const BaseChart: React.FC<BaseChartProps> = (props) => {
const {
canvasId,
width,
height,
pixelRatio,
type,
categories,
series,
config = {},
onTouchStart,
onTouchMove,
onTouchEnd,
} = props;
const chartRef = useRef<uCharts | null>(null);
// 计算响应式尺寸
const { cWidth, cHeight, actualPixelRatio } = useMemo(() => {
const sysInfo = Taro.getSystemInfoSync();
const pr = pixelRatio ?? (Taro.getEnv() === Taro.ENV_TYPE.ALIPAY ? sysInfo.pixelRatio : 1);
const cw = width ?? pr * sysInfo.windowWidth;
const ch = height ?? (500 / 750 * cw);
return { cWidth: cw, cHeight: ch, actualPixelRatio: pr };
}, [width, height, pixelRatio]);
// Canvas props
const canvasProps = useMemo(() => {
if (Taro.getEnv() === Taro.ENV_TYPE.ALIPAY) {
return {
width: cWidth,
height: cHeight,
style: { width: '100%', height: '100%' }
};
}
return {
style: { width: `${cWidth}px`, height: `${cHeight}px` }
};
}, [cWidth, cHeight]);
// 初始化图表
useEffect(() => {
const ctx = Taro.createCanvasContext(canvasId);
chartRef.current = new uCharts({
type,
context: ctx,
categories,
series,
width: cWidth,
height: cHeight,
pixelRatio: actualPixelRatio,
...config,
});
return () => {
// 清理图表实例
chartRef.current = null;
};
}, [canvasId, type, categories, series, cWidth, cHeight, actualPixelRatio, config]);
// 事件处理
const handleTouchStart = (e: any) => {
onTouchStart?.(e);
};
const handleTouchMove = (e: any) => {
onTouchMove?.(e);
};
const handleTouchEnd = (e: any) => {
onTouchEnd?.(e);
};
return (
<Canvas
canvas-id={canvasId}
id={canvasId}
{...canvasProps}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
/>
);
};
export default BaseChart;
// src/components/ColumnChart.tsx
import React from 'react';
import { BaseChart, BaseChartProps } from './BaseChart';
export interface ColumnChartProps extends Omit<BaseChartProps, 'type'> {
// 柱状图特有配置
dataLabel?: boolean;
columnType?: 'group' | 'stack';
}
export const ColumnChart: React.FC<ColumnChartProps> = (props) => {
const { dataLabel = true, columnType = 'group', ...baseProps } = props;
const defaultConfig = {
legend: true,
fontSize: 11,
background: '#FFFFFF',
animation: true,
dataLabel,
xAxis: { disableGrid: true },
yAxis: {},
extra: {
column: {
type: columnType,
// width 会在组件内部动态计算
}
}
};
// tooltip 事件处理
const handleTouchStart = (e: any) => {
if (baseProps.onTouchStart) {
baseProps.onTouchStart(e);
}
// 使用 chartRef 调用 showToolTip
};
return (
<BaseChart
{...baseProps}
type="column"
config={{ ...defaultConfig, ...baseProps.config }}
onTouchStart={handleTouchStart}
/>
);
};
export default ColumnChart;
类型定义原则:
any 类型(除非必要)React.FC<Props> 类型export 语法导出组件和类型来源: CLAUDE.md
pnpm typecheck 验证类型pnpm build 生成 .d.ts 声明文件所有新组件应创建在:
mini-ui-packages/mini-charts/src/components/mini-ui-packages/mini-charts/src/components/index.tsmini-ui-packages/mini-charts/src/index.ts(需要更新)any 类型(除非必要)完成本故事后,应该满足:
src/components/ 目录下所有文件存在src/index.ts 正确导出所有组件pnpm typecheck 无类型错误pnpm build 成功,自动生成 .d.ts 声明文件以下工作不在本故事范围内:
测试框架: Jest(mini 包使用 Jest,不是 Vitest)
测试要求:
测试命令:
# 运行所有测试
pnpm test
# 运行特定测试
pnpm test --testNamePattern "图表组件"
测试文件位置: tests/components/
| Date | Version | Description | Author |
|---|---|---|---|
| 2025-12-24 | 1.0 | 创建故事文档 | Bob (Scrum Master) |
| 2025-12-24 | 1.1 | 更新状态为 Approved | Bob (Scrum Master) |
| 2025-12-24 | 1.2 | 完成所有任务,状态更新为 Ready for Review | Dev Agent (claude-sonnet) |
此部分由开发代理在实施过程中填写
claude-sonnet
无重大调试问题。
src/components/ 目录,包含所有图表组件pnpm typecheck)pnpm build),生成完整的 .d.ts 文件createCanvasContext mock新增文件:
mini-ui-packages/mini-charts/src/components/BaseChart.tsxmini-ui-packages/mini-charts/src/components/ColumnChart.tsxmini-ui-packages/mini-charts/src/components/LineChart.tsxmini-ui-packages/mini-charts/src/components/CandleChart.tsxmini-ui-packages/mini-charts/src/components/PieChart.tsxmini-ui-packages/mini-charts/src/components/RadarChart.tsxmini-ui-packages/mini-charts/src/components/index.tsmini-ui-packages/mini-charts/tests/components/BaseChart.test.tsxmini-ui-packages/mini-charts/tests/components/ColumnChart.test.tsx修改文件:
mini-ui-packages/mini-charts/src/index.ts - 添加组件导出mini-ui-packages/mini-testing-utils/testing/taro-api-mock.ts - 添加 createCanvasContext mockmini-ui-packages/mini-testing-utils/package.json - 添加 exports 配置此部分由 QA 代理在审查完成后填写