【问题标题】:Typescript linked generics not properly discriminated打字稿链接的泛型没有正确区分
【发布时间】:2021-09-27 12:58:14
【问题描述】:

我有以下(简化的)代码:

type GetInput<U> = {
  defaultValue?: U
}

const getBool = (input: GetInput<boolean>) => {
  return input.defaultValue ?? true
}

const getNumber = (input: GetInput<number>) => {
  return input.defaultValue ?? 3;
}

type GetFunc<U> = (input: GetInput<U>) => U;

type ToType<T extends 'boolean' | 'number'> =
  T extends 'boolean'
    ? boolean
    : T extends 'number'
    ? number
    : never;

type GlobalGetInput<
  T extends 'boolean' | 'number',
  U extends ToType<T>
> = {
  type: T;
  defaultValue?: U;
};

const get = <T extends 'boolean' | 'number', U extends ToType<T>>(input: GlobalGetInput<T, U>) => {
  let func: GetFunc<U>;
  switch (input.type) {
    case 'boolean':
      func = getBool;
      break;
    case 'number':
      func = getNumber;
      break;
    default:
      throw new Error('Invalid type')
  }
  return func({ defaultValue: input.defaultValue })
} 

get({ type: 'boolean', defaultValue: true })

Playground

运行时可以正常工作,但是在func = getBool; 中输入失败

Type '(input: GetInput<boolean>) => boolean' is not assignable to type 'GetFunc<U>'.
  Types of parameters 'input' and 'input' are incompatible.
    Type 'GetInput<U>' is not assignable to type 'GetInput<boolean>'.
      Type 'U' is not assignable to type 'boolean'.
        Type 'ToType<T>' is not assignable to type 'boolean'.
          Type 'boolean | (T extends "number" ? number : never)' is not assignable to type 'boolean'.
            Type 'T extends "number" ? number : never' is not assignable to type 'boolean'.
              Type 'number' is not assignable to type 'boolean'.
                Type 'number' is not assignable to type 'boolean'.
                  Type 'number | boolean' is not assignable to type 'boolean'.
                    Type 'number' is not assignable to type 'boolean'.

发生这种情况是因为如果在类型参数中传递了字符串boolean,我不知道如何告诉编译器U 只能是boolean 类型。如果它是对象的联合,我们可以使用静态属性进行区分,但对于标量的联合我不知道。

谢谢!

【问题讨论】:

    标签: typescript types typescript-generics


    【解决方案1】:

    发生这种情况是因为差异。 TypeScript 可以防止你犯错。看下面的例子:

    // you can't assign a function with a broader result to a narrower one
    declare let fnA: () => 1 | 2
    declare let fnB: () => 1
    fnB = fnA
    
    // you can't assign a function with narrower params to a broader one
    declare let fnC: (a: 1 | 2) => 3
    declare let fnD: (a: 1) => 3
    fnC = fnD
    

    在最后一个示例中,因为您将fnD 分配给fnC,所以您的意思是fnD 可以处理与fnC 相同的输入 - 但事实并非如此。这可能会导致运行时错误。

    当您看到此类错误消息时,请记住 TypeScript 只是警告您未遵守其差异规则。如果您想了解更多关于这方面的信息,有一个很好的 presentation 和另一个 post

    在您的情况下,TypeScript 无法看到您正在正确处理事情,并且仍然认为您的操作是不安全的。因为你知道自己在做什么,所以你需要告诉 TypeScript 这很好。小心点。

    const get = <T extends 'boolean' | 'number', U extends ToType<T>>(input: GlobalGetInput<T, U>) => {
      let func: GetFunc<U>;
    
      switch (input.type) {
        case 'boolean':
          func = getBool as any as GetFunc<U>;
          break;
        case 'number':
          func = getNumber as any as GetFunc<U>;
          break;
        default:
          throw new Error('Invalid type')
      }
      return func({ defaultValue: input.defaultValue })
    } 
    

    我通常使用any强烈 表示这是一个覆盖,但你也可以使用unknown,这取决于你。

    【讨论】:

    • 这是我目前正在做的,但还是很遗憾不能告诉TS它是安全的。也许我可以将 GetFunc 切换为使用 T 并且它会认为它是安全的?
    猜你喜欢
    • 2018-02-09
    • 2021-05-06
    • 2021-12-21
    • 1970-01-01
    • 2015-11-27
    • 2017-09-26
    • 2022-12-20
    • 2021-10-12
    • 2021-11-26
    相关资源
    最近更新 更多