为什么我收到“类型实例化过深并且可能是无限的”?

wwo*_*ers 14 typescript typescript-generics

游乐场链接

我有这些一般定义:

type Module<P extends Payloads, C extends Children> = {
    payloads: P;
    children: C;
};

type Children = Record<string, any>; // some values will be nested Modules

type Payloads = Record<string, any>;

type ExtractPayloads<M extends Module<any, any>> = M extends Module<infer P, any> ? P : never;

type ExtractChildren<M extends Module<any, any>> = M extends Module<any, infer C> ? C : never;
Run Code Online (Sandbox Code Playgroud)

基本上,模块是指定类型的类型children,可以包含嵌套模块。

我有这种类型,可以Action根据模块的payload类型生成 s:

type ModuleRootActions<
  MODULE extends Module<any, any>,
  PAYLOADS = ExtractPayloads<MODULE>,
> = {
  [NAME in keyof PAYLOADS]: {
    type: NAME;
    payload: PAYLOADS[NAME];
  };
}[keyof PAYLOADS];
Run Code Online (Sandbox Code Playgroud)

接下来,我有一个递归类型,可以帮助我Action为模块树中的所有模块生成 s(即,包括其子模块和孙模块等):

type AllModuleActions<
  MODULE extends Module<any, any>,
  CHILDREN = ExtractChildren<MODULE>,
> =
  | ModuleRootActions<MODULE>
  | {
      [KEY in keyof CHILDREN]: CHILDREN[KEY] extends Module<any, any>
        ? AllModuleActions<CHILDREN[KEY]>
        : never;
    }[keyof CHILDREN];
Run Code Online (Sandbox Code Playgroud)

最后,我有这些具体的例子:

type C = Module<{
  "incrementBy": number;
}, {}>;

type B = Module<{
  "setIsSignedIn": boolean;
}, {
  c: C;
}>;

type A = Module<{
  "concat": string;
  "setIsDarkMode": boolean;
}, {
  b: B;
}>;
Run Code Online (Sandbox Code Playgroud)

到目前为止我的所有类型都是正确的——我已经手动验证了这一点。Action现在,我正在编写一个接受泛型 的函数Module。我可以成功定义这些类型:

type ConcreteAction<M extends Module<any, any>> = AllModuleActions<M>;

const concreteAction: ConcreteAction<A> = {
  type: "concat",
  payload: "str",
}
Run Code Online (Sandbox Code Playgroud)

但是,一旦我尝试将它们放入通用函数中,我就会在标题中收到错误。

const composedReducer = <MODULE extends Module<any, any>>(
  action: AllModuleActions<MODULE>,
) => {
  if (action) {

  }
};
Run Code Online (Sandbox Code Playgroud)

您会注意到Playground 链接中存在action错误:“类型实例化过深且可能无限”。我认为发生这种情况是因为该MODULE类型是通用的,并且定义中可能存在循环Module,即使从语义上我知道它是一棵树。

我该如何修复这个错误?有没有办法告诉编译器该图始终是一棵树并且永远不会包含无限循环?

jca*_*alz 14

AllModuleActions<M>类型是递归的,编译器无法很好地处理。您一次对一个对象类型进行任意深度的索引,以产生一个大的联合类型我之前在生成对象类型的所有点路径(例如,{a: {b: string, c: number}}变为)时遇到过这个问题"a.b" | "a.c"

AllModuleActions<M>当您评估诸如何时M特定类型之类的问题时,您通常不会看到问题;但是当它是未指定的泛型类型参数(或依赖于此类类型参数的类型)时,您可能会遇到麻烦。您可能会看到“过深”错误。更糟糕的是,编译器往往会陷入困境,导致 CPU 使用率激增并降低 IDE 的速度。我不知道为什么会发生这种情况。

也许最好的建议是不要构建这样的类型。如果你必须这样做,那么我发现了一些可以提供帮助的方法,但它们并不是万无一失的:


有时,您可以通过将其中一种类型重新表述为分布式条件类型,使编译器推迟对其中一种类型的求值。如果是泛型类型参数并给您带来麻烦,也许不会:MAllModuleActions<M>M extends any ? AllModuleActions<M> : never

const generic = <M extends Module<any, any>>(
  action: M extends any ? AllModuleActions<M> : never, // <-- defer
) => {
  action; // okay
};
Run Code Online (Sandbox Code Playgroud)

如果这不起作用,您可以尝试显式限制递归类型的深度,以便默认情况下只会下降三到四个级别:

type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

type AllModuleActions<M extends Module<any, any>, D extends Prev[number] = 4> =
  [D] extends [never] ? never :
  | ModuleRootActions<M>
  | {
    [K in keyof M["children"]]: M["children"][K] extends Module<any, any>
    ? AllModuleActions<M["children"][K], Prev[D]>
    : never;
  }[keyof M["children"]];
Run Code Online (Sandbox Code Playgroud)

这与您的类似,只是我们添加了一个D参数,该参数(默认情况下)从 开始4并在每次AllModuleActions求值时减少(注意Prev[4]is3Prev[3]is 2),直到最终达到never并且递归类型退出:

const generic = <M extends Module<any, any>>(
  action: AllModuleActions<M>
) => {
  action; // okay
};
Run Code Online (Sandbox Code Playgroud)

这些解决方法可能对特定用例有帮助,也可能没有帮助,并且可能会产生明显的副作用(例如,类型可能不相同;类型推断可能表现不同;显示的快速信息可能更复杂),所以要小心!

Playground 代码链接


Ali*_*i80 6

确保您没有旧的 Typescript 版本,因为旧版本的类型推断存在一些错误

对我来说更新从3.94.6.3解决了这个问题。


Sam*_*AUD 5

这个错误非常普遍,所以我不知道我的帖子是否真的有帮助,但我在使用TypescriptvueI18n的Vue项目上遇到了这个问题。当我在文件外部的实用程序代码中引用该方法时,编译器引发了错误。我正在使用 Vue3、Vuetify 和 vueI18n,我的文件如下所示VueI18n.global.t*.vue

import { createI18n } from 'vue-i18n'
import en from 'vuetify/lib/locale/en'
import fr from 'vuetify/lib/locale/fr'

const messages = {
  en: {
    ...require('@/locales/en.json'),
    $vuetify: en
  },
  fr: {
    ...require('@/locales/fr.json'),
    $vuetify: fr
  }
}

export default createI18n({
    locale: localStorage.getItem('locale') || process.env.VUE_APP_I18N_LOCALE || 'en',
    fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
    messages
  })
Run Code Online (Sandbox Code Playgroud)

经过几个小时的努力,我在这里找到了这个非常简单的例子。所以我更新了我的文件,现在它可以正确编译了。

import { createI18n, FallbackLocale } from 'vue-i18n'
import en from '@/locales/en.json'
import fr from '@/locales/fr.json'

const locale: string = localStorage.getItem('locale') || process.env.VUE_APP_I18N_LOCALE || 'en'
const fallbackLocale: FallbackLocale = process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en'

export default createI18n({
  locale: locale,
  fallbackLocale: fallbackLocale,
  messages: {
    en,
    fr,
  }
})
Run Code Online (Sandbox Code Playgroud)