根据任意属性名称复制值



我有一个类型A,它有几个属性,可以有不同的键:字符串,数字,类型B,不相关。我想将某些字段从对象 a1 的类型 A 复制到对象 a2,这也是类型 A,但我事先不知道我要复制哪些字段。我要复制的字段出现在一个字符串文本数组中,这些字符串文本告诉我要复制哪个字段。

type A = {
name: string,
length: number,
thing: B
}
const a1: A = {
name: "test",
length: 2,
thing: { whatever: true },
}
const a2: A = {
name: "",
length: 0,
thing: {whatever: false}
}
const propNames = ["name", "length"]
propNames.map(propName => a2[propName] = a1[propName as keyof A])

我将不胜感激任何可能的帮助,这是沙盒上的示例:

https://codesandbox.io/s/typescript-playground-export-fjol9

如果你写const arr = ["some", "strings"]编译器会推断出arrstring[]类型,它会立即忘记其内容的确切数量和值。 这通常是正确的行为,因为人们确实倾向于改变数组的内容(即使是const数组的内容仍然可以具有arr[0]="assigned";arr.push("pushed");(。

如果希望编译器跟踪数组文本中的特定字符串文本值类型,则需要更改声明变量的方式。 最简单的方法,const断言,是在TypeScript 3.4中引入的:

const arr = ["name", "length"] as const;

现在已知arr的类型是一个长度为 2 的数组,其第一个元素必须"name",其第二个元素必须"length"。 由此,如果你写arr.map(propName => ...),编译器就会知道propName一定是"name" | "length"的。


如果你解决了这个问题,你将面临的第二个问题是,假设你使用的是TypeScript 3.5或更高版本:

arr.map(propName => a2[propName] = a1[propName]); // error!
//  --------------> ~~~~~~~~~~~~
// Type 'string | number' is not assignable to type 'never'.

这里的问题是编译器认为赋值可能不安全,因为它无法验证a2[propName]是否设置了与您从a1[propName]读取的完全相同的属性。这对我们来说很明显,因为变量propName是相同的,但编译器看到的只是这些访问使用相同的键类型,这是两种可能性的并集,有四种可能的结果,如下所示:

const propName2 = arr[Math.random() < 0.5 ? 0 : 1];
const propName1 = arr[Math.random() < 0.5 ? 0 : 1];
a2[propName2] = a1[propName1]; // same error!

propName是并集类型时,唯一可以写入a2[propName]的是所有可能的属性类型的交集。 在这种情况下,这既是string又是number,或者string & number,这是不可能的,因此never

过去我建议,请参阅 microsoft/TypeScript#25051 一种可能的方法,要求编译器测试propName的每个可能值以验证它是安全的,但这不太可能发生。


幸运的是,有一种方法可以在这里继续:使用通用回调来map()

arr.map(
<K extends typeof arr[number]>(propName: K) => (a2[propName] = a1[propName])
);

在这种情况下,编译器会看到您正在将类型A[K]的值分配给类型A[K]的值。 这在理论上与以前的代码一样"不安全",但它是编译器允许的,并且很可能继续如此。 这就是我建议你继续的方式。


好的,希望有帮助;祝你好运!

操场链接到代码

  1. 确定一个包含所有要复制的键/值的对象。
  2. 对要复制的每个属性使用reduce,从空对象开始。
  3. 将目标对象与要覆盖的值组合在一起。
function copyPropertiesOver<E>(properties: string[], source: E, target: E): E {
const valuesToBeCopied = arr.reduce(
(acc, curr) => ({ ...acc, curr: source[curr as keyof E] }),
{}
);
const targetUpdated = ({ ...target, ...valuesToBeCopied } as unknown) as E;
return targetUpdated;
}
const a2Updated = copyPropertiesOver<A>(arr, a1, a2);
console.log(a2Updated);

操场

我最终使用的是对我的 a1 和 a2 对象进行A & Record<string, any>,因为这是最优雅且对我来说可以理解的解决方案。

const a1 = {
name: "test",
length: 2,
thing: { whatever: true }
} as A & Record<string, any>;
const a2 = {
name: "",
length: 0,
thing: { whatever: false }
} as A & Record<string, any>;
arr.filter(propName => propName in a2).forEach(propName => (a2[propName] = a1[propName]));

当然,在实际的生产解决方案中,我使用in类型运算符首先检查字符串是否是对象中的实际字段名称。if("value" in a2)等。

最新更新