【问题标题】:Narrow TypeScript type via external function?通过外部函数缩小 TypeScript 类型?
【发布时间】:2019-01-24 20:30:23
【问题描述】:

我正在尝试编写一个外部函数来使用 TypeScript 缩小对象的类型,但是一旦我将逻辑拉出 TypeScript 似乎就失去了线程。我有以下代码:

if (element instanceof HTMLInputElement && element.checked) {
  // do something
}

但是,我想取消第一次结帐并向我们发送通知,告诉我们有什么事情发生了:

function assertType(element, expectedType) {
  if (element instanceof expectedType) { return true; }

  notify(`element ${element} wasn't expected type ${expectedType}`);

  return false;
}

if (assertType(element, HTMLInputElement) && element.checked) {
  // do something
}

TypeScript 对第一个很满意,但第二个却抱怨:

error TS2339: Property 'checked' does not exist on type 'HTMLElement'.

即使我将它减少到最低限度,它仍然会失败:

function assertType(element, expectedType) {
  return element instanceof expectedType;
}

有没有办法可以编写 assertType 函数,以便 TypeScript 能够缩小类型?我试图避免进行类型转换以确保类型与底层 html 一致,但我想我可以在紧要关头使用它。甚至可能是这样的:

if (assertType(element, HTMLInputElement) && (element as HTMLInputElement).checked) {
  // do something
}

【问题讨论】:

  • 你知道user-defined type guards吗?
  • 我见过这些,但是除非我遗漏了一些东西,否则它们看起来不支持任意类型。有没有一种方法可以做到这一点,而无需为我希望能够断言的每种类型提供单独的辅助函数?

标签: typescript


【解决方案1】:

这是user-defined type guards 和generics 的工作,因此它可以适用于任何类型。这是使您的assertType 功能的一种方法:

function assertType<T>(element: any, expectedType: new(...args: any[])=>T): element is T {
  return element instanceof expectedType;
}

这个想法是expectedType应该是一些泛型类型T的构造函数,它会返回是否element is T。让我们看看它是否有效,输入系统明智:

declare const element: Element;

if (assertType(element, HTMLInputElement) && (element.checked)) {
  // do something
}

没有错误。希望有帮助。祝你好运!


更新

如果您想让它更加通用并允许expectedType 成为与instanceof 匹配的构造函数或与typeof 匹配的字符串,该怎么办?

想要一个函数来做这似乎有点多,但如果我要尝试,我可能会使用overloads 和lookup types,像这样:

type TypeofMapping = {
  string: string,
  number: number,
  boolean: boolean,
  symbol: symbol,
  undefined: undefined,
  object: object,
  function: Function
}

function assertType<T>(element: any, expectedType: new (...args: any[]) => T): element is T;
function assertType<K extends keyof TypeofMapping>(element: any, expectedType: K): element is TypeofMapping[K];
function assertType(element: any, expectedType: new (...args: any[]) => any | keyof TypeofMapping): boolean {
  return (typeof expectedType === "string") ? typeof element === expectedType : element instanceof expectedType;
}

关键是让代码选择两个重载之一。第一个和以前一样,但第二个将expectedType 限制为运行时typeof 运算符返回的字符串之一,并通过TypeofMapping 将该字符串映射到类型。让我们再试一次:

declare const element: Element;

if (assertType(element, HTMLInputElement) && (element.checked)) {
  // do something
}

declare const otherThing: string | number;
if (assertType(otherThing, "string") && otherThing.charAt(0) === 'a') {
  // do something else
}

似乎也可以工作(虽然尚未在运行时测试)。不过我自己还是会回避那种分裂人格的功能。由你决定。再次祝你好运!

【讨论】:

  • 其实,快速跟进。如果您想让它更加通用并允许expectedType 成为与instanceof 匹配的构造函数或与typeof 匹配的字符串,该怎么办?我想我可能会为它创建两个函数,但很好奇它会是什么样子。
  • 完美,谢谢!看起来你对它的复杂性是正确的。我可能会使用 2 个函数。
猜你喜欢
  • 2019-08-10
  • 1970-01-01
  • 1970-01-01
  • 2021-10-25
  • 2020-07-22
  • 2020-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多