TypescriptはどうやらASTで非常にうまく機能しているようです。をチェックするx.type == "Abc"
と、次の行、typescriptx
はタイプがであることがわかりAbc
ます。これを使用して、JSDOC形式の型注釈付きのJSファイルを型チェックすることに注意してください。しかし、私は同じことが純粋なタイプスクリプトファイルにも当てはまると思います
ただし、要素の配列のテストに問題があります。
この最初の例は機能します。これは、すべての要素をループし、型がチェックされている場合にのみプッシュするためです。だから、typescriptです正しくタイプを推定Property[]
機能の戻り値の型として
/**
* @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が1つのケースを他のケースとどのように異なる方法で処理するかについて誰かが洞察を与えることができますか?
Typescriptは、チェックを使用して特定のタイプをより具体的にすることができます(最初の例が機能するため)が、配列に対してそれらのチェックを実行できないようです。
これはtypescriptコンパイラのバグと見なすことができますか(両方のコードが明らかに同じ型を返すはずなので)?
編集:コンテキストとテスト容易性を与えるためにrecast
、次のようにタイプをインポートしました:
/**
* @typedef { import('recast').types.namedTypes.ObjectExpression} ObjectExpression
* @typedef { import('recast').types.namedTypes.Property} Property
*/
問題は、コンパイラがarray.every()
、の型の型ガードとして使用できることを理解していないことですarray
。さらに、コールバック関数p => p.type == "Property"
は、の型の型ガードであるとは推測されませんp
。コンパイラーは、潜在的な型の絞り込みについてインラインコードを分析するのに非常に優れていますが、制御フローが関数に渡されると、ほとんど諦めます(microsoft / TypeScript#9998を参照)。
TypeScriptに、boolean
戻り関数の呼び出しが型ガードとして機能することを理解させたい場合は、ユーザー定義の型ガードなどの関数に手動で注釈を付ける必要があります。以下のような機能がfoo(x: T): boolean
に変更することができfoo(x: T): x is U
、「どこx is U"
で型述語場合。foo(val)
リターンはtrue
、コンパイラが狭くなりますval
しU
。そうでなければ、それはしません。
コールバックの場合、これをに変更p => p.type == "Property"
する必要があります(p): p is Property => type == "Property"
。の場合array.every()
、そのメソッドはインターフェイス内の標準ライブラリで宣言されていArray<T>
ます。幸い、追加のメソッドオーバーロードをインターフェイスにマージできます(コードがモジュール内にある場合は、グローバル拡張を使用してのようなグローバルインターフェイスに追加する必要がある場合があることに注意してください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()
ただし、これは1回の使用には多すぎる作業になる可能性があります。実際には、おそらくタイプアサーションを使用して先に進む必要があります。型アサーションは、コンパイラーよりも型についてよく知っている状況を対象としています。これは、次のいずれかを使用するのに妥当な時期です。
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
}
さて、それが役立つことを願っています。幸運を!
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加