场景:
对于一个大对象(内部有很多属性,并且属性内部嵌套对象),要实现更新
现如今Pinia Store存储了一个大对象,内部提供了更新对象的方法,但是更新方法却只是用assign浅层分配了对象
外部在使用的时候还需要展开内部属性,重新构造一个完整对象。
例如有下面代码:
// cameraStore
updateCameraSettings(objectId: string, cameraSettings: Partial<SceneCameraSettings>, options?: SceneMutationOptions) {
const object = this.objects.find((item) => item.id === objectId)
if (!object?.camera) return
if (this.shouldRecordHistory(options)) {
this.recordHistory()
}
Object.assign(object.camera, cameraSettings)
}
我只想传入我关心的属性,然后单独对这个属性进行深层更新,应该如何设计解决方案呢?
方案设计
方案 1,把 updateCameraSettings 改成“深合并 partial object” (推荐)
// Store更新函数
updateCameraSettings(objectId: string, cameraSettings: DeepPartial<SceneCameraSettings>)
传入:
// 组件使用
sceneStore.updateCameraSettings(id, {
lens: { focalLengthMm: 35 },
})
然后 store 里做递归 deep merge。
优点是:
- 调用方最自然
- 以后再加
sensor、focus、exposure这种嵌套字段都能用 - 不需要给
lens单独写分支
缺点是:
- 需要一个
DeepPartial类型 - 需要一个通用 deep merge 工具
方案 2:保留顶层更新,但新增“路径更新”
// 组件使用
updateCameraSetting(objectId, 'lens.focalLengthMm', 35)
优点是:
- 接口很清晰
- 不用做 deep merge
- 对单字段编辑很直接
缺点是:
- API 变得偏字符串路径
- 类型安全一般,容易写错路径
- 调用体验不如对象式
方案 3:改成 updater 函数
这种思想是传入委托函数,交给Store执行
// 组件使用
updateCameraSettings(objectId, (camera) => {
camera.lens.focalLengthMm = 35
})
优点是:
- 最灵活
- 不需要考虑深合并规则
- 复杂更新很好写
缺点是:
- 调用方要理解“修改器”模式
- 对表单类场景稍微重一点
- 历史记录、可追踪性要处理得更小心
方案1实现
- 创建deepMerge.ts
export type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
}
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
if (value === null || typeof value !== 'object') return false
const prototype = Object.getPrototypeOf(value)
return prototype === Object.prototype || prototype === null
}
// 深层替换
const mergePlainObject = (
target: Record<string, unknown>,
patch: Record<string, unknown>,
) => {
for (const [key, patchValue] of Object.entries(patch)) {
if (patchValue === undefined) continue
const targetValue = target[key]
if (isPlainObject(targetValue) && isPlainObject(patchValue)) {
mergePlainObject(targetValue, patchValue)
continue
}
target[key] = patchValue
}
return target
}
export const mergeDeep = <Target extends Record<string, unknown>>(
target: Target,
patch: DeepPartial<Target>,
): Target => {
mergePlainObject(target, patch as Record<string, unknown>)
return target
}
- 修改使用规则
updateCameraSettings(objectId: string, cameraSettings: DeepPartial<SceneCameraSettings>, options?: SceneMutationOptions) {
const object = this.objects.find((item) => item.id === objectId)
if (!object?.camera) return
if (this.shouldRecordHistory(options)) {
this.recordHistory()
}
mergeDeep(object.camera, cameraSettings)
}

评论(0)
暂无评论