英文:
TypeScript: accept an array of different generics and return a merged type
问题
我试图弄清楚实体组件系统,为了提高可用性,我需要能够传递不同组件定义的数组并获得合并类型作为结果。
我试图做到这一点:
const Position = defineComponent({ x: 0, y: 0 });
const Container = defineComponent({ capacity: 0, contents: new Array<{ item: number; amount: number }>() });
world.withComponents(Position, Container).forEach(([id, entity]) => {
console.log(entity.x);
console.log(entity.capacity);
});
完整代码:
interface ISchema {}
type SimpleType = number | string | boolean;
type Type<V> = V extends SimpleType
? V
: V extends Array<infer RT>
? Array<Type<RT>>
: V extends ISchema
? ComponentType<V>
: never;
type ComponentType<T extends ISchema> = {
[key in keyof T]: Type<T[key]>;
};
type ComponentDefinition<T extends ISchema> = {
id: Symbol;
type: ComponentType<T>;
};
function defineComponent<T extends ISchema>(schema: T): ComponentDefinition<T> {
return {
id: Symbol(),
type: schema as ComponentType<T>,
};
}
type TupleToIntersection<T extends any[]> = {
[I in keyof T]: (x: T[I]) => void;
}[number] extends (x: infer I) => void
? I
: never;
class World {
protected readonly entities = new Map<number, { data: ISchema; components: Set<Symbol> }>();
withComponents<T extends ComponentType<ISchema>>(
definitions: ComponentDefinition<T>[]
): Array<[number, TupleToIntersection<T[]>]> {
const result: Array<[number, TupleToIntersection<T[]>]> = [];
for (const [id, entity] of this.entities) {
let isMatch = true;
for (const def of definitions) {
if (!entity.components.has(def.id)) {
isMatch = false;
break;
}
}
if (isMatch) {
result.push([id, entity.data as TupleToIntersection<T[]>]);
}
}
return result;
}
}
目前,我在这一行上遇到以下错误:
world.withComponents([Position, Container])
类型 'ComponentDefinition<{ capacity: number; contents: { item: number; amount: number; }[]; }>' 的参数不能赋给类型 'ComponentDefinition<{ x: number; y: number; }>' 的参数。
类型 'ComponentType<{ capacity: number; contents: { item: number; amount: number; }[]; }>' 中缺少属性 'x'、'y' 的类型。
所以我的问题是我无法弄清楚如何接受不同泛型的数组。
英文:
I'm trying to figure out Entity Component System and for usability I need to be able to pass an array of different Component definitions and get a merged type as a result.
I'm trying to do this:
const Position = defineComponent({ x: 0, y: 0 });
const Container = defineComponent({ capacity: 0, contents: new Array<{ item: number; amount: number }>() });
world.withComponents(Position, Container).forEach(([id, entity]) => {
console.log(entity.x);
consone.log(entity.capacity);
});
Full code:
interface ISchema {}
type SimpleType = number | string | boolean;
type Type<V> = V extends SimpleType
? V
: V extends Array<infer RT>
? Array<Type<RT>>
: V extends ISchema
? ComponentType<V>
: never;
type ComponentType<T extends ISchema> = {
[key in keyof T]: Type<T[key]>;
};
type ComponentDefinition<T extends ISchema> = {
id: Symbol;
type: ComponentType<T>;
};
function defineComponent<T extends ISchema>(schema: T): ComponentDefinition<T> {
return {
id: Symbol(),
type: schema as ComponentType<T>,
};
}
type TupleToIntersection<T extends any[]> = {
[I in keyof T]: (x: T[I]) => void;
}[number] extends (x: infer I) => void
? I
: never;
class World
{
protected readonly entities = new Map<number, { data: ISchema; components: Set<Symbol> }>();
withComponents<T extends ComponentType<ISchema>>(
definitions: ComponentDefinition<T>[]
): Array<[number, TupleToIntersection<T[]>]> {
const result: Array<[number, TupleToIntersection<T[]>]> = [];
for (const [id, entity] of this.entities) {
let isMatch = true;
for (const def of definitions) {
if (!entity.components.has(def.id)) {
isMatch = false;
break;
}
}
if (isMatch) {
result.push([id, entity.data as TupleToIntersection<T[]>]);
}
}
return result;
}
}
Currently I getting the following error on this line:
world.withComponents([Position, Container])
Argument of type 'ComponentDefinition<{ capacity: number; contents: { item: number; amount: number; }[]; }>' is not assignable to parameter of type 'ComponentDefinition<{ x: number; y: number; }>'.
Types of property 'type' are incompatible.
Type 'ComponentType<{ capacity: number; contents: { item: number; amount: number; }[]; }>' is missing the following properties from type 'ComponentType<{ x: number; y: number; }>': x, yts(2345)
So my problem is that I'm not able to figure out how to accept an array of different generics.
答案1
得分: 1
以下是代码部分的翻译:
You can make withComponents()
generic in the tuple type T
of arguments to ComponentDefinition<>
for each element of the definitions
input. That is, if definitions
is of type [ComponentDefinition<X>, ComponentDefinition<Y>, ComponentDefinition<Z>]
, then T
would be [X, Y, Z]
. Then you can make the type of definitions
be a mapped type over the tuple T
, and the compiler can infer T
from definitions
because it's a homomorphic mapped type (see https://stackoverflow.com/q/59790508/2887218). Like this:
将withComponents()
泛型 应用到 ComponentDefinition<>
的参数元组类型 T
中,用于处理 definitions
输入的每个元素。也就是说,如果 definitions
的类型是 [ComponentDefinition<X>, ComponentDefinition<Y>, ComponentDefinition<Z>]
,那么 T
就会是 [X, Y, Z]
。然后,可以将 definitions
的类型设定为元组 T
上的映射类型,编译器可以从 definitions
推断出 T
,因为它是同态映射类型(参见 https://stackoverflow.com/q/59790508/2887218)。就像这样:
withComponents<T extends ComponentType<ISchema>[]>(
definitions: [...{ [I in keyof T]: ComponentDefinition<T[I]> }]
): Array<[number, TupleToIntersection<T>]>
So { [I in keyof T]: ComponentDefinition<T[I]> }
is the relevant mapped type. And I've wrapped it in a variadic tuple type [...
+]
, which, as the implementing pull request microsoft/TypeScript#39094 mentions, "can conveniently be used to indicate a preference for inference of tuple types".
所以 { [I in keyof T]: ComponentDefinition<T[I]> }
是相关的映射类型。而且我已将它包裹在一个变参元组类型 [...
+]
中,正如实现的 pull request microsoft/TypeScript#39094 中所提到的,它可以方便地用于指示对元组类型的推断偏好。
And everywhere else in your code that uses T
will need to be updated to account for the fact that it's [X, Y, Z]
and not X | Y | Z
... that is, every TupleToIntersection<T[]>
should be replaced with TupleToIntersection<T>
.
而在你的代码的其他地方使用 T
的地方都需要更新,以考虑它是 [X, Y, Z]
而不是 X | Y | Z
...也就是说,每个 TupleToIntersection<T[]>
都应该替换为 TupleToIntersection<T>
。
Let's try it out:
让我们试一试:
const Position = defineComponent({ x: 0, y: 0 });
const Container = defineComponent({ capacity: 0, contents: new Array<{ item: number; amount: number }>() });
const world = new World();
world.withComponents([Position, Container]).forEach(([id, entity]) => {
console.log(entity.x);
console.log(entity.capacity);
});
Looks good!
看起来不错!
英文:
You can make withComponents()
generic in the tuple type T
of arguments to ComponentDefinition<>
for each element of the definitions
input. That is, if definitions
is of type [ComponentDefinition<X>, ComponentDefinition<Y>, ComponentDefinition<Z>]
, then T
would be [X, Y, Z]
. Then you can make the type of definitions
be a mapped type over the tuple T
, and the compiler can infer T
from definitions
because it's a homomorphic mapped type (see https://stackoverflow.com/q/59790508/2887218). Like this:
withComponents<T extends ComponentType<ISchema>[]>(
definitions: [...{ [I in keyof T]: ComponentDefinition<T[I]> }]
): Array<[number, TupleToIntersection<T>]>
So { [I in keyof T]: ComponentDefinition<T[I]> }
is the relevant mapped type. And I've wrapped it in a variadic tuple type [...
+]
, which, as the implementing pull request microsoft/TypeScript#39094 mentions, "can conveniently be used to indicate a preference for inference of tuple types".
And everywhere else in your code that uses T
will need to be updated to account for the fact that it's [X, Y, Z]
and not X | Y | Z
... that is, every TupleToIntersection<T[]>
should be replaced with TupleToIntersection<T>
.
Let's try it out:
const Position = defineComponent({ x: 0, y: 0 });
const Container = defineComponent({ capacity: 0, contents: new Array<{ item: number; amount: number }>() });
const world = new World();
world.withComponents([Position, Container]).forEach(([id, entity]) => {
console.log(entity.x);
console.log(entity.capacity);
});
Looks good!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论