TS2322:通用未正确传播

TS2322: Generic not propagated correctly

我们使用 JSON:API 作为 API 的主要序列化模式。无需详细说明,它要求来自服务器的 JSON 响应具有顶级 data 属性,其中可能包含单个实体 一组实体:

// Single item
{ "data": { "id": 1 } }

// Collection of items
{ "data": [
    { "id": 1 },
    { "id": 2 }
] }

我将此模式编码为 TypeScript 类型,因此我们可以正确键入 API 响应。这产生了以下结果:

// Attributes as the base type for our entities
type Attributes = Record<string, any>;

// Single resource object containing attributes
interface ResourceObject<D extends Attributes> {
    attributes: D;
}

// Collection of resource objects
type Collection<D extends Attributes> = ResourceObject<D>[];

// Resource object OR collection, depending on the type of D
type ResourceObjectOrCollection<D extends Attributes | Attributes[]> = D extends Array<infer E>
    ? Collection<E>
    : ResourceObject<D>;

// A response with a resource object or a collection of items of the same type
interface ApiResponse<T> {
    data: ResourceObjectOrCollection<T>;
}

一个response always contains a data property that may be a single resource object,或资源对象的集合。
资源对象总是包含一个 attributes 属性 来保存任意实体属性。此处所有键入的目的是通过将实体接口作为通用 T 传递来传播属性结构,如以下示例所示:

interface Cat {
    name: string;
}

function performSomeApiCall<Ret>(uri: string): Ret {
    return JSON.parse(''); // Stub, obviously
}

function single<T extends Attributes = Attributes>(uri: string): ApiResponse<T> {
    return performSomeApiCall<ApiResponse<T>>(uri);
}

single 函数从 returns 单个实体的端点获取响应 - 例如,/api/cats/42。因此,通过传递 Cat 接口,数据类型正确地解析为单个资源对象:

const name: string = single<Cat>('/api/cats/42').data.attributes.name;

我们也可以定义一个returns多个实体的函数:

function multiple<T extends Attributes = Attributes>(uri: string): ApiResponse<T[]> {
    return performSomeApiCall<ApiResponse<T[]>>(uri);
}

此函数 returns 是 T 的集合,因此以下内容也有效:

const names: string[] = multiple<Cat>('/api/cats').data.map(item => item.attributes.name);

然而,是定义一个直接检索单个实体的属性的函数,这是我不明白的:

function singleAttributes<T extends Attributes = Attributes>(uri: string): T {
    return single<T>(uri).data.attributes;
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}

Type 'Record<string, any>' is not assignable to type 'T'.
'Record<string, any>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Record<string, any>'. (2322)

为什么 Typescript 理解 single 函数 returns T 的资源对象,而不是 singleAttributes?在某处,它似乎放弃了通用类型以支持 Attributes 的基本类型,但我不明白为什么或在哪里。

查看 the playground link 问题演示。

Return 类型

function singleAttributes<T extends Attributes = Attributes>(uri: string): T {
    return single<T>(uri).data.attributes; < --- Record<string, any>
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}

实际上是Record<string, any>

看下一个例子:

function singleAttributes<T extends Attributes>(uri: string): T {
    let ret = single<T>(uri).data.attributes

    let t: T = null as any;
    ret = t; // ok
    t = single<T>(uri).data.attributes // error
}

Type 'Record<string, any>' is not assignable to type 'T'. 'Record<string, any>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Record<string, any>'.

T 可分配给 return 值,但 return 值不可分配给 T。这就是为什么您可以使用 T 作为 return type

的显式类型

T 可能是更宽的类型。

示例:

type WiderType = Record<string | symbol, any> extends Record<string, any> ? true : false // true


type WiderType2 = Record<string, any> extends Record<string | symbol, any> ? true : false // true

当您期望 Record<stirng, any> 时,T 也可以是 Record<string | symbol,any>。甚至 Record<string | symbol | number, any>

为了让它工作

从一开始就使用更宽的字体:

function singleAttributes<T extends Record<string | symbol | number, any>>(uri: string): T {
    return single<T>(uri).data.attributes
}

或删除显式 return 类型

我会说 Record<string, any>Record<number, any> 几乎相同,因为它将根据 js 规范推断为字符串。

Here你的解释很好

This is working as intended and is a result of the stricter checks introduced in #16368. In your example, the IMyFactoryType (or the interface, they're structurally identical) represents a function that is supposed to return exactly typed values of any type that derives from IMyObject, even though the function doesn't actually have any parameters involving T that would allow it to discover what T is and create an appropriate return value. In other words: