【问题标题】:How to write an "isX" type guard for a generic type List<T> without knowing the type of T?如何在不知道 T 类型的情况下为泛型类型 List<T> 编写“isX”类型保护?
【发布时间】:2019-12-30 05:54:28
【问题描述】:

我正在使用 TypeScript 编写一个类型化的 List 实现,作为一个普通对象,其结构使用 type 表达式来确保。我选择把它写成一个普通对象而不是一个类,因为我相信一个普通对象更容易让我确保它的不变性。该对象的类型定义如下:

type List<T> = Readonly<{
  head: T;
  tail: List<T> | void;
}>;

我想为这个结构编写一个类型保护,这样我就可以在其他代码中确保我正在对List 对象进行操作。我当前的类型保护如下所示:

export function isList(list: any): list is List {
  if (isNil(list)) {
    return false;
  }

  return ("head" in list && "tail" in list);
}

这个实现在第一行有一个错误,在 list is List:TypeScript 抱怨类型 List&lt;T&gt; 是通用的,因此,我必须在引用中提供一个类型。

此函数的预期输出如下:

import {getListSomehow, isList} from "list";

const list = getListSomehow("hello", "world", "etc...");
const arr = ["hello", "world", "etc..."];

console.log(isList(list)); // true
console.log(isList(arr)); // false
console.log(isList("a value")); // false

有没有什么方法可以不用知道T 的类型就可以写出这种类型保护?如果没有,有没有办法以某种方式检索这种类型,同时仍然允许函数获取任何值?

【问题讨论】:

  • 你试过list is List&lt;any&gt;吗?
  • 我应该指出,您可能以错误的方式处理此问题。与其让isList 检查list 是否看起来像一个列表并且嘎嘎叫像一个列表,不如只检查变量是否是List&lt;T&gt; 并完全避免使用未知类型的变量(@ 987654333@).
  • @Zenexer - Facepalms. 我不敢相信我错过了。我在list 类型定义中使用它,所以我不知道为什么我没有考虑在List&lt;T&gt; 中使用它。另外,我如何直接检查它是否是List&lt;T&gt;?
  • 将 TypeScript 用于其预期目的:如果变量声明为 List&lt;T&gt; 类型,则它是 List&lt;T&gt;。首先你不需要测试,因为如果你的代码不是List&lt;T&gt;,但你像使用它一样使用它,你的代码将无法编译——除非你使用any,这打破了TypeScript的保证。就目前而言,我可以创建一个通过isList 但实际上不是List&lt;T&gt; 的对象:{head: 1, tail: {head: "a"}}
  • 好的。谢谢。

标签: typescript typescript-generics typeguards


【解决方案1】:

这个问题暴露了 TypeScript 的一些缺点。简短的回答是:你可能不能做你想做的事,至少不能以你想做的方式做。 (我们在问题的 cmets 中对此进行了一些讨论。)

为了类型安全,TypeScript 通常依赖于编译时类型检查。与 JavaScript 不同,TypeScript 标识符有一个类型;有时这是明确给出的,有时是由编译器推断的。通常,如果您尝试将标识符视为与其已知类型不同的类型,编译器会报错。

这给与现有 JavaScript 库的接口带来了问题。 JavaScript 中的标识符没有类型。此外,无法在运行时可靠地检查值的类型。

这导致了类型保护的出现。可以在 TypeScript 中编写一个函数,其目的是告诉编译器如果函数返回 true,则传递给它的参数之一是已知的特定类型。这允许您实现自己的鸭子类型,作为 JavaScript 和 TypeScript 之间的一种联系。类型保护函数看起来有点像这样:

const isDuck = (x: any): x is Duck => looksLikeADuck(x) && quacksLikeADuck(x);

这不是一个很好的解决方案,但只要您注意检查类型的方式,它就可以工作,并且实际上没有任何替代方案。

但是,类型保护不适用于泛型类型。请记住,类型保护的目的是获取any 输入并确定它是否是特定类型。我们可以通过泛型类型部分实现:

function isList(list: any): list is List<any> {
    if (isNil(list)) {
        return false;
    }

    return "head" in list && "tail" in list;
}

不过,这仍然不理想。我们现在可以测试某个东西是否是List&lt;any&gt;,但我们不能测试更具体的东西,比如List&lt;number&gt;——没有它,结果可能对我们不会特别有用,因为我们所有的封装值仍然是未知类型。

我们真正想要的是可以检查某物是否为List&lt;T&gt; 的东西。这有点棘手:

function isList<T>(list: any): list is List<T> {
    if (isNil(list)) {
        return false;
    }

    if (!("head" in list && "tail" in list)) {
        return false;
    }

    return isT<T>(list.head) && (isNil(list.tail) || isList<T>(list.tail));
}

现在我们必须定义isT&lt;T&gt;:

function isT<T>(x: any): x is T {
    // What goes here?
}

但我们不能这样做。我们无法在运行时检查一个值是否是任意类型。我们可以解决这个问题:

function isList<T>(list: any, isT: (any) => x is T): list is List<T> {
    if (isNil(list)) {
        return false;
    }

    if (!("head" in list && "tail" in list)) {
        return false;
    }

    return isT(list.head) && (isNil(list.tail) || isList<T>(list.tail, isT));
}

现在是调用者的问题:

function isListOfNumbers(list: any): list is List<number> {
    return isList<number>(list, (x): x is number => typeof x === "number");
}

这些都不理想。如果你能避免它,你应该;改用 TypeScript 的严格类型检查。我已经提供了示例,但首先,我们需要对List&lt;T&gt; 的定义进行调整:

type List<T> = Readonly<null | {
    head: T;
    tail: List<T>;
}>;

现在,使用该定义,而不是:

function sum(list: any) {
    if (!isList<number>(list, (x): x is number => typeof x === "number")) {
        throw "Panic!";
    }

    // list is now a List<number>--unless we wrote our isList or isT implementations incorrectly.

    let result = 0;

    for (let x = list; x !== null; x = x.tail) {
        result += list.head;
    }

    return result;
}

用途:

function sum(list: List<number>) {
    // list is a List<number>--unless someone called this function directly from JavaScript.

    let result = 0;

    for (let x = list; x !== null; x = x.tail) {
        result += list.head;
    }

    return result;
}

当然,如果您正在编写库或处理纯 JavaScript,您可能无法在任何地方都进行严格的类型检查,但您应该尽可能地依赖它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-13
    • 1970-01-01
    相关资源
    最近更新 更多