typescript - TS2322:泛型未正确传播
我们使用 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>;
}
一个响应总是包含一个 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);
}
此函数返回一个 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
' 不能赋值给类型 'T'.
'Record' 可以赋值给类型 'T' 的约束,但 'T' 可能会被实例化为约束 'Record ' 的不同子类型。(2322)
为什么 Typescript 能理解 single 函数返回的是 T 的资源对象,但对 singleAttributes 却不能?在某些地方,它似乎放弃了泛型类型,而使用了 Attributes 的基础类型,但我不明白原因或发生的地方。
查看游乐场链接以获取问题演示。
答案#1
返回类型
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
' 不能分配给类型 'T'。'Record ' 可分配给类型 'T' 的约束,但 'T' 可能会被实例化为约束 'Record ' 的不同子类型。
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> 几乎是一样的,因为根据 JavaScript 规范,它会被推断为字符串。
这里你有很棒的解释
这是按照预期工作的,是 #16368 中引入的更严格检查的结果。在你的示例中,IMyFactoryType(或接口,它们在结构上是相同的)表示一个函数,该函数应该返回从 IMyObject 派生的任何类型的精确定义的值,尽管该函数实际上没有涉及 T 的任何参数,因此无法确定 T 是什么并创建相应的返回值。换句话说:
