- 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.5 KiB
4.5 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Use shallowRef Pattern for External State Libraries | MEDIUM | External state systems (Immer, XState, Redux) should use shallowRef to avoid double-wrapping in proxies | efficiency |
|
Use shallowRef Pattern for External State Libraries
Impact: MEDIUM - When integrating Vue with external state management libraries (Immer, XState, Redux, MobX), use shallowRef() to hold the external state. This prevents Vue from deep-wrapping the external state in proxies, which can cause conflicts and performance issues.
The pattern: hold external state in a shallowRef, and replace .value entirely when the external system updates. This gives Vue reactivity while letting the external library manage state internals.
Task Checklist
- Use
shallowRef()to hold external library state - Replace
.valueentirely when external state changes (don't mutate) - Integrate update functions that produce new state objects
- Consider this pattern for immutable data structures
Integrating with Immer:
import { produce } from 'immer'
import { shallowRef } from 'vue'
export function useImmer(baseState) {
const state = shallowRef(baseState)
function update(updater) {
// Immer produces a new immutable state
// Replace shallowRef value entirely to trigger reactivity
state.value = produce(state.value, updater)
}
return [state, update]
}
// Usage
const [todos, updateTodos] = useImmer([
{ id: 1, text: 'Learn Vue', done: false }
])
// Update with Immer's mutable API (produces immutable result)
updateTodos(draft => {
draft[0].done = true
draft.push({ id: 2, text: 'Use Immer', done: false })
})
Integrating with XState:
import { createMachine, interpret } from 'xstate'
import { shallowRef, onUnmounted } from 'vue'
export function useMachine(options) {
const machine = createMachine(options)
const state = shallowRef(machine.initialState)
const service = interpret(machine)
.onTransition((newState) => {
// Replace state entirely on each transition
state.value = newState
})
.start()
const send = (event) => service.send(event)
onUnmounted(() => service.stop())
return { state, send }
}
// Usage
const { state, send } = useMachine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: { on: { TOGGLE: 'active' } },
active: { on: { TOGGLE: 'inactive' } }
}
})
// In template: state.value.matches('active')
send('TOGGLE')
Integrating with Redux-style stores:
import { shallowRef, readonly } from 'vue'
export function createStore(reducer, initialState) {
const state = shallowRef(initialState)
function dispatch(action) {
state.value = reducer(state.value, action)
}
function getState() {
return state.value
}
return {
state: readonly(state), // Prevent direct mutations
dispatch,
getState
}
}
// Usage
const store = createStore(
(state, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 }
default:
return state
}
},
{ count: 0 }
)
store.dispatch({ type: 'INCREMENT' })
console.log(store.state.value.count) // 1
Why NOT use ref() for external state:
import { ref } from 'vue'
import { produce } from 'immer'
// WRONG: ref() deep-wraps the state
const state = ref({ nested: { value: 1 } })
// This creates double-proxying:
// 1. Vue wraps state in Proxy
// 2. External library may also wrap/expect raw objects
// 3. Causes identity issues and potential conflicts
// WRONG: Mutating ref with Immer
state.value = produce(state.value, draft => {
draft.nested.value = 2
})
// Vue's deep proxy on state.value may interfere with Immer's proxies
Correct pattern with shallowRef:
import { shallowRef } from 'vue'
// CORRECT: shallowRef only tracks .value replacement
const state = shallowRef({ nested: { value: 1 } })
// External library works with raw objects inside
state.value = produce(state.value, draft => {
draft.nested.value = 2
})
// Clean separation: Vue tracks outer ref, library manages inner state