typescript - 映射类型和泛型函数输入错误

标签 typescript

我编写了一个相当简单的基于映射类型的代码,由于某种原因它不想进行类型检查。

首先,定义输入和输出:

interface Validated<T> {
  valid: boolean;
  value: T;
} 

interface FieldInputs {
  name: string;
  price: number;
}

interface ParsedFields {
  name: Validated<string>;
  price: Validated<number>;
}

定义解析器类型和解析器映射:

type FieldKey = keyof FieldInputs & keyof ParsedFields;
type FieldParser<F extends FieldKey> = (value?: FieldInputs[F]) => ParsedFields[F];
type FieldParsers = {
  [F in FieldKey]: FieldParser<F>;
};

declare let fieldParsers: FieldParsers;

现在这个非常简单的通用函数无法进行类型检查:

function update<F extends FieldKey>(field: F, value: FieldInputs[F]) {
  const parser: FieldParser<F> = fieldParsers[field];
  parser.apply(value);
}

给出以下错误(--strictFunctionTypes):

Type 'FieldParsers[F]' is not assignable to type 'FieldParser<F>'.
  Type 'FieldParser<"name"> | FieldParser<"price">' is not assignable to type 'FieldParser<F>'.
    Type 'FieldParser<"name">' is not assignable to type 'FieldParser<F>'.
      Types of parameters 'value' and 'value' are incompatible.
        Type 'FieldInputs[F]' is not assignable to type 'string'.
          Type 'string | number' is not assignable to type 'string'.
            Type 'number' is not assignable to type 'string'.

我错过了什么?

Playground Link

最佳答案

编译器正在保护您免受不太可能发生的事情的影响,您需要决定如何解决它(剧透警报:使用 type assertion )


想象一下如果我这样做:

const field = Math.random() < 0.5 ? "name" : "price";
const value = Math.random() < 0.5 ? "Widget" : 9.95;
update(field, value); // no error

在本例中,field类型为FieldKeyvalue类型为FieldInputs[FieldKey] ,并且有 50% 的可能性它们不匹配。尽管如此,编译器不会警告您:它推断 FFieldKey (这对于它来说是完全有效的),以及对 update() 的调用是允许的。

里面执行update() ,有警告 FieldParsers[F]可能不是FieldParser<F> 。如果FFieldKey如上所述,这种不匹配变得明显。 FieldParsers[F]将是FieldParser<'name'> | FieldParser<'price'> ,但是FieldParser<F>FieldParser<'name' | 'price'> 。前者是解析 string 的东西。 解析 number 的东西。后者可以解析 string 。或number 。它们不相同(由于 contravariance of function parameters 使用 --strictFunctionTypes 启用)。当上面的代码最终调用 update("name", 9.95) 时,这些类型之间的差异就暴露出来了。然后你尝试解析 numberstring解析器。你想要一个FieldParser<F> ,但你拥有的只是 FieldParsers[F] .


现在备份一下,是否有人可能玩这样的游戏,其中F是值(value)观的结合?如果是这样,那么您可能想要更改 update() 的定义明确禁止F不再是单个字符串文字。类似...

type NotAUnion<T, U = T> =
  U extends any ? [T] extends [U] ? T : never : never;

declare function update<F extends FieldKey>(
  field: F & NotAUnion<F>, 
  value: FieldInputs[F]
);

但这可能有点过分了,它仍然无法解决 update() 实现中的警告。 。编译器根本不够聪明,无法理解 F 的值。是单个字符串文字值,并且您所做的事情是安全的。

要消除该错误,您可能需要执行 type assertion 。要么你知道没有人会故意通过加宽来搬起石头砸自己的脚FFieldKey ,或者您已通过使用类似 NotAUnion 的内容阻止调用者执行此操作。无论哪种情况,您都可以告诉编译器您知道 fieldParsers[field]将是有效的FieldParser<F> :

function update<F extends FieldKey>(field, value: FieldInputs[F]) {
  const parser = fieldParsers[field] as FieldParser<F>; // okay
  parser.apply(value);
}

这样就可以了。希望有帮助。祝你好运!

关于typescript - 映射类型和泛型函数输入错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50610521/

相关文章:

Angular 7 : "Cannot find control with unspecified name attribute"

typescript - 没有泛型的类型推断?

javascript - ..._1.default 在使用 Mocha 测试 TypeScript 时不是构造函数

typescript - 为具有所有可选属性的接口(interface)制作类型保护

javascript - 如何在 Typescript 中将 safeStringify() 添加到 JSON?

javascript - 带有 typescript require 和 .d.ts 文件的 Node.js

typescript - Angular2 - 使用服务在组件之间共享数据

typescript - Typescript 中的 nameof 关键字

javascript - 接口(interface)将键值限制为 const Typescript 的精确值

typescript - 动态键,它包含 typescript 中特定字符串和迭代整数的串联