typescript - TS2345 即使在不可能的路径上

标签 typescript

export interface MyDoc{
  field: string;
}

function insert(doc: MyDoc) { /* db insert code here */ }

let x; // simulate undefined value
let doc = { field: x };

console.log(JSON.stringify(doc));
if (x === undefined) { // also does not prevent with doc.field === undefined
  return; // return if x is undefined
}

insert(doc); // causes TS2345: Argument of type error

就我而言,这是不可能的,但感觉像是代码味道。我如何通知 TS 类型检查器 doc 不能包含值为 undefinedfield,即使可以将其用于其他目的(就像日志记录一样)

最佳答案

几乎所有narrowing TypeScript 表现出的行为仅作用于正在检查的值。也就是说,对表达式 exp1 的某些操作可能会导致 exp1 的表观类型发生变化,但它几乎永远不会改变其他表达式 exp2< 的表观类型。/.

有一些值得注意的异常(exception):检查 discriminated union 的判别属性object 会缩小对象本身的类型;有时你可以save the result of a type guard check to a boolean value然后稍后检查该值将缩小原始表达式的范围;你可以destructure discriminated unions分成单独的变量,这样检查一个变量就会缩小另一个变量的范围。

但是您正在做的事情不属于这些异常(exception)之一。该语言中的任何内容都不会导致对 x 的检查影响 doc 的表观类型。即使他们曾经实现 microsoft/TypeScript#42384 的建议将属性类型保护扩展到非受歧视联合的对象,它仍然不会给你这种行为。为了一般支持此类事情,编译器必须执行“完整反事实分析”,如 microsoft/TypeScript#46915 中所述。 ,其中跟踪每个表达式的每个可能的缩小,这对于编译器来说将是非常昂贵的。因此,按照您想要的方式工作的希望不大。

那么你能做什么呢?


如果您不想重构代码,您可以随时 use a type assertion只是告诉编译器应该将 doc 视为 MyDoc:

if (x === undefined) throw new Error();
insert(doc as MyDoc); // assert

现在没有错误了。但现在您负责验证类型安全,因为编译器不能。如果执行错误的检查,仍然不会出现错误:

if (x !== undefined) throw new Error(); // oops
insert(doc as MyDoc); // assert still "works"

所以要小心。


如果您更想让编译器遵循您的逻辑,则必须重构为受支持的缩小方法。最灵活的方法是实现 user-defined type guard function这准确地解释了您对编译器所做的缩小范围。它仍然只适用于您正在检查的值,但至少您可以表达更复杂的类型。

例如,您可以编写一个检查来专门查看某个值是否是有效的 MyDoc,可能如下所示:

function isMyDoc(x: any): x is MyDoc {
  return x && ("field" in x) && (typeof x.field === "string");
}

编译器不会验证该函数的实现,但现在您可以在其他地方随意使用它:

if (!isMyDoc(doc)) throw new Error();
insert(doc); // okay

或者,您可以编写一个更通用的防护来检查对象是否在给定键处具有已定义的属性:

function hasDefinedProp<T extends object, K extends keyof T>(
  obj: T, key: K): obj is T & { [P in K]-?: Exclude<T[K], undefined> } {
  return typeof obj[key] !== "undefined"
}

这种输入有点复杂,但让我们看看它的实际效果:

if (!hasDefinedProp(doc, "field")) throw new Error();
doc;
// { field: string | undefined } & { field: string }
insert(doc); // okay

这是有效的,因为 doc 已从 { field: string |未定义}{ 字段:字符串 | undefined } & { field: string} 可分配给 MyDochasDefinedProp() 类型保护函数比 isMyDoc() 具有更多的潜在用例,因此根据代码库中进行此类检查的频率,它可能是更有用。


但是,如果您对干净/清晰的代码感兴趣,最好的方法是重构,以便您在开始别名变量之前进行检查:

const x = Math.random() < 0.5 ? "abc" : undefined;
if (x === undefined) throw new Error(); // do this first
let doc = { field: x }; // then this
insert(doc); // okay

这种重构并不总是适用于每个用例,但编译器和人类读者都会更容易理解这种明显的类型保护流程。

Playground link to code

关于typescript - TS2345 即使在不可能的路径上,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74791978/

相关文章:

javascript - Angular2 http调用使测试执行两次

javascript - angular2 i18n 的替代品

javascript - Angular 2 - 表单提交未检索 value 属性

typescript - 函数应返回非空参数的类型

reactjs - namespace 声明不能位于与其合并的类或函数之前

typescript - Firebase/typescript 问题 - 当我使用 this.ref.getAuth().password.email 时它有效但我收到 FirebaseAuthData 类型错误

Angular2 viewContainerRef.createComponent 正常工作

html - 如何导航(重定向)到 Angular 中的另一个页面?

asp.net - Typescript 2.7 中的严格类初始化

reactjs - 如何将动态 Prop 传递给接受对象联合作为其类型的 React 组件?