类型脚本实用程序记录和部分不能与受约束的泛型一起使用 [英] Typescript utilities 'Record' and 'Partial' don't work with a constrained generic

查看:0
本文介绍了类型脚本实用程序记录和部分不能与受约束的泛型一起使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要基于受约束的泛型创建新类型。新类型应该与泛型具有相同的键,使它们成为可选的,并将它们映射到一个数字。

我的第一个方法是使用Partial<Record<keyof Entity, number>>,但由于某种原因导致错误Type [...] is not assignable to type 'Partial<Record<keyof Entity, number>>'.

所以我求助于手动编写类型映射{ [K in keyof Entity]?: number },这似乎很管用。

interface Person {
  firstName: string;
  lastName: string;
}

export class SomeClass<Entity extends Person> {
  protected getFilter(): void {
    let prio1: { [K in keyof Entity]?: number };
    prio1 = { firstName: 1 };

    // Type '{ firstName: 2; }' is not assignable to type 'Partial<Record<keyof Entity, number>>'.
    let prio2: Partial<Record<keyof Entity, number>>;
    prio2 = { firstName: 2 };
  }
}

谁能告诉我这两种表示法之间的区别是什么,为什么内置的打字实用程序不能工作?

该代码片段也可在TypeScrip Platform上找到:see here

我使用的资源:RecordsMapped TypesGenerics


更新:

我试图分解TypeScrip在内部的作用,使用官方的类型定义一步一步地对其进行美化:

/**
 * Make all properties in T optional
 */
type Partial<T> = { [P in keyof T]?: T[P] };

/**
 * Construct a type with a set of properties K of type T
 */
type Record<K extends keyof any, T> = { [P in K]: T };


class SomeClass<Entity extends Person> {
  getFilter(): void {
    let prio1: { [K in keyof Entity]?: number };
    prio1 = { firstName: 1 };

    let p1: Partial<Record<keyof Entity, number>>;
    let p2: Partial<{ [P in keyof Entity]: number }>;
    let p3: { [P2 in keyof { [P1 in keyof Entity]: number }]?: { [P1 in keyof Entity]: number }[P2] };
    let p4: { [P2 in keyof Entity]?: { [P1 in keyof Entity]: number }[P2] };
    let p5: { [P2 in keyof Entity]?: number };

    p1 = { firstName: 3 }; // error
    p2 = { firstName: 3 }; // error
    p3 = { firstName: 3 }; // error
    p4 = { firstName: 3 }; // error
    p5 = { firstName: 3 }; // works
  }
}

TypeScrip似乎无法将{ [P1 in keyof Entity]: number }[P2]P2 in keyof Entity解析为number

推荐答案

首先请看这个answer。它应该会为您提供一些背景信息。

Entity视为Person的子类型。它不仅有Person道具,还可能有其他道具。

请参见此示例:

interface Person {
    firstName: string;
    lastName: string;
}

type User = {
    premium: true
} & Person

UserPerson的子类型,也就是扩展了Person

让我们创建用户对象:

interface Person {
    firstName: string;
    lastName: string;
}

type User = {
    premium: true
} & Person

let user: Record<keyof User, number> = {
    firstName: 1,
    lastName: 2,
    premium: 3
}

因此,我们的Partial用户可能有很多状态:

// just an alias
type PartiaUser = Partial<Record<keyof User, number>>

let partialUser1: PartiaUser = {}
let partialUser2: PartiaUser = { firstName: 1 }
let partialUser3: PartiaUser = { firstName: 1, lastName: 2, }
let partialUser4: PartiaUser = { lastName: 2, }
let partialUser5: PartiaUser = { lastName: 2, premium: 3, }

参见partialUser5它是一个没有firstName属性的对象。

让我们回到您的示例:

interface Person {
    firstName: string;
    lastName: string;
}

export class SomeClass<Entity extends Person> {
    getFilter(): void {
        let prio1: { [K in keyof Entity]?: number };
        prio1 = { firstName: 1 };

        let prio2: Partial<Record<keyof Entity, number>>;
        prio2 = { firstName: 2 };
    }
}

prior1之所以有效,是因为在这种情况下,prior1肯定具有可选的firstName属性,因为Entity具有Person的所有键。

prior2非常有趣。

我认为问题出在Java脚本的动态性上。这只是我的观点。

请参见此示例:

type User = {
    firstName: string;
    lastName: string;
    premium: true
} & string

type IsExtends<T> = T extends Person ? true : false

type Test = IsExtends<User>

问题是上述User类型仍然扩展Person

interface Person {
    firstName: string;
    lastName: string;
}

type User = {
    firstName: string;
    lastName: string;
    premium: true
} & string

export class SomeClass<Entity extends Person> {
    getFilter(): void {
        let prio1: { [K in keyof Entity]?: number };
        prio1 = { firstName: 1 };

        let prio2: Partial<Record<keyof Entity, number>>;
        prio2 = { firstName: 2 };
    }
}
const result = new SomeClass<User>() // no error

事实上,User是一个带有一些静态属性的字符串。

它在类型系统中可表示,但在运行时不可表示。

让我们用一个小例子进行测试:

type PartialUser = Partial<Record<keyof User, number>>
declare var partialUser: PartialUser

partialUser = { firstName: 2 } // error
在这种情况下,问题出在toString方法中。请看一下toString是如何在string原语中实现的:

// toString: () => string;
type ToString = Pick<{
    [Prop in keyof string]: string[Prop]
}, 'toString'>

您可能已经注意到,toString是一个函数。我的意思是toString的值是一个函数。但是Record<keyof User, number>预期每个值都是number

让我们再来看看我们的示例:

interface Person {
    firstName: string;
    lastName: string;
}

type User = {
    firstName: string;
    lastName: string;
    premium: true;
    toString: number;
}

export class SomeClass<Entity extends Person> {
    getFilter(): void {
        let prio1: { [K in keyof Entity]?: number };
        prio1 = { firstName: 1 };
        // added User for experimenting with prio2 = { firstName: 2, toString: 42 }
        let prio2: Partial<Record<keyof User, number>>;
        prio2 = { firstName: 2 };
    }
}
const result = new SomeClass<User>() // no error

为清楚起见,我已显式添加了toString属性。

prio1 = { firstName: 1 }-之所以有效,是因为我们创建了新类型,其中每个值都是number

由于我们已覆盖内置方法,因此TypeScrip声明此方法与默认方法不兼容toString

尝试使用prio2 = { firstName: 2, toString: 42 }。您将看到它将修复错误。

默认情况下,TS期望每个对象都有toString作为方法,这是预期的行为。但如果您覆盖它-它会使TS不高兴

我希望现在清楚您为什么会出错。

extends-不表示-equal

Playground

Related github issue

这篇关于类型脚本实用程序记录和部分不能与受约束的泛型一起使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆