如何在不知道 T 类型的情况下为泛型类型 List<T> 编写 "isX" 类型保护?
How to write an "isX" type guard for a generic type List<T> without knowing the type of T?
我正在用 TypeScript 编写一个类型化的 List 实现,作为一个普通对象,其结构是使用 type
表达式确保的。我选择将它写成普通对象而不是 class,因为我相信普通对象更容易让我确保其不变性。该对象的类型定义如下:
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<T>
是通用的,因此,我必须在对它的引用中提供一个类型。
此函数的预期输出如下:
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
类型的情况下编写这种类型保护?如果不是,有没有办法以某种方式检索此类型,同时仍允许该函数取任何值?
这个问题暴露了TypeScript的一些缺点。简短的回答是:你可能无法做你想做的事,至少不能以你想做的方式去做。 (我们在问题的评论中对此进行了一些讨论。)
为了类型安全,TypeScript 通常依赖于编译时类型检查。与 JavaScript 不同,TypeScript 标识符有一个类型;有时这是明确给出的,有时是由编译器推断的。通常,如果您尝试将标识符视为与其已知类型不同的类型,编译器会报错。
这给与现有 JavaScript 库的接口带来了问题。 JavaScript 中的标识符没有类型。此外,不可能在运行时可靠地检查值的类型。
这导致了类型保护的出现。可以用 TypeScript 编写一个函数,其目的是告诉编译器如果函数 returns 为真,则传递给它的参数之一已知为特定类型。这允许您实现自己的鸭子类型,作为 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<any>
,但我们无法测试更具体的东西,例如 List<number>
——如果没有它,结果可能对我们不会特别有用,因为我们所有封装的值仍然是未知类型。
我们真正想要的是可以检查某物是否为 List<T>
的东西。这变得有点棘手:
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<T>
:
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");
}
None这个比较理想。如果你能避免它,你应该;而是依赖 TypeScript 的严格类型检查。我提供了示例,但首先,我们需要调整 List<T>
:
的定义
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,你可能没有到处都进行严格类型检查的奢侈,但你应该尽可能地依赖它.
我正在用 TypeScript 编写一个类型化的 List 实现,作为一个普通对象,其结构是使用 type
表达式确保的。我选择将它写成普通对象而不是 class,因为我相信普通对象更容易让我确保其不变性。该对象的类型定义如下:
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<T>
是通用的,因此,我必须在对它的引用中提供一个类型。
此函数的预期输出如下:
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
类型的情况下编写这种类型保护?如果不是,有没有办法以某种方式检索此类型,同时仍允许该函数取任何值?
这个问题暴露了TypeScript的一些缺点。简短的回答是:你可能无法做你想做的事,至少不能以你想做的方式去做。 (我们在问题的评论中对此进行了一些讨论。)
为了类型安全,TypeScript 通常依赖于编译时类型检查。与 JavaScript 不同,TypeScript 标识符有一个类型;有时这是明确给出的,有时是由编译器推断的。通常,如果您尝试将标识符视为与其已知类型不同的类型,编译器会报错。
这给与现有 JavaScript 库的接口带来了问题。 JavaScript 中的标识符没有类型。此外,不可能在运行时可靠地检查值的类型。
这导致了类型保护的出现。可以用 TypeScript 编写一个函数,其目的是告诉编译器如果函数 returns 为真,则传递给它的参数之一已知为特定类型。这允许您实现自己的鸭子类型,作为 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<any>
,但我们无法测试更具体的东西,例如 List<number>
——如果没有它,结果可能对我们不会特别有用,因为我们所有封装的值仍然是未知类型。
我们真正想要的是可以检查某物是否为 List<T>
的东西。这变得有点棘手:
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<T>
:
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");
}
None这个比较理想。如果你能避免它,你应该;而是依赖 TypeScript 的严格类型检查。我提供了示例,但首先,我们需要调整 List<T>
:
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,你可能没有到处都进行严格类型检查的奢侈,但你应该尽可能地依赖它.