typescript 用于创建和返回泛型对象数组的泛型函数

k2arahey  于 2023-03-13  发布在  TypeScript
关注(0)|答案(3)|浏览(135)

每当我认为我已经理解了泛型,我就会想出一个证明我错了的任务,这就是我想要的:
Playground

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不喜欢我试图推送到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: {}; }'.

有没有人能给我解释一下我必须怎么改变功能?或者是不可能的?

l7mqbcuq

l7mqbcuq1#

因为T可能是其他类型,如tAssets & { id: tFruits } & { anotherId: tFruits },它不像createAssets那样与tAssets & { id: tFruits }兼容。
createAssets说它可以从U[]创建T[],但实际上它只能从U[]创建(tAssets & { id: U })[]。如果T包含anotherIdcreateAssets将不会返回它,这可能会导致错误。

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']);

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

hof1towb2#

当你可以Assert它并暂时结束它的时候,

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

这实际上是不合理的,因为调用者选择了TU类型,这意味着它通过了类型检查:

const fruitAssets = createAssets<tCarAssets, tFruits>(["apple", "orange", "pear"]);
//                               ^^^^^^^^^^ should be invalid

您可以通过向泛型参数添加约束来轻松地使其更安全。

const createAssets = <T extends tAssets & { id: U }, U extends string>(ids: U[]): T[] => {

但是,现在您必须使用as unknown as T,因为TypeScript看到了有人可能使用

createAssets<{ id: string; image: {}; text: {}; extraProperty: {} }, string>(...);
//                                               ^^^^^^^^^^^^^ extra property here

这意味着将t推到assets是不合理的,因为您将丢失那个额外的属性。如果您真的想这样做,您还可以通过稍微更改约束来防止这种情况发生...

const createAssets = <T extends tAssets & { id: U }, U extends (tAssets & { id: U } extends T ? string : never)>(ids: U[]): T[] => {

不幸的是,在所有这些都准备就绪的情况下,Assert仍然是必要的,您可以尝试使用map使它更简洁一些(去掉双重Assert):

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

Playground

q5lcpyga

q5lcpyga3#

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现在很满意,但我觉得它更像是一种变通方法,而不是一个适当的解决方案。我在这里自欺欺人吗?

相关问题