5.4 KiB
5.4 KiB
代码简化:使用 data-track 替代 track
Date: 2025-12-05 改进: 使用标准 HTML data 属性简化声明式埋点实现
问题
原始实现使用自定义的 track 属性,导致:
- TypeScript 类型错误:HTML 元素不支持自定义属性
- 复杂的类型转换:需要使用
{...({ track: 'event_name' } as any)} - 代码冗余:每个地方都要写复杂的 spread 语法
Before (复杂写法)
// ToolButton 组件
type ToolButtonBaseProps = {
track?: string;
// ...
};
<button
{...(props.track ? { track: props.track } : {})}
// ... other props
>
// HTML 元素
<div {...({ track: 'toolbar_click_menu' } as any)} />
// MenuItem 组件
<MenuItem {...({ track: 'toolbar_click_menu_save' } as any)} />
解决方案
使用标准的 data-track 属性:
After (简洁写法)
// ToolButton 组件
type ToolButtonBaseProps = {
'data-track'?: string; // ✅ TypeScript 原生支持
// ...
};
<button
data-track={props['data-track']} // ✅ 直接赋值
// ... other props
>
// HTML 元素
<div data-track="toolbar_click_menu" /> // ✅ 简洁清晰
// MenuItem 组件
<MenuItem data-track="toolbar_click_menu_save" /> // ✅ 不需要 as any
优势
1. 标准化
data-*是 HTML5 标准属性- 所有现代浏览器原生支持
- TypeScript 完全支持,无需额外类型定义
2. 代码简化
- Before:
{...({ track: 'event_name' } as any)}(45 字符) - After:
data-track="event_name"(26 字符) - 减少 42% 的代码量
3. 类型安全
- 不需要
as any类型断言 - TypeScript 编译器无警告
- IDE 自动补全支持更好
4. 可读性
// Before - 难以阅读
<MenuItem
icon={SaveFileIcon}
{...({ track: 'toolbar_click_menu_save' } as any)}
onSelect={() => saveAsJSON(board)}
/>
// After - 清晰易读
<MenuItem
icon={SaveFileIcon}
data-track="toolbar_click_menu_save"
onSelect={() => saveAsJSON(board)}
/>
修改的文件
核心服务
- tracking-service.ts - 从
[track]改为[data-track]- Line 116:
target.closest('[data-track]') - Line 118:
getAttribute('data-track')
- Line 116:
组件类型定义
- tool-button.tsx - 添加
'data-track'?: string- Line 22: 添加 data-track 到类型
- Line 130: button 元素使用 data-track
- Line 191: label 元素使用 data-track
Toolbar 组件
-
app-toolbar.tsx - 所有 ToolButton
- Menu:
data-track="toolbar_click_menu" - Undo:
data-track="toolbar_click_undo" - Redo:
data-track="toolbar_click_redo"
- Menu:
-
app-menu-items.tsx - 所有 MenuItem
- Open:
data-track="toolbar_click_menu_open" - Save:
data-track="toolbar_click_menu_save" - Export:
data-track="toolbar_click_menu_export" - Export PNG:
data-track="toolbar_click_menu_export_png" - Export JPG:
data-track="toolbar_click_menu_export_jpg" - Clean:
data-track="toolbar_click_menu_clean" - Settings:
data-track="toolbar_click_menu_settings" - GitHub:
data-track="toolbar_click_menu_github"
- Open:
-
creation-toolbar.tsx - 所有创建工具按钮
- Popover buttons:
data-track={\toolbar_click_${popupKey}`}` - Normal buttons:
data-track={\toolbar_click_${button.pointer || button.key}`}`
- Popover buttons:
-
zoom-toolbar.tsx - 所有缩放按钮
- Zoom out:
data-track="toolbar_click_zoom_out" - Zoom menu:
data-track="toolbar_click_zoom_menu" - Zoom fit:
data-track="toolbar_click_zoom_fit" - Zoom 100%:
data-track="toolbar_click_zoom_100" - Zoom in:
data-track="toolbar_click_zoom_in"
- Zoom out:
-
theme-toolbar.tsx - 主题选择器
- Theme select:
data-track="toolbar_click_theme"
- Theme select:
-
feedback-button.tsx - 反馈按钮
- Feedback:
data-track="toolbar_click_feedback"
- Feedback:
-
TaskToolbarButton.tsx - 任务按钮
- Tasks:
data-track="toolbar_click_tasks"
- Tasks:
文档
- TOOLBAR_TRACKING.md - 更新实现说明
验证
TypeScript 类型检查
npx nx typecheck drawnix
结果: ✅ 所有 track 相关的类型错误已消失
功能测试
- 点击工具栏按钮
- 查看浏览器控制台:
[Tracking] Event tracked: toolbar_click_menu - 检查 Umami 面板: 事件正常上报
性能影响
运行时
- 无影响:
data-*属性和自定义属性在运行时行为完全相同 - DOM 查询:
querySelector('[data-track]')性能等同于querySelector('[track]')
编译时
- 改善: 减少 TypeScript 类型转换,编译更快
- 减少警告: 无
as any断言,代码质量更高
最佳实践
✅ 推荐写法
// 直接使用 data-track
<button data-track="button_click">Click me</button>
// ToolButton 组件
<ToolButton data-track="toolbar_click_save" />
// MenuItem 组件
<MenuItem data-track="menu_item_export" />
❌ 避免的写法
// 不要再使用复杂的 spread 语法
<button {...({ track: 'button_click' } as any)}>Click me</button>
// 不要使用自定义属性
<button track="button_click">Click me</button>
总结
通过使用标准的 data-track 属性:
- ✅ 代码更简洁 - 减少 42% 的代码量
- ✅ 类型更安全 - 无需
as any断言 - ✅ 标准化 - 遵循 HTML5 规范
- ✅ 易维护 - 更清晰、更易读
- ✅ 零性能损失 - 运行时行为完全相同
这是一次成功的代码简化,提高了代码质量和可维护性。