如果在变量上使用_.isObject,Typescript会忽略明确声明的ANY类型,并将变量视为类型对象,从而导致属性名称错误

问题描述

我有一个变量键入“ any”并对_.isObject进行lodash检查,然后尝试访问该变量的属性时,它会引发打字错误error TS2339: Property 'propertyName' does not exist on type 'object'.

我希望打字稿继续将变量视为:any,因为这是显式声明的,并且它允许访问对象的任何属性而不会引发打字稿错误


示例

没有对象检查,没有打字稿错误

const myObjectvariable: any = service.getobject();
this.anotherVariable = myObjectvariable.propertyName;

对象检查导致错误

const myObjectvariable: any = service.getobject();
if (_.isObject(myObjectvariable) {
    this.anotherVariable = myObjectvariable.propertyName;
}

提高:error TS2339: Property 'propertyName' does not exist on type 'object'.

为什么打字稿会忽略原始类型并将其视为对象,并且我可以在不更改代码的情况下将其关闭或使其停止?

解决方法

我认为这个可能有点疏忽了如何实现从any开始的缩小。 Lodash的isObject()方法是一个user-defined type guard方法,如果返回true,它的参数变窄,但这会与any交互,您可能不满意。


最初,当一个类型防护调用一个类型为any的值的特定对象类型变窄时,它根本不会变窄并停留在any上。这使人感到困扰,因为编译器应该能够捕获这样的错误:

interface Foo { x: string; }
declare function isFoo(x: any): x is Foo;
declare const v: any;

if (isFoo(v)) {
    v // still any
    v.x.toUperCase(); // no error?
}

此问题在microsoft/TypeScript#9999中提出,并在microsoft/TypeScript#10334中得到解决。因此,现在人们得到了以下理想的行为:

if (isFoo(v)) {
    v // Foo
    v.x.toUperCase(); // error!
    // Property 'toUperCase' does not exist on type 'string'. Did you mean 'toUpperCase'?
}

但是无论如何,其目的并不是要缩小any的范围。预期的逻辑用this comment表示:从any缩小到基元或特定对象类型是可取的,但不希望缩小到ObjectFunction,因为这些类型几乎是无用的,最好使用any

因此,如果lodash的isObject()的键入方式如下:

interface LoDashStatic {
    isObject(value?: any): value is Object; // Object 
}

那么您就不会有问题了:

if (_.isObject(myObjectVariable)) {
    // still any
    this.anotherVariable = myObjectVariable.propertyName; // okay
}

但是isObject()实际上是这样声明的:

interface LoDashStatic {
    isObject(value?: any): value is object; // lowercase o object 
}

那是使用object,而不是Objectobject类型的意思是“非原始对象”,并且是通过TypeScript 2.2microsoft/TypeScript#12501中引入的。那是在 之后,缩小了从any的公关。

换句话说:在哈希何时缩小any的逻辑时,没有考虑 object类型。可能他们会将objectObjectFunction一起作为例外。或者可能不是;很难确定。也许有人甚至可以在GitHub上提交新的issue,要求使用此处链接的问题作为理由,以防止从any缩小到object

但是现在,这就是原来的样子,any缩小为object

if (_.isObject(myObjectVariable)) {
    // object
    this.anotherVariable = myObjectVariable.propertyName; // error!
}

那么,作为解决方法,您能做什么?好吧,您始终可以通过declaration merging为使用isObject()的{​​{1}}使用自己的自定义键入:

Object

或者您可以使用类型断言来将宽度“扩展”回// merge into lodash typings declare module 'lodash' { interface LoDashStatic { isObject(value?: any): value is Object; } }

any

或者,由于通常any is a problematic type,您可能想给this.anotherVariable = (myObjectVariable as any).propertyName; // error! 一个更准确的类型,以表示您对它的实际了解。例如,也许您知道它可能是myObjectVariablestring或某个类型为number的{​​{1}}属性的对象。然后,您将使用它:

propertyName

并且很高兴编译器删除了stringconst myObjectVariable: string | number | { propertyName: string } = service.getObject(); if (_.isObject(myObjectVariable)) { // {propertyName: string} this.anotherVariable = myObjectVariable.propertyName; // okay } 并为您留下了对象类型。 (这只是一个示例;您可以改用string ...或其他一些类型的并集,其中可以在number键处索引与{ [k: string]: any } | undefined兼容的类型。)>

如果您无法在代码中执行此操作,那么您就不能这样做,但这是我最强烈推荐的建议,因为它使用TypeScript的编译器来帮助确保代码类型安全,而不是寻找保持代码的方法通过坚持object不安全


无论如何,希望能说明情况并为您提供一些选择。祝你好运!

Playground link