这个问题暴露了 TypeScript 的一些缺点。简短的回答是:你可能不能做你想做的事,至少不能以你想做的方式做。(我们在问题的评论中对此进行了一些讨论。)
为了类型安全,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;
}
不过,这仍然不理想。我们现在可以测试某个东西是否是 a 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");
}
这些都不是理想的。如果你能避免它,你应该;改用 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,您可能无法在任何地方进行严格的类型检查,但您应该尽可能地依赖它。