TypeScript:聯合類型的對象可能為 null
我有一個基礎介面,其中至少需要兩個屬性中的一個。
interface Base {
someProp: { subProp: string; } | null;
condProp1?: boolean;
condProp2?: boolean;
}
interface WithProp1 extends Base {
condProp1: true;
}
interface WithProp2 extends Base {
condProp2: true;
}
export type MyType = WithProp1 | WithProp2;
當我在檢查過 someProp 不為 null 並且在檢查 condProp1 或 condProp2 是否為真之後嘗試使用 MyType,編譯器仍然認為 someProp 可能是 null。
function doSomething(myType: MyType) {
if (myType.someProp && (
myType.condProp1 && myType.someProp.subProp === 'foo' ||
myType.condProp2 && myType.someProp.subProp === 'bar')) {
console.log("It's true, isn't it? It's really true.");
}
}
在 doSomething() 中,myType.someProp.subProp 出現編譯時錯誤,提示 someProp 可能為 null。如果我在函數簽名中將 MyType 換成 Base,則不會發生這種情況。
// Compiles fine
function doSomethingBase(myType: Base) {
if (myType.someProp && (
myType.condProp1 && myType.someProp.subProp === 'foo' ||
myType.condProp2 && myType.someProp.subProp === 'bar')) {
console.log("It's true, isn't it? It's really true.");
}
}
將順序改成 myType.someProp.subProp === 'foo' && myType.condProp1 能作為第一行的變通方法,但對第二行無效。有人知道為什麼會這樣或怎麼解決嗎(我可以使用 !,但其實不應該需要)?看起來像是編譯器的錯誤。
我正在使用 TSC 版本 3.5.3。TypeScript 遊樂場
答案#1
我懷疑這實際上是編譯器錯誤;我對內部原理並不是非常熟悉,所以不能確定,但我懷疑編譯器把初始的保護語句評估為類型 Base,然後把內部的語句評估為 WithPropX,並且遺失了 myType 作為 WithPropX 已經被防護過 null 的 someProp 的信息。
你可以透過反轉檢查的順序來解決這個問題,這樣更具體的類型推斷會在條件中較早發生(然後 someProp 的 null 檢查會在編譯器知道 myType 是 WithPropX 而不是 Base 的時候發生):
function doSomething(myType: MyType) {
if (myType.condProp1 && myType.someProp && myType.someProp.subProp === 'foo' ||
myType.condProp2 && myType.someProp && myType.someProp.subProp === 'bar') {
console.log("It's true, isn't it? It's really true.");
}
}
這個問題似乎可能與你在這裡觀察到的情況有關。
答案#2
我喜歡 @Chris Helad 說的:
我懷疑編譯器將初始的保護子句評估為 Base 類型,然後將內部子句評估為 WithPropX,並且失去了 myType 作為 WithPropX 已經對 null 的 someProp 進行過保護的信息。
所以,編譯器,可能,是這樣工作的:
function doSomething(myType: MyType) {
if (myType.someProp && ( // myType is type of WithProp1 | WithProp2 AND someProp is defined
myType.condProp1 && myType.someProp.subProp === 'foo' || // condProp1 is defined, so, myType is typeof WithProp1 (forgot that someProp is defined)
myType.condProp2 && myType.someProp.subProp === 'bar')) { // condProp2 is defined, so, myType is typeof WithProp2 (forgot that someProp is defined)
console.log(`It's true, isn't it? It's really true.`);
}
}
為了擊敗它,你可以定義一個簡單的類型保護來告訴編譯器實際的類型:
function hasProp(t: MyType): t is MyType & {someProp: NonNullable<MyType['someProp']>} {
return !!t.someProp;
}
function doSomething(myType: MyType) {
if (hasProp(myType) && (
myType.condProp1 && myType.someProp.subProp === 'foo' ||
myType.condProp2 && myType.someProp.subProp === 'bar')) {
console.log(`It's true, isn't it? It's really true.`);
}
}
