重命名对象/接口的键,保证类型安全。

3

我希望能够使用映射函数动态重命名键。为此,我已经编写了以下代码:

interface DateRange {
    startDate: string;
    endDate: string;
}

function format<K extends string>(range: DateRange, mapping: {[X in keyof DateRange]: K}): {[P in K]: string} {
    return {
        [mapping.startDate]: range.startDate,
        [mapping.endDate]: range.endDate,
    };
}

当我使用 as {[P in K]: string} 来强制转换此函数的返回值时,一切都正常工作,但是如果没有这个强制转换,它就无法编译。
错误信息:
TS2322: Type '{ [x: string]: string; }' is not assignable to type '{ [P in K]: string; }'.

从我看到的情况来看,DateRange接口的两个键都被使用,因此返回值的键都应该是K类型。

如果不需要进行强制转换,函数应该是什么样子的?

调用示例:

const formatted = format(
    {startDate: 'startDateValue', endDate: 'endDateValue'}, 
    {startDate: 'start', endDate: 'end'}
);
// formatted = {end: 'endDateValue', start: 'startDateValue'}
1个回答

1
我可以重现您的问题,并且似乎在GitHub问题中已经被记录过。该问题已被标记为“已解决”,但其中的这部分显然没有。无论如何,这可能只是一种设计限制...... TypeScript表示具有泛型类型参数的对象作为键的唯一方法是使用映射类型,我怀疑TypeScript中的任何内容都会自动将对象字面量解释为映射类型。我不知道是否在GitHub上存在更合适的现有问题,或者是否应该有人打开一个问题。
目前,您必须处理解决方法。如果您小心,您使用的那个方法是可以的(断言返回值的类型)。稍微更安全的方法是逐个添加属性,如下所示:
function format<K extends string>(
  range: DateRange, 
  mapping: { [X in keyof DateRange]: K }
): { [P in K]: string } {
  const ret = {} as { [P in K]: string }; // assertion
  ret[mapping.startDate] = range.startDate; // safer
  ret[mapping.endDate] = range.endDate; // safer
  return ret;
}

它仍然不会抱怨如果你省略一个属性,但它会阻止你添加随机的其他字符串类型的计算属性。
  const badProp: string = "uh oh"
  return {
    [mapping.startDate]: range.startDate,
    [mapping.endDate]: range.endDate,
    [badProp]: "whoops" // no error
  } as { [P in K]: string };

vs

  const badProp: string = "uh oh"
  const ret = {} as { [P in K]: string };
  ret[mapping.startDate] = range.startDate;
  ret[mapping.endDate] = range.endDate;
  ret[badProp] = "whoops"; // error (implicit any)

但是这可能对你来说不值得,而你的类型断言是合理的。希望这有所帮助。祝好运!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接