typescript 为什么type '(...arg:[T]|[])=>void' not assignable to type 'T extends undefined?()=>void:(params:T)=>void'?

siv3szwd  于 2023-04-13  发布在  TypeScript
关注(0)|答案(1)|浏览(328)

此泛型函数产生以下错误:类型(...arg: [TParams] | []) => void不能分配给类型TParams extends undefined ? () => void : (params: TParams) => void

function useExample<
  TParams extends object | undefined = undefined
>(): TParams extends undefined ? () => void : (params: TParams) => void {
  const open = (...arg: [TParams] | []) => {};

  return open;
         ^^^^ Type '(...arg: [TParams] | []) => void' is not assignable to type 'TParams extends undefined ? () => void : (params: TParams) => void'.
}

根据TParams泛型参数的不同,类型可以是() => void(params: TParams) => void,所以在我看来,应该折叠为(...arg: [TParams] | []) => void是合乎逻辑的。我错了/错过了什么吗?或者这是类型脚本目前无法处理的吗?
如果是这样,我如何创建一个有参数或不依赖于泛型参数的函数?示例:Playground

wkyowqbh

wkyowqbh1#

请参阅microsoft/TypeScript#51488以获得此问题的规范答案。
让我们定义

type F<T> = T extends undefined ? () => void : (params: T) => void

这样你函数就属于

function useExample<
  TParams extends object | undefined = undefined
>() {
  const open: F<TParams> = (...arg: [TParams] | []) => { };
  return open;
}

并且在下面的内容中,为了简洁,我们可以仅引用F<T>
你遇到的问题是编译器不认为(...arg: [TParams] | []) => void可以赋值给F<TParams>,即使从F<T>的定义来看,它可以赋值给条件的两边。
当你有一个conditional type依赖于一个generic类型参数时,比如F<T>,编译器通常会 * 推迟 * 对它的求值,因此类型本质上是 * 不透明的 *。编译器并不会真正尝试查看什么值可以或不可以分配给这样的类型,至少直到类型参数实际上是用类型参数指定的。当条件类型是分布式的时尤其如此(其中所检查的类型是裸类型参数,例如T extends ⋯ ? ⋯ : ⋯,而不是SomethingElse<T> extends ⋯ ? ⋯ : ⋯)。
分布条件类型分布在联合体之间,因此如果G<T>是分布条件类型,则G<A | B>的计算结果将为G<A> | G<B>。请注意,never类型被视为 * 空联合体 *,因此无论如何G<never>都将是never(如果你意识到A | neverA,那么如果G<A | never>分布到G<A> | G<never>,那么对于所有可能的A和分布GG<A>总是G<A> | G<never>,这是有意义的。所以G<never>可能最好是never)。由于您的类型F<T>是分布式的,这意味着F<never>never ...而不是() => void(params: never) => void。由于open不是有效的F<never>,从技术上讲,编译器抱怨赋值是正确的。即使不是这样,要弄清楚一个值是否可以为所有可能的union类型的T赋值也太复杂了,编译器甚至都不去尝试。所以你得到一个错误。
一般来说,当你有一个泛型条件类型,并且你需要给它赋值时,你可能只需要使用类型Assert,然后继续:

function useExample<
  TParams extends object | undefined = undefined
>() {
  const open = ((...arg: [TParams] | []) => { }) as F<TParams>;
  return open;
}

但是在这个特殊的例子中,你可能并不希望F<T>T中是分布式的(对吧?你不希望F<string | number>((params: string) => void) | ((params: number) => void),对吧?)所以你可以按照文档中关于分布式条件类型的说明来做:“为了避免这种行为,你可以用方括号将extends关键字的两边括起来。”(更多信息请参见How to avoid distributive conditional types

type F<T> = [T] extends [undefined] ? () => void : (params: T) => void;

突然间一切都好了:

function useExample<
  TParams extends object | undefined = undefined
>() {
  const open: F<TParams> = (...arg: [TParams] | []) => { }; // okay
  return open;
}

这是因为对于非分配的条件类型,编译器会检查值是否可分配给每一方,如果是,则允许分配。类型F<TParams>肯定会是() => void(params: TParams) => void,并且由于(...arg: [TParams] | []) => void可分配给这两者,因此分配被接受。
因此,这可能是您在这个特定情况下想要做的,即使这个问题的一般版本(您可能需要分布式条件类型)在没有类型Assert之类的东西的情况下无法解决。
Playground链接到代码

相关问题