- API 路由统一: 移除 /api/h5/,统一 /api/pms/ 入口 - 二进制改名: api-server -> pms-api(与服务名一致) - nginx: 合并 /api/ + /pms/api/ 为 /api/pms/ - 新增 docs/API.md 接口文档 - 后端: CabinetDetail 增加 rssi/pow_fail/address/soc 等字段 - Redis 管道批量读取仓体实时数据(SOC/电压/电流/功率) - DB: 所有 TIMESTAMP 列改为 DATETIME(修复类型不匹配) - 用户管理: 新增创建时间/修改时间列,状态改用 Switch - 角色管理/用户管理: 操作按钮改为图标 - PC 设备详情: 基本信息补全 RSSI/ICCID/电源/地址 - PC 卡片: 通道统计改用 Lucide 图标,颜色统一 - PC 二维码: canvas 渲染,含编号文字,支持右键下载 - H5: 按 uni-app 规范修正导航(navigateTo 替代 reLaunch) - H5: 详情页加返回导航条 - H5: 扫码支持摄像头(jsQR)+ ?id= 参数 - H5: 底部 tabBar 在所有页面常显 - H5: 首页统计改为 6 项(设备/通道/空闲/充电/充满/故障) - H5: 下拉刷新 / 上拉加载 - 产品首页: 添加业务联系 - 文档: 清理过时文件,补充缺失规范章节
4.3 KiB
4.3 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Deep Watch Callback Receives Same Object Reference for Old and New Values | MEDIUM | Comparing oldValue and newValue in deep watchers is misleading since they reference the same object | capability |
|
Deep Watch Callback Receives Same Object Reference for Old and New Values
Impact: MEDIUM - When using deep watchers on reactive objects, both newValue and oldValue in the callback point to the same object reference. They will always be equal for nested mutations because Vue doesn't clone the object before mutation.
Don't rely on comparing newValue to oldValue in deep watchers for detecting what changed. Instead, track specific values or implement your own diffing.
Task Checklist
- Don't compare newValue === oldValue in deep watchers to detect changes
- For change detection, watch specific properties instead
- If you need old values, manually snapshot before changes
- Consider using a serialization approach for complex diffing needs
- The values differ only when the entire object is replaced
Incorrect:
import { reactive, watch } from 'vue'
const state = reactive({
user: {
name: 'John',
preferences: { theme: 'dark' }
}
})
// BAD: Trying to compare old and new values
watch(
() => state.user,
(newUser, oldUser) => {
// This comparison is ALWAYS true for nested mutations!
if (newUser === oldUser) {
console.log('Same reference!') // Always logs for nested changes
}
// This also won't work - they're the same object
if (newUser.name !== oldUser.name) {
console.log('Name changed') // Never logs for nested mutations
}
},
{ deep: true }
)
// When this happens:
state.user.name = 'Jane'
// Both newUser and oldUser are { name: 'Jane', preferences: { theme: 'dark' } }
Correct:
import { reactive, watch, ref } from 'vue'
const state = reactive({
user: {
name: 'John',
preferences: { theme: 'dark' }
}
})
// CORRECT: Watch specific properties you care about
watch(
() => state.user.name,
(newName, oldName) => {
console.log(`Name changed from "${oldName}" to "${newName}"`)
// oldName and newName are primitives, work correctly
}
)
// CORRECT: Watch multiple specific properties
watch(
[() => state.user.name, () => state.user.preferences.theme],
([newName, newTheme], [oldName, oldTheme]) => {
if (newName !== oldName) {
console.log(`Name: ${oldName} -> ${newName}`)
}
if (newTheme !== oldTheme) {
console.log(`Theme: ${oldTheme} -> ${newTheme}`)
}
}
)
Manual Snapshot Pattern
import { reactive, watch, ref } from 'vue'
const state = reactive({ count: 0, items: [] })
// Keep a manual snapshot for comparison
const previousSnapshot = ref(JSON.stringify(state))
watch(
state,
(newState) => {
const currentSnapshot = JSON.stringify(newState)
if (currentSnapshot !== previousSnapshot.value) {
const oldData = JSON.parse(previousSnapshot.value)
console.log('Old:', oldData)
console.log('New:', newState)
// Update snapshot for next comparison
previousSnapshot.value = currentSnapshot
}
},
{ deep: true }
)
When Old and New Values Differ
import { reactive, watch } from 'vue'
const state = reactive({
currentUser: { name: 'John' }
})
watch(
() => state.currentUser,
(newUser, oldUser) => {
// THESE DIFFER when the object itself is replaced
console.log('Old:', oldUser) // { name: 'John' }
console.log('New:', newUser) // { name: 'Jane' }
},
{ deep: true }
)
// Object replacement - old and new are different
state.currentUser = { name: 'Jane' }
// vs. Mutation - old and new are the same reference
// state.currentUser.name = 'Jane'
Using Getter Returns New Object
import { reactive, watch } from 'vue'
const state = reactive({
user: { firstName: 'John', lastName: 'Doe' }
})
// CORRECT: Getter returns new object, so old/new comparison works
watch(
() => ({ ...state.user }), // Shallow clone
(newUser, oldUser) => {
// Now these are different objects
console.log('Changed from', oldUser, 'to', newUser)
},
{ deep: true }
)