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:
我们使用 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: