typescript 如何在Map中的不同方法之间动态键入参数

nwo49xxi  于 2023-03-04  发布在  TypeScript
关注(0)|答案(1)|浏览(141)

我在将函数的预期参数动态Map到父 Package 器中选定的函数时遇到问题。我已经设法正确Map了所有 * 可以 * 调用的方法,但Typescript在我传递的参数处抛出错误。
这是我的想法:

type TGenerateSetA = { city: number; address: number; }
const setASelects = {
  city: ({ appendComma }: { appendComma: boolean }) => `city${appendComma ? ',' : ''}`,
  address: ({ appendComma }: { appendComma: boolean }) => `address${appendComma ? ',' : ''}`,
};

type TGenerateSetB = { user: number; role: number; }
const setBSelects = {
  user: ({ appendComma }: { appendComma: boolean }) => `user${appendComma ? ',' : ''}`,
  role: ({ appendComma }: { appendComma: boolean }) => `role${appendComma ? ',' : ''}`,
};

type TSetObject = Record<string, ({ appendComma }: { appendComma: boolean }) => string>;
interface ISetMapping {
    SET_A: TSetObject
    SET_B: TSetObject
}

const SELECTS: ISetMapping = {
  SET_A: setASelects,
  SET_B: setBSelects,
}

type TGenerateCommonResult = {
  selectFrom: keyof ISetMapping;
} & TGenerateSetA
const generateCommonResult = ({ selectFrom, ...args }: TGenerateCommonResult) => {
  const result: string[] = [];

  const totalEnabledSelects = Object.values(args).reduce((acc, shouldSelect) => acc + shouldSelect, 0);
  let selectsAdded = 0;

  

    Object.entries(args).forEach(([select, shouldSelect], index, self) => {
        if (shouldSelect) {
            result.push(SELECTS[selectFrom][select]({appendComma: selectsAdded + 1 !== totalEnabledSelects}));
        }

    selectsAdded++;
    });

    return result.join(' ');
}

console.log(generateCommonResult({ selectFrom: 'SET_A', city: 1, address: 0 })); // no error expected
console.log(generateCommonResult({ selectFrom: 'SET_B', user: 1, city: 0 })); // error expected
console.log(generateCommonResult({ selectFrom: 'SET_B', user: 1, role: 0 })); // no error expected

我怎样才能让它工作?
Playground链接

yqlxgs2m

yqlxgs2m1#

看起来你想让TGenerateCommonResult成为一个有区别的联合类型,用selectFrom作为 * discriminant * 属性。如果你愿意,你可以直接写出这个类型:

interface TGenerateCommonResultA extends TGenerateSetA {
  selectFrom: "SET_A"
}
interface TGenerateCommonResultB extends TGenerateSetB {
  selectFrom: "SET_B"
}
type TGenerateCommonResult =
  TGenerateCommonResultA | TGenerateCommonResultB;

这将如预期的那样工作:

generateCommonResult({ selectFrom: 'SET_A', city: 1, address: 0 }); //okay
generateCommonResult({ selectFrom: 'SET_B', user: 1, city: 0 }); // error
generateCommonResult({ selectFrom: "SET_B", role: 1, user: 2 }); // okay
generateCommonResult({ selectFrom: "SET_A", role: 1, user: 2 }); // error

如果你有很多这样的键,那么你可能希望通过一些Map接口以编程的方式计算TGenerateCommonResult,这个接口只是将每个键与对应的值相关联:

interface GenerateCommonResultMapping {
  SET_A: TGenerateSetA,
  SET_B: TGenerateSetB
}

理想情况下,这个接口或者包含相同信息的东西应该已经存在于你的代码库中,你提供的例子没有做到这一点;最接近的是ISetMapping,但是那个接口的值类型不区分TGenerateSetATGenerateSetB,或者根本不关心那个类型,我在这里不担心这个,但是你可能想重构你的代码,以便保留和使用那个信息。
无论如何,给定Map,我们可以将TGenerateCommonResult计算为 * 分布式对象类型 *(在ms/TS#47109中创造的术语),这是一个mapped type,我们立即用完整的键集对它执行index,以得到一个并集。本质上,如果你有一个类型函数F<K>,其中K是一个类似键的类型,并且你想在K中的联合体上分布F,你可以写{[P in K]: F<P>}[K],它把K1 | K2 | ... | KN转换成F<K1> | F<K2> | ... | F<KN>,在你的例子中,类型函数是{ selectFrom: K }GenerateCommonResultMapping[K]的交集,所以我们想要的类型是

type TGenerateCommonResult = { [K in keyof GenerateCommonResultMapping]:
  { selectFrom: K } & GenerateCommonResultMapping[K]
}[keyof GenerateCommonResultMapping]

您可以验证它的计算结果是否为

/* type TGenerateCommonResult = 
 ({ selectFrom: "SET_A"; } & TGenerateSetA) | 
 ({ selectFrom: "SET_B"; } & TGenerateSetB) 
*/

其在结构上与上面人工写出的版本相同。
Playground代码链接

相关问题