注释类型时打字稿映射类型错误

Typescript mapped type error when type is annotated

不确定标题是否明确(很难用一句话描述这个问题)但这里有一些代码可以说明这个问题(代码块后有更多关于该问题的详细信息):

type AttributeDefinition = {
    name:string
    type: 'string' | 'number',

}
type EntityDefinition = {
    attributes: Readonly<AttributeDefinition[]>
}

export type Entity<ET extends EntityDefinition> = {
    [K in ET['attributes'][number] as K['name']]:
    K['type'] extends 'string' ? string :
    K['type'] extends 'number' ? number :
    never
}

const def1 = {
    attributes: [
        {name:'foo', type: 'string'},
        {name:'baz', type: 'number'},
    ] as const
}

const def2: EntityDefinition = {
    attributes: [
        {name:'foo', type: 'string'},
        {name:'baz', type: 'number'},
    ] as const
}

const entity1: Entity<typeof def1> = {
    foo: 'bar',
    baz: 42
}

const entity2: Entity<typeof def2> = {
    foo: 'bar',
    baz: 42
}

所以我的问题是,当我想将 'bar' 分配给 foo(以及 42 分配给 baz 时,打字稿会在 entity2 上抛出错误.问题是 TS2322: Type 'string' is not assignable to type 'never'.

但是,entity1 不会出现此问题。

这个问题的根源似乎与我注释EntityDefinition类型的def2的声明有关。我这样做是为了让 IDE 的输入提示帮助我正确声明 def2 object.

但是 def1 object,即使没有类型注释,稍后在创建 entity1 时也会得到正确处理,而 def2(我希望是以正确的方式输入)在用于创建 entity2.

时失败

我不太确定为什么它会以这种方式失败,我如何才能像注释 def2 一样注释 object 并使用这样的 object 后来就像我为 entity1.

所做的那样

谢谢!

我相信即将推出的功能可以解决此特定用例:"satisfies" operator to ensure an expression matches some type (feedback reset) #47920

const def2 = {
    attributes: [
        {name:'foo', type: 'string'},
        {name:'baz', type: 'number'},
    ] as const
} satisfies EntityDefinition;

它仍在开发中,但您可以在 Staging Playground

查看工作示例

进入 TS 之前,您可以使用受约束的标识函数来提供 IntelliSense 帮助,同时在编辑器中物理键入您的定义,同时仍然允许编译器推断其(更具体) 文字属性:

TS Playground

function createEntityDefinition <T extends EntityDefinition>(def: T): T {
  return def;
}

const def2 = createEntityDefinition({
  attributes: [
    {name:'foo', type: 'string'},
    {name:'baz', type: 'number'},
  ] as const,
});

const entity2: Entity<typeof def2> = {
  foo: 'bar',
  baz: 42,
}; // ok 

Entity<typeof def2> 失败的原因是注释加宽了 def2 的类型,这导致细节丢失。 Entity<typeof def2> 的类型等于 Entity<EntityDefinition>,计算结果为 {[x: string]: never}

您或许可以创建一个保留信息的泛型类型注释,但是您必须在常量类型中指定泛型参数,这会导致代码重复。

处理这个问题的方法是创建一个构造函数,它只是 returns 参数但对其有约束:

const mkEntityDefinition = <T extends EntityDefinition>(def: T) => def

你可以这样使用它:

const def3 = mkEntityDefinition({
    attributes: [
        {name:'foo', type: 'string'},
        {name:'baz', type: 'number'},
    ] as const
})

export const entity3: Entity<typeof def3> = {
    foo: 'bar',
    baz: 42
}

它会防止错误:

const defBad = mkEntityDefinition({
    attributes: [ 
        {name:'foo', type: 'strrring'},
        {name:'baz', type: 'number'},
    ] as const
})
// Error: Type '"strrring"' is not assignable to type '"string" | "number"'.
// Did you mean '"string"'?

TypeScript playground