typescript 为什么一个类型保护起作用而其他类型保护不起作用,包括类型保护函数

xpcnnkqh  于 2023-02-25  发布在  TypeScript
关注(0)|答案(2)|浏览(174)

我不知道为什么类型保护函数和给变量赋值在下面的例子中不起类型保护的作用。
有人能告诉我为什么吗?

interface UserData {
  personalInfo?: {
    name: string;
  }
}

const isUserData = (i: any): i is UserData => {
  // This is fine for our example
  return true;
};

const user: UserData = {
  personalInfo: {
    name: `Joe`,
  }
}

/*
name could be:
 - undefined: personalInfo is undefined
 - string: user.personalInfo is defined, and name is a string
*/
const usersName = user.personalInfo?.name;

if (usersName) {
  // inside this conditional we know that name is a string,
  // and that it isn't a falsy string ("")
  // Why doesn't this work??
  // TS error "user.personalInfo is possibly undefined"
  const newString:string = user.personalInfo.name;
}

// A type guard function doesn't work
if (isUserData(usersName)) {
  // TS error "user.personalInfo is possibly undefined"
  const newString: string = user.personalInfo.name;
}

// However if we use the variable directly, it works
if (user.personalInfo?.name) {
  // No TS error.
  const newString: string = user.personalInfo.name;
}

// This also works (as expected)
if (!!user.personalInfo?.name) {
  // No TS error.
  const newString: string = user.personalInfo.name;
}
9nvpjoqh

9nvpjoqh1#

TypeScript中的narrowing类型仅在特定情况下发生,这些情况已被专门实现以产生此效果。收缩支持本质上是一系列启发式规则,这些规则在许多情况下都很有用。它不是对值之间的每个关系的每个可能的逻辑含义的全面分析。
根据TS团队开发负责人对类似缩小问题microsoft/TypeScript#41926的评论:
控制流收缩的工作原理是寻找适用于相关变量的语法,在这个例子中,似乎没有任何东西直接影响value,所以它具有非收缩类型。
我们没有足够的预算来做正确检测这种模式所需的全宇宙反事实分析。
通过optional chaining对嵌套属性的直接真实性检查缩小了该属性的类型,因为这个行为是直接在microsoft/TypeScript#33821中实现的。

if (user.personalInfo?.name) {
  const newString: string = user.personalInfo.name;
}

if (!!user.personalInfo?.name) {
  const newString: string = user.personalInfo.name;
}

但是,对嵌套属性的别名副本执行的检查并没有缩小该属性的范围,因为这种缩小从未实现过,可能很难做到这一点,因为每一个复制的值都有可能触发大量额外的分析工作。
但也许在未来的某一天,如果有足够多的人提出要求,它就可以实施,毕竟类似的事情已经发生过;并且对别名条件和判别式的控制流分析也有一些支持。所以也许还有希望?但最终这种启发式分析将永远无法预测每一个可能的开发者用例,所以总是会有一些代码,人们可以看到是正确的,但编译器不能。那么你的选择是重构到编译器理解的版本,或者使用类型Assert来告诉编译器你比它聪明:

if (usersName) {
    const newString: string = user.personalInfo!.name; // <-- assert
}

这里的非空Assert操作符只是告诉编译器不要担心空的可能性,并且 * 你 * 已经验证了正确性。
Playground代码链接

q9yhzks0

q9yhzks02#

if使用了中间变量usersName,但是在条件内部使用了初始变量user.personalInfo.name,因此Typescript不能保证值在中间没有改变。
例如:

const usersName = user.personalInfo?.name;

user.personalInfo = undefined; // reassign a value to user.personalInfo

if (usersName) {
  // here, usersName is not undefined
  // but user.personalInfo is, because it was reassigned above
  const newString: string = user.personalInfo.name;
}

但这是可行的,因为在if中,Typescript肯定知道usersName已经定义。

const usersName = user.personalInfo?.name;

user.personalInfo = undefined; // reassign a value to user.personalInfo

if (usersName) {
  // here, usersName is not undefined, you can assign its value to a string
  const newString: string = usersName;
}

相关问题