检查打字稿中数组的类型
Check types of an array in typescript
显然,Typescript 似乎与 AST 配合得很好。如果我检查 x.type == "Abc"
,那么下一行,打字稿知道 x
是类型 Abc
。请注意,我用它来检查带有 JSDOC 格式类型注释的 JS 文件。但我想这同样适用于纯打字稿文件
但是,我在测试元素数组时遇到问题。
第一个示例有效,因为我遍历了每个元素,并且仅在检查类型时才推送它。所以 typescript 正确地将类型 Property[]
推断为函数的 return 类型
/**
* @param {ObjectExpression} objectAst
*/
function getPropertiesList(objectAst) {
let propertiesList = []
for (let p of objectAst.value.properties) {
if (p.type == "Property")
propertiesList.push(p)
else
throw new Error("Properties field has elements that aren't of type `Property`")
}
return propertiesList
}
然而,这个示例在功能上是相同的(但在我看来更清晰并且不创建新数组)不起作用。推断类型为 (SpreadElement|Property|ObjectMethod|ObjectProperty|SpreadProperty)[]
。所以它没有考虑支票。
/**
* @param {ObjectExpression} objectAst
*/
function getPropertiesList(objectAst) {
let propertiesList = objectAst.value.properties
if (!propertiesList.every(p => p.type == "Property"))
throw new Error("Properties field has elements that aren't of type `Property`")
return propertiesList
}
任何人都可以深入了解打字稿如何以不同的方式处理一种情况吗?
Typescript 可以使用检查来使特定类型更具体(如第一个示例所示),但显然它无法对数组执行这些检查。
这可以被认为是 typescript 编译器中的一个错误(因为这两段代码显然应该 return 相同的类型)?
编辑:为了提供一些上下文和可测试性,我从 recast
中导入了如下类型:
/**
* @typedef { import('recast').types.namedTypes.ObjectExpression} ObjectExpression
* @typedef { import('recast').types.namedTypes.Property} Property
*/
问题是编译器不理解 array.every()
在控制流传递给函数时可以用作 type guard on the type of array
. Furthermore, the callback function p => p.type == "Property"
is also not inferred to be a type guard on the type of p
. The compiler is pretty good about analyzing inline code for potential type narrowing, but it pretty much gives up (see microsoft/TypeScript#9998)。
如果您希望 TypeScript 理解调用 boolean
返回函数作为类型保护,您需要手动将此类函数注释为 user-defined type guard。像 foo(x: T): boolean
这样的函数可以更改为 foo(x: T): x is U
,其中“x is U"
是一个 类型的谓词 。如果 foo(val)
returns true
,那么编译器会将 val
缩小为 U
。否则不会。
对于回调,这需要将p => p.type == "Property"
更改为(p): p is Property => type == "Property"
。对于 array.every()
,嗯,该方法是 declared in the standard library inside the Array<T>
interface. Luckily, you are allowed to merge in extra method overloads to interfaces (beware that if your code is in a module you may have to specifically use global augmentation 添加到全局接口,如 Array<T>
)。它看起来像这样:
interface Array<T> {
every<U extends T>(cb: (x: T) => x is U): this is Array<U>;
}
现在编译器会发现,如果回调是类型保护函数,那么 every()
本身就充当类型保护。您的代码将按预期工作:
function getPropertiesList(objectAst: ObjectAST): Property[] {
let propertiesList = objectAst.value.properties
if (!propertiesList.every((p): p is Property => p.type == "Property"))
throw new Error("Properties field has elements that aren't of type `Property`")
return propertiesList
}
不过,单次使用 every()
可能工作量太大。实际上,您可能应该只使用 type assertion 并继续。类型断言适用于您比编译器更了解类型的情况;这是使用一个的合理时间:
function getPropertiesListAssert(objectAst: ObjectAST): Property[] {
let propertiesList = objectAst.value.properties
if (!propertiesList.every(p => p.type == "Property"))
throw new Error("Properties field has elements that aren't of type `Property`")
return propertiesList as Property[]; // assert
}
好的,希望对您有所帮助;祝你好运!
显然,Typescript 似乎与 AST 配合得很好。如果我检查 x.type == "Abc"
,那么下一行,打字稿知道 x
是类型 Abc
。请注意,我用它来检查带有 JSDOC 格式类型注释的 JS 文件。但我想这同样适用于纯打字稿文件
但是,我在测试元素数组时遇到问题。
第一个示例有效,因为我遍历了每个元素,并且仅在检查类型时才推送它。所以 typescript 正确地将类型 Property[]
推断为函数的 return 类型
/**
* @param {ObjectExpression} objectAst
*/
function getPropertiesList(objectAst) {
let propertiesList = []
for (let p of objectAst.value.properties) {
if (p.type == "Property")
propertiesList.push(p)
else
throw new Error("Properties field has elements that aren't of type `Property`")
}
return propertiesList
}
然而,这个示例在功能上是相同的(但在我看来更清晰并且不创建新数组)不起作用。推断类型为 (SpreadElement|Property|ObjectMethod|ObjectProperty|SpreadProperty)[]
。所以它没有考虑支票。
/**
* @param {ObjectExpression} objectAst
*/
function getPropertiesList(objectAst) {
let propertiesList = objectAst.value.properties
if (!propertiesList.every(p => p.type == "Property"))
throw new Error("Properties field has elements that aren't of type `Property`")
return propertiesList
}
任何人都可以深入了解打字稿如何以不同的方式处理一种情况吗?
Typescript 可以使用检查来使特定类型更具体(如第一个示例所示),但显然它无法对数组执行这些检查。
这可以被认为是 typescript 编译器中的一个错误(因为这两段代码显然应该 return 相同的类型)?
编辑:为了提供一些上下文和可测试性,我从 recast
中导入了如下类型:
/**
* @typedef { import('recast').types.namedTypes.ObjectExpression} ObjectExpression
* @typedef { import('recast').types.namedTypes.Property} Property
*/
问题是编译器不理解 array.every()
在控制流传递给函数时可以用作 type guard on the type of array
. Furthermore, the callback function p => p.type == "Property"
is also not inferred to be a type guard on the type of p
. The compiler is pretty good about analyzing inline code for potential type narrowing, but it pretty much gives up (see microsoft/TypeScript#9998)。
如果您希望 TypeScript 理解调用 boolean
返回函数作为类型保护,您需要手动将此类函数注释为 user-defined type guard。像 foo(x: T): boolean
这样的函数可以更改为 foo(x: T): x is U
,其中“x is U"
是一个 类型的谓词 。如果 foo(val)
returns true
,那么编译器会将 val
缩小为 U
。否则不会。
对于回调,这需要将p => p.type == "Property"
更改为(p): p is Property => type == "Property"
。对于 array.every()
,嗯,该方法是 declared in the standard library inside the Array<T>
interface. Luckily, you are allowed to merge in extra method overloads to interfaces (beware that if your code is in a module you may have to specifically use global augmentation 添加到全局接口,如 Array<T>
)。它看起来像这样:
interface Array<T> {
every<U extends T>(cb: (x: T) => x is U): this is Array<U>;
}
现在编译器会发现,如果回调是类型保护函数,那么 every()
本身就充当类型保护。您的代码将按预期工作:
function getPropertiesList(objectAst: ObjectAST): Property[] {
let propertiesList = objectAst.value.properties
if (!propertiesList.every((p): p is Property => p.type == "Property"))
throw new Error("Properties field has elements that aren't of type `Property`")
return propertiesList
}
不过,单次使用 every()
可能工作量太大。实际上,您可能应该只使用 type assertion 并继续。类型断言适用于您比编译器更了解类型的情况;这是使用一个的合理时间:
function getPropertiesListAssert(objectAst: ObjectAST): Property[] {
let propertiesList = objectAst.value.properties
if (!propertiesList.every(p => p.type == "Property"))
throw new Error("Properties field has elements that aren't of type `Property`")
return propertiesList as Property[]; // assert
}
好的,希望对您有所帮助;祝你好运!