问题描述
当我有一个变量键入“ 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
缩小到基元或特定对象类型是可取的,但不希望缩小到Object
或Function
,因为这些类型几乎是无用的,最好使用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
,而不是Object
。 object
类型的意思是“非原始对象”,并且是通过TypeScript 2.2在microsoft/TypeScript#12501中引入的。那是在 之后,缩小了从any
的公关。
换句话说:在哈希何时缩小any
的逻辑时,没有考虑 object
类型。可能他们会将object
与Object
和Function
一起作为例外。或者可能不是;很难确定。也许有人甚至可以在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!
一个更准确的类型,以表示您对它的实际了解。例如,也许您知道它可能是myObjectVariable
,string
或某个类型为number
的{{1}}属性的对象。然后,您将使用它:
propertyName
并且很高兴编译器删除了string
和const 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
来不安全。
无论如何,希望能说明情况并为您提供一些选择。祝你好运!