我在 TypeScript 型別中遇到了這個問題。我在同一介面中使用基于不同屬性的條件型別來確定一個屬性的型別。在這里,財產slicer的FruitBasket要么是AppleSlicer或BananaSlicer視type。
如果您查看該函式test,它會收到一個 的實體FruitBasket,它可以是Apple或Banana。因此,我通過檢查type是否等于其中之一來縮小型別范圍,但它仍然抱怨這basket.slicer不是確定性的。但是,它應該包含確定它是AppleSlicer.How所需的所有資訊。我該如何解決這個問題?
我正在使用 TypeScript 4.5.4。
enum Fruits {
Apple = "Apple",
Banana = "Banana",
}
// These interfaces can have completely different shapes as shown.
interface AppleSlicer { apple(): void }
interface BananaSlicer { banana(): void }
export type FruitSlicer<TType> = TType extends typeof Fruits.Apple
? AppleSlicer
: TType extends typeof Fruits.Banana
? BananaSlicer
: never
export interface FruitBasket<TType extends Fruits> {
type: TType
slicer: FruitSlicer<TType>
}
const test = (basket: FruitBasket<Fruits>) => {
if (basket.type === Fruits.Apple) {
// This gives me a compile error because basket.slicer is either AppleSlicer or BananaSlicer.
// But, it should have all information it needs to deduce that it can only be an AppleSlicer
basket.slicer.apple()
}
}
uj5u.com熱心網友回復:
這里的大問題是這FruitBasket<Fruits>不是一個適當的可區分聯合,您可以在其中檢查判別屬性(這將是type)以縮小物件的型別。它不僅不是一個有歧視的工會,甚至根本就不是一個工會。相當于
{ type: Fruits, slicer: AppleSlicer | BananaSlicer }
因此,FruitBasket<Fruits>根據該定義,這是完全有效的:
const whoops: FruitBasket<Fruits> = {
type: Fruits.Apple,
slicer: { banana() { } }
} // okay
僅僅因為type是Fruits.Apple,并不意味著slicer它將是FruitSlicer<Fruits.Apple>。因此,如果test()接受 a FruitBasket<Fruits>,則它接受whoops:
test(whoops) // no error here either
而該辦法的實施test()真的不能有把握地斷定有關什么特別basket.slicer通過查看basket.type。編譯器錯誤是有效的。哎呀。
所以你不想test接受FruitBasket<Fruits>。你想要的是 union type FruitBasket<Fruits.Apple> | FruitBasket<Fruts.Banana>,一個有區別的聯合,其中type是判別屬性。
如果你不想手動撰寫出型別(例如,有很多其他的Fruits你enum),你可以從你的版本產生這個聯盟FruitBasket<T>如下:
type FruitBasketUnion = { [F in Fruits]: FruitBasket<F> }[Fruits]
// type FruitBasketUnion = FruitBasket<Fruits.Apple> | FruitBasket<Fruits.Banana>
在這里,我們為每個in創建一個具有屬性型別的映射型別,然后立即索引到該映射型別以獲取所需的聯合。FruitBasket<F>FFruitsFruits
現在我們可以將引數型別設為fortest()并查看檢查basket.type === Fruits.Apple 是否縮小了basketto的型別FruitBasket<Fruits.Apple>:
const test = (basket: FruitBasketUnion) => {
if (basket.type === Fruits.Apple) {
basket.slicer.apple() // okay
}
}
現在test()實作編譯沒有錯誤。這最好意味著你不能再打電話test(whoops)了:
test(whoops); // error!
// ~~~~~~
// Argument of type 'FruitBasket<Fruits>' is not assignable to
// parameter of type 'FruitBasketUnion'.
所以編譯器正確地拒絕了whoops. 讓我們確保它接受FruitBasket<Fruits.Apple>并且FruitBasket<Fruits.Banana>:
test({
type: Fruits.Banana,
slicer: { banana() { } }
}); // okay
test({
type: Fruits.Apple,
slicer: { apple() { } }
}); // okay
看起來挺好的。
Playground 代碼鏈接
uj5u.com熱心網友回復:
我的首選模式是將切片器調整為通用模式,您無需進行此類檢查。(我會在幾分鐘后更新)不過,您的問題的答案是有一個明確的型別保護來正確檢查型別,這將向型別系統回傳確認它確實符合您作為特定型別的條件.
enum Fruits {
Apple = "Apple",
Banana = "Banana",
}
// These interfaces can have completely different shapes as shown.
interface AppleSlicer { apple(): void }
interface BananaSlicer { banana(): void }
export type FruitSlicer<TType> = TType extends typeof Fruits.Apple
? AppleSlicer
: TType extends typeof Fruits.Banana
? BananaSlicer
: never
export interface FruitBasket<TType extends Fruits> {
type: TType
slicer: FruitSlicer<TType>
}
const test = (basket: FruitBasket<Fruits>) => {
if (isAppleBasket(basket)) {
// This gives me a compile error because basket.slicer is either AppleSlicer or BananaSlicer.
// But, it should have all information it needs to deduce that it can only be an AppleSlicer
basket.slicer.apple()
}
else if (isBananaBasket(basket)) {
basket.slicer.banana()
}
}
function isAppleBasket(candidate: FruitBasket<Fruits>): candidate is FruitBasket<Fruits.Apple> {
return candidate.type === Fruits.Apple;
}
function isBananaBasket(candidate: FruitBasket<Fruits>): candidate is FruitBasket<Fruits.Banana> {
return candidate.type === Fruits.Banana;
}
更新
所以事實證明,轉換現有代碼并不像我想象的那么容易,因為從列舉擴展與類或介面的作業方式不同。這是我更新的示例,有一些解釋。如果您想使用類或帶有型別的純 javascript 物件,這取決于您。如果只是為了能夠使用instanceof.
這不一定向您展示我提到的配接器模式(用另一個包裝您的介面/類以使它們具有相同的功能集),但它顯示了如果您這樣做會發生什么(FruitSlicer<T extends Fruit>)
enum Fruits {
Apple = "Apple",
Banana = "Banana",
}
interface Fruit {
fruitType: Fruits;
}
class Apple implements Fruit {
public fruitType: Fruits = Fruits.Apple;
}
class Banana implements Fruit {
public fruitType: Fruits = Fruits.Banana;
}
interface SlicedFruit {
fruitType: Fruits;
numberOfSlices: number;
}
interface FruitSlicer<T extends Fruit> {
fruitType: Fruits;
canHandleFruit(fruit: Fruit): boolean;
slice(fruit: T): SlicedFruit;
}
class AppleSlicer implements FruitSlicer<Apple> {
public fruitType = Fruits.Apple;
public canHandleFruit(fruit: Fruit): boolean {
return fruit.fruitType === this.fruitType;
}
public slice(fruit: Apple): SlicedFruit{
return {
// if we turn the number of slices into a property,
// we could make an abstract base class that does this work for us
// for any future fruit.
fruitType: fruit.fruitType,
numberOfSlices: 8
}
}
}
class BananaSlicer implements FruitSlicer<Banana> {
public fruitType = Fruits.Banana;
public canHandleFruit(fruit: Fruit): boolean {
return fruit.fruitType === this.fruitType;
}
public slice(fruit: Banana): SlicedFruit {
return {
fruitType: Fruits.Banana,
numberOfSlices: 15
}
}
}
const allSlicers: FruitSlicer<Fruit>[] = [ new AppleSlicer(), new BananaSlicer() ]
// in terms of objects, a basket can hold several types of fruit. We could make a basket of
// all bananas or all apples, but it doesn't make sense to have ONLY that option.
// this example has a full basket of any fruit type, and we decide for each fruit
// what type of slicer to use later on.
const basket: Fruit[] = [ new Apple(), new Banana(), new Banana(), new Apple()];
basket.forEach(f => {
// allSlicers.find could make use of the factory pattern,
// and could be more performant with a map lookup, etc.
const slicedResult: SlicedFruit | undefined = allSlicers.find(s => s.canHandleFruit(f))?.slice(f);
console.log(!slicedResult ? 'null' : `${slicedResult?.fruitType} sliced ${ slicedResult.numberOfSlices } ways!`);
})
uj5u.com熱心網友回復:
在使用籃子之前,您可以撰寫一個型別謂詞函式來檢查籃子的型別。
enum Fruits {
Apple = "Apple",
Banana = "Banana",
}
// These interfaces can have completely different shapes as shown.
interface AppleSlicer { apple(): void }
interface BananaSlicer { banana(): void }
export type FruitSlicer<TType> = TType extends typeof Fruits.Apple
? AppleSlicer
: TType extends typeof Fruits.Banana
? BananaSlicer
: never
export interface FruitBasket<TType extends Fruits> {
type: TType
slicer: FruitSlicer<TType>
}
function checkTypeOfBasket<T extends Fruits>(basket: FruitBasket<Fruits>, type: T): basket is FruitBasket<T> {
return basket.type === type
}
const test = (basket: FruitBasket<Fruits>) => {
if (checkTypeOfBasket(basket, Fruits.Apple)) {
// after checking, typescript understands the basket is an apple basket
basket.slicer.apple()
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/409684.html
標籤:
上一篇:基于條件引數值的函式回傳型別
