首页 > 解决方案 > TS2322:泛型未正确传播

问题描述

我们使用JSON:API作为 API 的主要序列化模式。data在不深入细节的情况下,它要求来自服务器的 JSON 响应具有可能包含单个实体实体数组的顶级属性:

// 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>;
}

响应始终包含一个属性,该data属性可以是单个资源对象,也可以是资源对象的集合。
资源对象始终包含一个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函数从返回单个实体的端点获取响应,例如/api/cats/42. 因此,通过传递Cat接口,数据类型正确地解析为单个资源对象:

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

我们还可以定义一个返回多个实体的函数:

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

此函数返回 s 的集合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;
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}

类型 'Record<string, any>' 不可分配给类型 'T'。
'Record<string, any>' 可分配给类型'T' 的约束,但'T' 可以用约束'Record<string, any>' 的不同子类型来实例化。(2322)

为什么 Typescript 理解single函数返回 T 的资源对象,而不是singleAttributes?在某个地方,它似乎放弃了泛型类型,转而支持 的基本类型Attributes,但我不明白为什么或在哪里。

查看操场链接以获取该问题的演示。

标签: typescriptjson-api

解决方案


返回类型

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
}

类型 'Record<string, any>' 不可分配给类型 'T'。“Record<string, any>”可分配给“T”类型的约束,但“T”可以用约束“Record<string, any>”的不同子类型来实例化。

T可分配给返回值,但返回值不可分配给T. 这就是为什么您可以将T其用作返回类型的显式类型

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
}

或删除显式返回类型

我想说这Record<string, any>几乎是一样的,Record<number, any>因为它将根据 js 规范推断为字符串。

在这里你有很好的解释

这是按预期工作的,是 #16368 中引入的更严格检查的结果。在您的示例中, IMyFactoryType (或接口,它们在结构上是相同的)表示一个函数,该函数应该返回从 IMyObject 派生的任何类型的精确类型值,即使该函数实际上没有任何涉及 T 的参数将允许它发现 T 是什么并创建适当的返回值。换句话说:


推荐阅读