为什么在循环依赖中命名函数与箭头函数的处理方式不同?

Why are named functions treated differently than arrow functions in circular dependencies?

我正在尝试在 TypeScript 中推出我自己的依赖注入机制,我遇到了一个有趣的问题。在声明我的依赖项时,我必须使用命名函数而不是箭头函数(这通常是我的默认值),否则我会得到循环声明错误。

下面是构成我的依赖解析器的一组类型:

type CollectionTemplate = {
    [key: string]: (...args: any) => any
}

type RegistryTemplate = {
    [key: string]: CollectionTemplate
}

type RegistryCollection<R extends RegistryTemplate> = keyof R

type CollectionEntry<C extends CollectionTemplate> = keyof C

type BaseRegistry<R extends RegistryTemplate> = R

type BaseContext<R extends RegistryTemplate> = {
    [C in RegistryCollection<R>]: {
        [E in CollectionEntry<R[C]>]: ReturnType<R[C][E]>
    }
}

type BaseUseDep<R extends RegistryTemplate> =
    <C extends RegistryCollection<R>, E extends CollectionEntry<R[C]>>
        (collection: C, entry: E) => BaseContext<R>[C][E]

现在,当我使用命名函数时一切正常:

type DepRegistry = BaseRegistry<typeof registry>
type DepContext = BaseContext<DepRegistry>
type UseDep = BaseUseDep<DepRegistry>

function greet(deps: UseDep)
{
    return (name: string) =>
    {
        const console = deps('system', 'console')
        console.log(name)
    }
}

function printGreeting(deps: UseDep)
{
    return () =>
    {
        const greet = deps('social', 'greet')
        greet('John')
    }
}

const registry = {
    system: {
        console: () => console,
    },
    social: {
        greet,
        printGreeting,
    }
}

但是,如果我使用箭头函数而不是命名函数来声明这些函数,则会出现以下错误:

Type alias 'DepRegistry' circularly references itself. ts(2456)
Type alias 'UseDep' circularly references itself. ts(2456)

这是为什么?仅仅是因为我将函数分配给变量而不是显式声明函数吗?

编辑:这里是游乐场 named functions and with arrow functions

以下回答部分解释了问题;这意味着要发表评论

我认为,命名函数和箭头函数之间存在差异的原因 可能是 类型签名的 lazy vs eager 分辨率。

Anders Hejlsberg compared interface vs type resolution in this GitHub comment:

The trick is to make the recursive back references within interface types. This works because resolution of interface base types and interface members is deferred, whereas resolution of type aliases is performed eagerly.

function 的签名可以用 interface { (...params: Params): Return } 指定,因此 Typescript 编译器使用惰性解析策略并且允许递归定义。

我不知道为什么箭头函数的处理方式不同,但似乎他们的类型签名被急切地评估。


我经常遵循类似的依赖注册/注入模式,之前遇到过这个问题。从来没有想过用常规函数替换箭头。很高兴知道这是可能的。您也可以首先考虑避免循环依赖。

这个问题本身非常违反直觉,同时也很有趣。