英文:
Generic function for creating and returning a generic array of objects
问题
在这段Typescript代码中,你遇到了一个类型问题,TypeScript 不喜欢你尝试将对象推送到 createAssets 函数内的 assets 数组中。它显示以下错误消息:
Argument of type '{ id: U; image: {}; texts: {}; }' is not assignable to parameter of type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to '{ id: U; image: {}; texts: {}; }'.
这个问题是由于 TypeScript 无法正确推断 T
的类型,因为它可能与 { id: U; image: {}; texts: {}; }
不相关。为了解决这个问题,你可以使用 TypeScript 的泛型约束来明确告诉 TypeScript T
应该是什么类型。
你可以将 createAssets
函数修改如下:
const createAssets = <T extends { id: U; image: any; text: any }, U>(ids: U[]): T[] => {
const assets: T[] = [];
for (const dataId of ids) {
assets.push({
id: dataId,
image: {},
text: {},
});
}
return assets;
}
在这里,我们使用 <T extends { id: U; image: any; text: any }, U>
来指定 T
必须是具有特定属性的对象类型。这样,TypeScript 就能够正确推断 T
的类型,并且你的代码将不再产生错误。
请注意,我已经将 text
更正为 texts
,以匹配你的类型定义。你可以根据你的需求进行修改。希望这对你有所帮助。如果还有其他问题,请随时提出。
英文:
Everytime I think I have understood the generics, I come up with a task which proves me wrong. This is what I want:
type tFruits = 'apple' | 'orange' | 'pear';
type tCars = 'peugot' | 'mercedes'| 'nissan';
type tFlowers = 'rose' | 'tulip' | 'violet';
type tAssets = {
image: {},
text: {},
}
type tFruitAssets = tAssets & { id: tFruits }
type tCarAssets = tAssets & { id: tCars }
type tFlowerAssets = tAssets & { id: tFlowers }
const createAssets = <T, U>(ids: U[]):T[] => {
const assets:T[] = [];
for (const dataId of ids){
// Typescript doesn't like this:
assets.push({
id: dataId,
image:{},
text:{},
});
}
return assets;
}
const fruitAssets = createAssets<tFruitAssets, tFruits>(['apple','orange','pear']);
const carAssets = createAssets<tCarAssets, tCars>(['peugot','mercedes','nissan']);
const flowerAssets = createAssets<tFlowerAssets, tFlowers>(['rose','tulip','violet']);
Typescript doesn't like the object I try to push to the assets array inside my createAssets functions. It says:
Argument of type '{ id: U; image: {}; texts: {}; }' is not assignable to parameter of type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to '{ id: U; image: {}; texts: {}; }'.
Could someone please explain me how I have to change the function? Or is it not possible?
答案1
得分: 1
createAssets
函数的作用是从 U[]
创建 (tAssets & { id: U })[]
,因此其类型应该是:
const createAssets = <U>(ids: U[]): (tAssets & { id: U })[] => {
const assets: (tAssets & { id: U })[] = [];
for (const dataId of ids) {
assets.push({
id: dataId,
image: {},
text: {},
});
}
return assets;
};
英文:
Because T
could be other type like tAssets & { id: tFruits } & { anotherId: tFruits }
which is not compatible with tAssets & { id: tFruits }
like the createAssets
do.
createAssets
say it can create T[]
from U[]
, but it actually only create (tAssets & { id: U })[]
from U[]
.If T
includes anotherId
, createAssets
will not return it and this may cause error.
type tFruits = 'apple' | 'orange' | 'pear';
type tAssets = {
image: {};
text: {};
};
type FruitAssets = tAssets & { id: tFruits };
type OtherFruitAssets = FruitAssets & { anotherId: tFruits };
//works
const tFruitAssets: FruitAssets[] = createAssets<FruitAssets, tFruits>(['apple']);
//not work properly,but will not throw type error here
//expect otherTFruitAssets to be type OtherFruitAssets[]
//but actually otherTFruitAssets will be FruitAssets[] and this may cause error
const otherTFruitAssets: OtherFruitAssets[] = createAssets<OtherFruitAssets, tFruits>(['apple']);
What createAssets
do is create (tAssets & { id: U })[]
from U[]
, so its type should be:
const createAssets = <U,>(ids: U[]): (tAssets & { id: U })[] => {
const assets: (tAssets & { id: U })[] = [];
for (const dataId of ids) {
assets.push({
id: dataId,
image: {},
text: {},
});
}
return assets;
};
答案2
得分: 0
10分钟后,我想出了这个:
// [...]
const createAssets = <T, U>(ids: U[]): T[] => {
const assets: T[] = [];
for (const dataId of ids) {
const t = {
id: dataId,
image: {},
text: {},
} as T;
assets.push(t);
}
return assets;
}
Typescript 现在满足要求了,但我觉得这更像是一种权宜之计,而不是一个正式的解决方案。我是不是在自欺欺人?
英文:
10 Minutes later I came up with this:
// [...]
const createAssets = <T, U>(ids: U[]):T[] => {
const assets:T[] = [];
for (const dataId of ids){
const t = {
id: dataId,
image:{},
text:{},
} as T;
assets.push(t);
}
return assets;
}
Typescript is satisfied now but I feel like as if it would be more of a workaround than a proper solution. Am I fooling myself here?
答案3
得分: 0
以下是翻译好的内容:
"While you could assert it and be done with it for now, this is actually unsound because the caller chooses the type of T
and U
, which means this passes type-checking:
const fruitAssets = createAssets<tCarAssets, tFruits>(["apple", "orange", "pear"]);
// ^^^^^^^^^^ should be invalid
You can easily make it safer by adding constraints to the generic parameters.
const createAssets = <T extends tAssets & { id: U }, U extends string>(ids: U[]): T[] => {
However, now you will have to use as unknown as T
because TypeScript sees the possibility that someone may use
// ^^^^^^^^^^^^^ extra property here
which means that pushing t
to assets
is unsound, since you would be missing that extra property. If you really wanted to, you could also prevent this from happening by changing the constraints a little...
const createAssets = <T extends tAssets & { id: U }, U extends (tAssets & { id: U } extends T ? string : never)>(ids: U[]): T[] => {
Unfortunately, with all of these in place, the assertion is still necessary. You could try to make it a little cleaner (get rid of the double assertion) with map
:
const createAssets = <T extends tAssets & { id: U }, U extends (tAssets & { id: U } extends T ? string : never)>(ids: U[]): T[] => {
const assets = ids.map((id) => ({ id, image: {}, text: {} })) as T[];
return assets;
};
英文:
While you could assert it and be done with it for now,
const createAssets = <T, U>(ids: U[]): T[] => {
const assets: T[] = [];
for (const dataId of ids) {
const t = {
id: dataId,
image: {},
texts: {},
} as T;
assets.push(t);
}
return assets;
};
this is actually unsound because the caller chooses the type of T
and U
, which means this passes type-checking:
const fruitAssets = createAssets<tCarAssets, tFruits>(["apple", "orange", "pear"]);
// ^^^^^^^^^^ should be invalid
You can easily make it safer by adding constraints to the generic parameters.
const createAssets = <T extends tAssets & { id: U }, U extends string>(ids: U[]): T[] => {
However, now you will have to use as unknown as T
because TypeScript sees the possibility that someone may use
createAssets<{ id: string; image: {}; text: {}; extraProperty: {} }, string>(...);
// ^^^^^^^^^^^^^ extra property here
which means that pushing t
to assets
is unsound, since you would be missing that extra property. If you really wanted to, you could also prevent this from happening by changing the constraints a little...
const createAssets = <T extends tAssets & { id: U }, U extends (tAssets & { id: U } extends T ? string : never)>(ids: U[]): T[] => {
Unfortunately, with all of these in place, the assertion is still necessary.
You could try to make it a little cleaner (get rid of the double assertion) with map
:
const createAssets = <T extends tAssets & { id: U }, U extends (tAssets & { id: U } extends T ? string : never)>(ids: U[]): T[] => {
const assets = ids.map((id) => ({ id, image: {}, text: {} })) as T[];
return assets;
};
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论