boxmoe_header_banner_img

Hello! 欢迎来到我的博客!

加载中

文章导读

JS大对象深层更新方案


avatar
xiaoifei 2026年8月3日 22

场景:

对于一个大对象(内部有很多属性,并且属性内部嵌套对象),要实现更新
现如今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。
优点是:

  • 调用方最自然
  • 以后再加 sensorfocusexposure 这种嵌套字段都能用
  • 不需要给 lens 单独写分支

缺点是:

  • 需要一个 DeepPartial 类型
  • 需要一个通用 deep merge 工具

方案 2:保留顶层更新,但新增“路径更新”

// 组件使用
updateCameraSetting(objectId, 'lens.focalLengthMm', 35)

优点是:

  • 接口很清晰
  • 不用做 deep merge
  • 对单字段编辑很直接

缺点是:

  • API 变得偏字符串路径
  • 类型安全一般,容易写错路径
  • 调用体验不如对象式

方案 3:改成 updater 函数

这种思想是传入委托函数,交给Store执行

// 组件使用
updateCameraSettings(objectId, (camera) => {
  camera.lens.focalLengthMm = 35
})

优点是:

  • 最灵活
  • 不需要考虑深合并规则
  • 复杂更新很好写

缺点是:

  • 调用方要理解“修改器”模式
  • 对表单类场景稍微重一点
  • 历史记录、可追踪性要处理得更小心

方案1实现

  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
}
  1. 修改使用规则
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)

查看评论列表

暂无评论


发表评论

表情 颜文字
插入代码