如何将对象值与 Lodash 合并(array.push 方法)?

How can I merge object values with Lodash (array.push method)?

我遇到以下情况,有两个稍微不同的对象:

const primary: PrimaryInterface = {
  numbers: [1,2,3,4], // <= 5
  other_numbers: [4,5,6,7], // <= 8
  //tons of other array fields to merge
}

const child: ChildInterface = {
  numbers: 5, //but be any number, even non unique
  other_numbers: 8
  //other fields, which have the same element stucture
}

现在我想合并 child 个值,最多 primary 个对象,作为 primary[key].push(child[key])

听起来很简单,我编写了自己的函数来负责那种合并。但是由于我将我的代码重构为打字稿,我在直接通过键访问对象值时遇到了问题(通过对象[键],这个问题说明了原因:)。

因此,与其重写我几乎所有的代码,我想,可能 Lodash(或任何其他熟悉的 object/array 操作库)及其方法可能会有用。我在 Lodash 中找到了 merge 方法,它对我有用吗?

TypeScript 中的对象类型是 open 从某种意义上说,如果你有一个类型(比如){a: string, b: number} 的值,而你可以确定会有string 属性 在键 "a"number 属性 在键 "b", 你不能确定会有不是 "a""b" 以外的键的属性。这种开放性允许 interfaceclass 扩展之类的东西,其中子接口和子-classes 可以比它们的父接口和 classes 拥有更多的属性,而不会违反父接口或 class 类型的约定。TypeScript 没有“精确类型”(如microsoft/TypeScript#12936) where you can say that Exact<{a: string, b: number}> is like {a: string, b: number} but is known to have no other properties but "a" and "b". So unless you add a string index signature to your types, the compiler will balk at the suggestion that the keys of an object will be limited to the ones it knows about. See 在这里可能是规范的答案。安全的方法是迭代已知键的硬编码数组,如 ["numbers", "other_numbers", /* etc */].


但是如果您已经有 JavaScript 代码迭代对象属性,例如 Object.keys()Object.entries(),并且它对您有效,那么您已经 运行 这种风险 JavaScript 没有明显的危害。如果你想继续这样做而不是重写你的代码,那么你总是可以使用 type assertions 告诉编译器虽然你理解它对代码安全的关注,但你确信它足够安全为了你的目的。例如:

function merge<T>(primary: { [K in keyof T]: Array<T[K]> }, child: T) {

  // the following type assertion is not always safe,
  // since primary may in fact have keys not known to the compiler
  // but we will assume that it doesn't
  const childKeys = Object.keys(child) as Array<keyof T>;

  childKeys.forEach(<K extends keyof T>(k: K) => primary[k].push(child[k]))

}

这个 merge() 的实现是一个 generic 函数,它按照您描述的方式工作。 childKeys 处的类型断言是告诉编译器你知道 Object.keys(child)string 的数组,但你会将其视为 keyof T 的数组并面对任何错误的后果都可能发生。

您也可以这样做:

function merge2<T>(primary: { [K in keyof T]: Array<T[K]> }, child: T) {
  for (const k in child) {
    primary[k].push(child[k]);
  }
}

这里编译器实际上做了相同的(有时是不合理的)假设,你可能会做出 for (const k in child) 将只迭代在类型 T 中找到的键。所以你根本不需要任何类型断言。


这个答案的重要部分是类型断言没问题。编译器并不总是知道您对代码的了解。有时编译器是错误的,类型断言可以解决这个问题。有时它在技术上是正确的,但它引发的问题不太可能出现在你的代码中,而不是重写你的代码,类型断言可以用来平息抱怨。在任何一种情况下,有时您都希望编译器专注于代码的其他部分,而不是警告实际上没有问题的部分。在这种时候,您最好坐下来认真思考您正在做的事情,以确保某个断言是有根据的。如果是这样,那就去吧!

Playground link to code