我有一些代码,如下所示:
type UserTypes = "user" | "super-user" | "admin" ;
function getUserType() : UserTypes {
}
function getAdminPrivs(userType: "user" | "super-user") : null
function getAdminPrivs(userType: "admin") : string
// nb. we need to double declare the final line
// see: https://stackoverflow.com/questions/70146081/why-does-an-overloaded-function-declaration-sometimes-force-useless-type-narrowi
function getAdminPrivs(userType: UserTypes) : null | string
function getAdminPrivs(userType: UserTypes) : null | string {
if(userType === "admin"){
return "hello"
}
return null;
}
// "user" | "super-user" | "admin"
const userType = getUserType();
// string| null
const result = getAdminPrivs(userType);
if(userType === "user" || userType === "super-user"){
}
else {
// string | null
// I really want this to be just string
result
}
在这里,我想要 TypeScript 做的是缩小
result
的类型,作为检查 userType
类型的结果。这可能吗?
举个例子,如果我稍微改变一下我的代码:
const userType = getUserType();
if(userType === "user" || userType === "super-user"){
// null
const result = getAdminPrivs(userType);
}
else {
//string
const result = getAdminPrivs(userType);
}
这在语义上是相同的,但我们获得了类型缩小的优势。 (我无法在实际用例中使用此技术,因为
getAdminPrivs
实际上是一个 React hook,并且不允许有条件地调用 React hook)
TypeScript 的 narrowing 仅适用于明确编程到类型检查器中的特定情况。它的控制流分析相当强大,但它只适用于“向前”的方向。也就是说,给定的检查可以缩小检查后其余范围的值,但它不能返回并重新解释先前分析的代码。除了最简单的程序之外,这对于所有程序来说都会非常糟糕。 类似的东西
const userType = getUserType();
if(userType === "user" || userType === "super-user"){
const result = getAdminPrivs(userType); // null
}
else {
const result = getAdminPrivs(userType); //string
}
之所以有效,是因为
userType
可以缩小,userType
的后续使用反映了这种缩小。 但是
const userType = getUserType();
const result = getAdminPrivs(userType);
if(userType === "user" || userType === "super-user"){
result; // want null, but still string | null
}
else {
result; // want string, but still string | null
}
不可能在 TypeScript 中工作,因为它必须在之前的所有话语中缩小
userType
,然后 retroactively 缩小 userType
,然后重新分析 getAdminPrivs
的返回类型,并重新分析 result
的类型
。对于人类来说,进行此类分析很容易,因为您知道自己在寻找什么。但是想象一下编译器会花费多少精力来分析任意程序,其中任何值的每次检查都可能影响整个程序该值的类型,这会影响依赖于该值的任何内容的类型,从而影响任何内容的类型这取决于那些值等等。
这基本上不可能以任何可扩展的方式发生,因此这是 TypeScript 的基本限制。
有关此限制的来源,请参阅 microsoft/TypeScript#41926,其中 TS 团队开发负责人说
控制流缩小的工作原理是寻找适用于相关变量的语法。在这个例子中,似乎没有任何东西直接影响[
],所以它有其未缩小的类型。result
我们没有性能预算来进行正确检测这种模式所需的全宇宙反事实分析。
另请参阅 microsoft/TypeScript#56221 上的类似评论:
缩小的工作原理是形成可能影响变量类型的事物的控制流图,并检查这些事物以查看它们是否是看起来可能导致某些内容缩小的语法形式。它不是一个约束求解器风格的东西,可以处理逻辑运算符的所有其他组合和排列,因此,如果您的逻辑依赖于某种反事实归纳,则它不会通过缩小而被拾取。值得庆幸的是,在大多数情况下,这通常对应于更具可读性的代码。
最后一点本质上是说,如果你正在编写 TS 无法分析的代码,那么对于人类来说可能也会更困难,并且如果你重写它(例如,由于 React hook 限制,通过冗余形式你无法编写) )人类和TS都会更幸福。
我不知道以下内容是否适用于您的用例,但您可以将
userType
重新打包为 discriminated union 的判别属性,如下所示:
type TypeAndPrivs =
{ userType: "user" | "super-user", adminPrivs: null } |
{ userType: "admin", adminPrivs: string }
function getTypeAndAdminPrivs(userType: UserTypes): TypeAndPrivs {
return (userType === "admin") ? { userType, adminPrivs: "hello" } : { userType, adminPrivs: null }
}
然后您可以解构它并使用结果为您提供您期望的分析:
const { userType, adminPrivs: result } = getTypeAndAdminPrivs(getUserType());
if (userType === "user" || userType === "super-user") {
result; // null
}
else {
result; // string
}
是的,它仍然有点多余,但至少你不会多次调用
getAdminPrivs()
。