打字稿 - [MyType<T>,推断 T] 数组,其中 T 可以因元素而异



TL;博士

如何制作一个元组数组,其中元组的第二个元素是从第一个元素推断出来的?

这是以下代码的打字稿游乐场。

我的情况

我正在使用Puppeteer打开各种数独网站,在游戏中阅读,解决它,然后移动到下一个网站。

每个站点都由一个Puppet<TDiff>表示,其中TDiff是一组字符串,表示特定于站点的难度名称(如果有)。 我有一个名为runPuppet的函数,它接受一个Puppet和一些选项,其中一个是难度,然后执行上述算法。

(很抱歉代码片段很长。 我试图使它们尽可能简短。

运行傀儡

interface Puppet<TDiff extends string | undefined = undefined> {
// ...
}
interface RunOptions<TDiff extends string | undefined> {
difficulty: TDiff;
newGame: boolean;
// ...
}
const default options = {
newGame: false,
// difficulty is not defined
// ...
};
export default async function runPuppet<TDiff extends string | undefined = undefined>(
puppet: Puppet<TDiff>,
options: Partial<RunOptions<TDiff>>
) {
options = Object.assign({}, defaultOptions, options);
// ...
}

数独-com.ts(sudoku.com)

type Difficulty = 'easy' | 'medium' | 'hard' | 'expert';
const SudokuDotComPuppet: Puppet<Difficulty> = {
// ...
}

websudoku-com.ts (for websudoku.com)

type Difficulty = 'easy' | 'medium' | 'hard' | 'evil';
const WebSudokuDotComPuppet: Puppet<Difficulty> = {
// ...
}

主目录

(async () => {
await runPuppet(WebSudokuDotComPuppet, { difficulty: 'evil' });
await runPuppet(WebSudokuDotComPuppet, { difficulty: 'evil', newGame: true });
await runPuppet(WebSudokuDotComPuppet, { difficulty: 'evil', newGame: true });
await runPuppet(SudokuDotComPuppet, { difficulty: 'expert' });
await runPuppet(SudokuDotComPuppet, { difficulty: 'expert', newGame: true });
await runPuppet(SudokuDotComPuppet, { difficulty: 'expert', newGame: true });
})();

此代码运行两个木偶三次,并且工作正常。

现在我想抽象 main.ts 中的代码以使用元组数组:Array<[Puppet<TDiff>, TDiff]>每个元素都有自己的 TDiff。 这样我就可以做到:

// needs to be fixed
type PuppetDifficulty<TDiff extends string | undefined = undefined> =
[Puppet<TDiff>, TDiff];
(async () => {
// throws compile-time errors
const puppets: PuppetDifficulty[] = [
[ WebSudokuDotComPuppet, 'evil' ],
[ SudokuDotComPuppet, 'expert' ],
];
for (const [puppet, difficulty] of puppets) {
for (let i = 0; i < 3; i++) {
await runPuppet(puppet, { difficulty, newGame: !!i });
}
}
})();

这抛出了四个错误,基本上都归结为'expert''evil'不能分配给undefined。 这是因为没有<TDiff>Puppet假设它是未定义的,而不是试图根据参数进行推断。

我尝试使用ElementType<T>模式:

type DifficultyType<TPuppet extends Puppet> =
TPuppet extends Puppet<infer T> ? T : undefined;
type PuppetDifficulty<TPuppet extends Puppet = Puppet> = [ TPuppet, DifficultyType<TPuppet> ];
(async () => {
const puppets: PuppetDifficulty[] = [
[ WebSudokuDotComPuppet, 'evil' ],
[ SudokuDotComPuppet, 'expert' ],
];
// ...
)();

这会导致相同的四个错误。

我不知道我是否有时间完全解释这一点,但作为一个草图......基本思想是创建一个泛型帮助程序函数,其泛型类型参数旨在成为对应于每个对的第一个参数的元组,然后将此元组类型映射到您正在传入的实际对。 编译器应该能够使用来自映射类型的推理来推断传入数组的类型参数,或者在执行无法推断的操作时发出警告。 关于提示编译器将["a", 1]等参数解释为[string, number]而不是Array<string | number>,如何获得正确的推理等,有各种各样的警告,但这是基本思想。 这里有一种方法可以做到这一点:

const puppetDifficulties = <P extends Array<Puppet<any>>>(
arr: [] | { [I in keyof P]: [P[I], P[I] extends Puppet<infer S> ? S : never] }
) => arr as Exclude<typeof arr, []>;

然后我会像这样使用它:

const puppets = puppetDifficulties([
[Puppet1, "expert"],
[Puppet2, "evil"],
[Puppet1, "XXXX"] // error! not assignable to [Puppet<Puppet1Diff>, Puppet1Diff]
]);

请注意,即使puppets是强类型,通过迭代它就像一个常规数组一样(迭代类型为[string, number, boolean]的元组只会给你string | number | boolean类型的元素。 因此,以下内容不会给您任何错误,而只是因为事物被解释为联合:

for (const [puppet, difficulty] of puppets) {
for (let i = 0; i < 3; i++) {
runPuppet(puppet, { difficulty, newGame: !!i });
}
}

好的,希望有帮助;祝你好运!

链接到代码

相关内容

最新更新