“Type could be instantiated with a different subtype”错误,使用扩展和剩余部分。

huangapple go评论73阅读模式
英文:

"Type could be instantiated with a different subtype" error using spread and rest

问题

以下是翻译好的代码部分:

type DocumentReference<T extends {} = {}> = T & {
	id: number;
};

type MappableDocumentReference<T extends {} = {}> = T & {
	id: string;
};

export const mapFromDocumentReference = <T extends {}>({
	id,
	...rest
}: DocumentReference<T>): MappableDocumentReference<T> => ({
	...rest,
	id: `${id}`,
});
Type 'Omit<DocumentReference<T>, "id"> & { id: string; }' is not assignable to type 'MappableDocumentReference<T>'.
  Type 'Omit<DocumentReference<T>, "id"> & { id: string; }' is not assignable to type 'T'.
    'Omit<DocumentReference<T>, "id">' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.(2322)

希望这对你有所帮助。如果你需要进一步的信息或有其他问题,请随时提出。

英文:

I'm trying to map an object such that the object stays the same except one property is mapped to a different type, but I'm having an issue with using spread and rest with generic types. Code and error below and playground here.

type DocumentReference&lt;T extends {} = {}&gt; = T &amp; {
	id: number;
};

type MappableDocumentReference&lt;T extends {} = {}&gt; = T &amp; {
	id: string;
};

export const mapFromDocumentReference = &lt;T extends {}&gt;({
	id,
	...rest
}: DocumentReference&lt;T&gt;): MappableDocumentReference&lt;T&gt; =&gt; ({
	...rest,
	id: `${id}`,
});
Type &#39;Omit&lt;DocumentReference&lt;T&gt;, &quot;id&quot;&gt; &amp; { id: string; }&#39; is not assignable to type &#39;MappableDocumentReference&lt;T&gt;&#39;.
  Type &#39;Omit&lt;DocumentReference&lt;T&gt;, &quot;id&quot;&gt; &amp; { id: string; }&#39; is not assignable to type &#39;T&#39;.
    &#39;Omit&lt;DocumentReference&lt;T&gt;, &quot;id&quot;&gt; &amp; { id: string; }&#39; is assignable to the constraint of type &#39;T&#39;, but &#39;T&#39; could be instantiated with a different subtype of constraint &#39;{}&#39;.(2322)

I understand that the error is saying T can be defined as a narrower type than {} and the value being returned may not conform to that narrower type. But when using spread and rest I assumed that would cover narrower types since the properties of the argument would be spread into the returned value. In theory this is ensuring T is conformed to. So I don't understand why I still get the error.

I can make this work by removing the annotation for the return type and letting TS infer the return type which is good enough for my use case. But I'd like to know why this doesn't work with the annotation.

答案1

得分: 2

问题在于 TypeScript 无法保证映射的对象仍然符合原始类型 T。这是因为扩展操作符(...rest)不会保留类型信息,也就是 TypeScript 无法保证从 rest 中扩展的属性仍然满足 T 的约束条件。

在您的情况下,您试图返回一个包含 TMappableDocumentReference&lt;T&gt;。然而,TypeScript 无法保证 { ...rest, id: ${id} } 对象仍然是 T,因为扩展操作符可能不包括 T 的所有属性(或可能包括不在 T 中的附加属性)。

修复这个问题的一种方法是将返回的对象强制转换为 MappableDocumentReference&lt;T&gt;,但这不是类型安全的,如果可能的话应该避免使用:

export const mapFromDocumentReference = &lt;T extends {}&gt;({
    id,
    ...rest
}: DocumentReference&lt;T&gt;): MappableDocumentReference&lt;T&gt; =&gt; ({
    ...rest,
    id: `${id}`,
} as MappableDocumentReference&lt;T&gt;);

更好的解决方案是重新设计您的类型,使得 T 只包含在从 DocumentReference&lt;T&gt;MappableDocumentReference&lt;T&gt; 映射时要保留的属性。这样,TypeScript 可以保证映射的对象仍然符合 TT 只包含在映射时要保留的属性,而 id 是与 T 分开的。这样可以确保 TypeScript 仍然能够保证映射的对象符合 T

type DocumentReference&lt;T extends {} = {}&gt; = {
    data: T;
    id: number;
};

type MappableDocumentReference&lt;T extends {} = {}&gt; = {
    data: T;
    id: string;
};

export const mapFromDocumentReference = &lt;T extends {}&gt;({
    id,
    data
}: DocumentReference&lt;T&gt;): MappableDocumentReference&lt;T&gt; =&gt; ({
    data,
    id: `${id}`,
});
英文:

The issue here is that TypeScript is unable to guarantee that the mapped object will still conform to the original type T. This is because the spread operator (...rest) is not type-preserving, meaning that TypeScript cannot guarantee that the properties spread from rest will still satisfy the constraints of T.

In your case, you're trying to return a MappableDocumentReference&lt;T&gt;, which includes T. However, TypeScript can't guarantee that the { ...rest, id: ${id} } object will still be a T, because the spread operator might not include all properties of T (or might include additional properties that are not in T).

One way to fix this is to cast the returned object to MappableDocumentReference&lt;T&gt;, but this is not type-safe and should be avoided if possible:

export const mapFromDocumentReference = &lt;T extends {}&gt;({
    id,
    ...rest
}: DocumentReference&lt;T&gt;): MappableDocumentReference&lt;T&gt; =&gt; ({
    ...rest,
    id: `${id}`,
} as MappableDocumentReference&lt;T&gt;);

A better solution would be to refactor your types so that T only includes the properties that you want to preserve when mapping from DocumentReference&lt;T&gt; to MappableDocumentReference&lt;T&gt;. This way, TypeScript can guarantee that the mapped object will still conform to T. T only includes the properties that you want to preserve when mapping, and id is separate from T. This allows TypeScript to guarantee that the mapped object will still conform to T.

type DocumentReference&lt;T extends {} = {}&gt; = {
    data: T;
    id: number;
};

type MappableDocumentReference&lt;T extends {} = {}&gt; = {
    data: T;
    id: string;
};

export const mapFromDocumentReference = &lt;T extends {}&gt;({
    id,
    data
}: DocumentReference&lt;T&gt;): MappableDocumentReference&lt;T&gt; =&gt; ({
    data,
    id: `${id}`,
});

huangapple
  • 本文由 发表于 2023年7月28日 02:02:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/76782356.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定