合并打字稿记录或类似字典的键入与固定键键入?

时间:2019-08-06 08:25:14

标签: typescript

是否可以扩展内置的Record(或{[key:string]:string}接口),在其中您还可以定义一些固定键及其类型?

让我们说这个:

const otherValues = {
    some: 'some',
    other: 'other',
    values: 'values',
}

const composedDictionary = {
    id: 1,
    ...otherValues
}

我想为compositionDictionary定义一个接口,其中id的类型为number(仅数字),其他所有类型的类型为string

我已经尝试过了:

interface ExtendedRecord extends Record<string, string> {
    id: number;
}

还有:

interface MyDictionary {
    [key: string]: string;
    id: number;
}

均失败:

Property 'id' of type 'number' is not assignable to string index type 'string'

有什么想法吗?

2 个答案:

答案 0 :(得分:1)

理想情况下,索引签名应反映任何可能的索引操作结果。如果使用不可检查的字符串键访问composedDictionary,则如果number实际上是string(例如:'id'),则结果可能是composedDictionary['id' as string],键入内容将显示是string,但在运行时却是number)。这就是类型系统在此问题上与您抗争的原因,这是不一致的类型。

您可以定义索引以使所有属性保持一致:

interface MyDictionary {
    [key: string]: string | number;
    id: number;
}

打字稿检查索引和属性的一致性存在漏洞。环形孔是交叉点类型:

type MyDictionary  = Record<string, string> & {
  id: number
}


const composedDictionary: MyDictionary = Object.assign({
    id: 1,
}, {
    ...otherValues
});

编译器仍然会在分配上与您抗争,在类型系统中创建此类不一致对象的唯一方法是使用Object.assign

答案 1 :(得分:1)

正如另一个答案所说,TypeScript不支持其中某些属性是索引签名的 exceptions 的类型,因此无法将您的MyDictionary表示为一致的具体类型。不一致的相交解决方案({[k: string]: string]} & {id: number})可以用于属性读取,但是很难用于属性写入。


有一个旧的suggestion允许“其余”索引签名,在这里您可以说索引签名应该表示除指定属性以外的所有属性

还有一些更新(但可能被搁置)的实现negated typesarbitrary key types for index signatures的增强功能,它们将allow you to represent such exception/default index signature properties{ id: number; [k: string & not "id"]: string }一样。但这还不能编译(TS3.5),并且可能永远也不会编译,因此这只是现在的梦想。


因此您不能将MyDictionary表示为具体类型。但是,您可以 将其表示为generic constraint。突然使用它要求您所有以前的具体函数必须成为通用函数,并且您先前的具体值必须成为通用函数的输出。因此,它可能是太多的机械,而不是值得的。尽管如此,让我们看看如何做到这一点:

type MyDictionary<T extends object> = { id: number } & {
  [K in keyof T]: K extends "id" ? number : string
};

在这种情况下,MyDictionary<T>采用候选类型T,并将其转换为与所需MyDictionary类型相匹配的版本。然后,我们使用以下帮助函数来检查是否匹配:

const asMyDictionary = <T extends MyDictionary<T>>(dict: T) => dict;

注意自引用约束T extends MyDictionary<T>。因此,这是您的用例及其工作方式:

const otherValues = {
  some: "some",
  other: "other",
  values: "values"
};

const composedDictionary = asMyDictionary({
  id: 1,
  ...otherValues
}); // okay

该编译没有错误,因为asMyDictionary()的参数是有效的MyDictionary<T>。现在让我们看看一些失败的地方:

const invalidDictionary = asMyDictionary({
    id: 1,
    oops: 2 // error! number is not a string
})

const invalidDictionary2 = asMyDictionary({
    some: "some" // error! property id is missing
})

const invalidDictionary3 = asMyDictionary({
    id: "oops", // error! string is not a number
    some: "some" 
})

编译器会捕获所有这些错误,并告诉您问题出在哪里。


因此,从TS3.5开始,这是我所能获得的最接近的功能。好的,希望能有所帮助;祝你好运!

Link to code