This commit is contained in:
2025-10-17 20:56:50 +08:00
commit 90094ccd5a
342 changed files with 144988 additions and 0 deletions

View File

@@ -0,0 +1,281 @@
# 紧急修复 - 变量作用域和性能警告 v3.13.5.1
## 📋 问题描述
### 1. 严重错误:`ref(...) is not a function`
**错误信息:**
```
batchTaskStore.js:96 Uncaught (in promise) TypeError: ref(...) is not a function
```
**原因分析(经过两次调试):**
**第一次错误(行 323-325**
- `savedProgress` 变量定义在函数引用之后,导致变量访问顺序问题
**第二次错误(行 96根本原因**
- **JavaScript 自动分号插入ASI问题**
- 代码结构如下:
```javascript
const savedProgress = ref(...)
(() => { ... })()
```
- JavaScript 解析器将其理解为:
```javascript
const savedProgress = ref(...)((() => { ... })())
```
- 也就是说,解析器认为 `ref()` 返回一个函数,然后立即调用它
- 因为 `ref()` 返回的是一个响应式对象而不是函数,所以报错 `ref(...) is not a function`
**技术细节:**
- ASIAutomatic Semicolon Insertion是 JavaScript 的一个特性
- 当一行以 `(` 开头时JavaScript 可能不会自动插入分号
- 这导致前一行和当前行被连在一起解析
- 这是一个经典的 JavaScript 陷阱,特别是在使用 IIFE立即执行函数表达式
### 2. Vue 性能警告
**警告信息:**
```
[Vue warn]: Vue received a Component that was made a reactive object.
This can lead to unnecessary performance overhead and should be avoided
by marking the component with `markRaw` or using `shallowRef` instead of `ref`.
```
**原因分析:**
- `Home.vue` 中的 `featureCards` 和 `features` 数组包含了组件对象PersonCircle、Cube、Ribbon、Settings
- 这些组件被 Vue 的响应式系统包装,导致不必要的性能开销
- 组件本身不需要响应式追踪,应该使用 `markRaw` 标记
## 🔧 修复方案
### 修复 1调整变量定义顺序 + 修复 ASI 问题
**文件:** `src/stores/batchTaskStore.js`
**修改内容:**
1. 将 `savedProgress` 的定义从第 323 行移到第 93 行(在被引用的函数之前)
2. 将初始化 IIFE 也一起移动,确保在 store 初始化早期就完成进度数据的清理
3. **关键修复:** 在 `ref()` 调用和 IIFE 之间添加分号,避免 ASI 问题
**修改前(第 322-344 行):**
```javascript
// 任务执行历史记录最近10次
const executionHistory = ref(...)
// 批量任务执行进度记录(用于刷新后恢复)
const savedProgress = ref(
JSON.parse(localStorage.getItem('batchTaskProgress') || 'null')
)
// 🆕 v3.13.5: 启动时清理过期的进度数据
(() => {
if (savedProgress.value) {
// ...清理逻辑
}
})()
```
**修改后(第 93-115 行):**
```javascript
// 🆕 连接池实例
let wsPool = null
// 批量任务执行进度记录(用于刷新后恢复)
const savedProgress = ref(
JSON.parse(localStorage.getItem('batchTaskProgress') || 'null')
); // ⚠️ 关键:添加分号,避免 ASI 问题
// 🆕 v3.13.5: 启动时清理过期的进度数据
(() => {
if (savedProgress.value) {
const progress = savedProgress.value
const now = Date.now()
const elapsed = now - (progress.timestamp || 0)
const isExpired = elapsed > 24 * 60 * 60 * 1000 // 24小时
if (isExpired) {
console.log(`🧹 [启动清理] 清除过期的进度数据 (${Math.floor(elapsed / 3600000)} 小时前)`)
localStorage.removeItem('batchTaskProgress')
savedProgress.value = null
} else if (progress.completedTokenIds && progress.allTokenIds) {
const remaining = progress.allTokenIds.length - progress.completedTokenIds.length
console.log(`📂 [启动恢复] 发现未完成的进度数据: ${progress.completedTokenIds.length}/${progress.allTokenIds.length} (剩余 ${remaining} 个)`)
}
}
})(); // ⚠️ 也添加分号,保持一致性
// 日志包装函数:根据开关决定是否输出
const batchLog = (...args) => {
if (ENABLE_BATCH_LOGS) {
console.log(...args)
}
}
```
**效果:**
- ✅ `savedProgress` 在被函数引用前就已定义
- ✅ 添加分号明确语句边界,避免 ASI 陷阱
- ✅ 防止 `ref()` 的返回值被误认为是函数并调用
- ✅ 确保 store 初始化过程中的正确执行顺序
### 修复 2使用 markRaw 标记组件
**文件:** `src/views/Home.vue`
**修改内容:**
1. 导入 `markRaw` 函数
2. 使用 `markRaw()` 包装所有组件对象
**修改前:**
```javascript
import { ref, onMounted } from 'vue'
// ...
const featureCards = ref([
{
id: 1,
icon: PersonCircle, // 未标记,会被包装为响应式
title: '角色管理',
description: '统一管理游戏角色'
},
// ...
])
```
**修改后:**
```javascript
import { ref, markRaw, onMounted } from 'vue'
// ...
const featureCards = ref([
{
id: 1,
icon: markRaw(PersonCircle), // 使用 markRaw 标记,不会被响应式包装
title: '角色管理',
description: '统一管理游戏角色'
},
// ...
])
```
**应用范围:**
- `featureCards` 数组中的 3 个组件PersonCircle, Cube, Ribbon
- `features` 数组中的 4 个组件PersonCircle, Cube, Ribbon, Settings
**效果:**
- ✅ 消除 Vue 性能警告
- ✅ 减少不必要的响应式追踪开销
- ✅ 提升渲染性能
## 📊 影响范围
### 文件修改
1. **src/stores/batchTaskStore.js**
- 变量定义位置调整
- 无功能变更,只是顺序优化
2. **src/views/Home.vue**
- 添加 `markRaw` 标记
- 无功能变更,性能优化
### 用户影响
- ✅ 修复了启动时的严重错误
- ✅ 消除了控制台警告
- ✅ 提升了页面渲染性能
- ✅ 不影响任何现有功能
## 🧪 测试建议
### 1. 基本功能测试
- [ ] 启动应用,确认没有错误信息
- [ ] 访问首页,确认无 Vue 警告
- [ ] 检查控制台,确认无 `ref(...) is not a function` 错误
### 2. 批量任务测试
- [ ] 执行批量任务,任务中断后刷新页面
- [ ] 确认进度恢复功能正常
- [ ] 检查进度数据过期清理是否正常工作
### 3. 性能观察
- [ ] 观察首页渲染速度
- [ ] 检查浏览器内存占用
- [ ] 确认组件图标正常显示
## 📝 技术说明
### markRaw 的作用
`markRaw()` 是 Vue 3 提供的工具函数,用于标记一个对象,使其永远不会被转换为响应式对象。
**适用场景:**
1. 组件定义对象(如本次修复)
2. 大型不可变数据结构
3. 第三方库实例
4. DOM 元素引用
**优势:**
- 减少响应式系统的追踪开销
- 避免不必要的深度响应式转换
- 提升性能,特别是在处理大量数据时
### JavaScript ASI自动分号插入陷阱
这是一个经典的 JavaScript 陷阱,值得所有开发者注意:
**问题场景:**
当一行以 `(`, `[`, `` ` ``, `+`, `-`, `/` 等符号开头时JavaScript 可能不会在上一行末尾自动插入分号。
**危险示例:**
```javascript
// ❌ 错误:会被解析为 fn1()(fn2())
const result = fn1()
(fn2())
// ❌ 错误:会被解析为 arr[0]
const arr = [1, 2, 3]
[0].forEach(...)
// ✅ 正确:显式添加分号
const result = fn1();
(fn2())
// ✅ 正确:或使用分号开头(防御性编程)
const result = fn1()
;(fn2())
```
**在本项目中的体现:**
```javascript
// ❌ 会被解析为: ref(...)((() => {})())
const savedProgress = ref(...)
(() => {})()
// ✅ 正确:添加分号
const savedProgress = ref(...);
(() => {})()
```
**最佳实践:**
1. **使用一致的分号风格**:要么总是加,要么总是不加(配合 linter
2. **IIFE 前加分号**:如果不用分号风格,在 IIFE 前加 `;` 保护
3. **使用 ESLint**:配置 `semi` 规则强制统一风格
4. **代码审查**:特别注意以 `(` 或 `[` 开头的行
### 变量定义顺序的重要性
在 Pinia setup store 中,虽然闭包允许函数引用后面定义的变量,但:
1. `computed` 属性可能在定义时就触发计算
2. IIFE立即执行函数会在定义时立即执行
3. 某些情况下可能导致访问未初始化的变量
**最佳实践:**
- 将被多处引用的状态变量定义在前面
- 确保在使用前完成初始化
- 特别注意 `computed` 和 IIFE 的执行时机
- 在 `ref()` 等返回对象的调用后,如果紧跟 IIFE必须加分号
## 🎯 版本信息
- **版本号:** v3.13.5.1
- **修复类型:** 紧急修复
- **优先级:** 🔴 高(阻塞性错误)
- **向后兼容:** ✅ 完全兼容
## 📌 相关文档
- [v3.13.5 - 内存清理机制优化](./内存清理机制优化v3.13.5.md)
- [Vue 3 - markRaw API 文档](https://vuejs.org/api/reactivity-advanced.html#markraw)
- [Pinia - Setup Stores](https://pinia.vuejs.org/core-concepts/#setup-stores)