我有一个类型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"]
编译器会推断出arr
是string[]
类型,它会立即忘记其内容的确切数量和值。 这通常是正确的行为,因为人们确实倾向于改变数组的内容(即使是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]
的值。 这在理论上与以前的代码一样"不安全",但它是编译器允许的,并且很可能继续如此。 这就是我建议你继续的方式。
好的,希望有帮助;祝你好运!
操场链接到代码
- 确定一个包含所有要复制的键/值的对象。
- 对要复制的每个属性使用
reduce
,从空对象开始。 - 将目标对象与要覆盖的值组合在一起。
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)
等。