未正确区分打字稿链接的泛型

时间:2021-07-19 22:29:16

标签: typescript types typescript-generics

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

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 类型。如果它是对象的联合,我们可以使用静态属性进行区分,但对于标量的联合,我不知道。

谢谢!

1 个答案:

答案 0 :(得分:1)

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

@Override
@EntityGraph(attributePaths = { "sentMessages", "receivedMessages" })
Optional<Profile> findById(int id);

在最后一个示例中,因为您将 // 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 相同的输入 - 但事实并非如此。这可能会导致运行时错误。

当您看到此类错误消息时,请记住 TypeScript 只是警告您您没有遵守其差异规则。如果您想了解更多有关此内容的信息,有关于它和其他 presentation 的优秀post

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

fnC

我通常使用 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,这取决于您。

相关问题