簡體   English   中英

TypeScript - 從通用聯合類型縮小類型

[英]TypeScript - narrowing type from generic union type

//Type declaration:

interface TickListFilter {
   type: "tickList";
   value: string;
}

interface ColorFilter {
   type: "color"
   value: ColorValueType
}

type Filter = TickListFilter | ColorFilter;



...
onValueChange = (filter: Filter, newValue: Filter["value"]) => {
        if (filter.type === "tickList") {
            // variable 'filter' has TickListFilter type (OK)
            // variable 'filter.value' has string type (OK)
            // variable newValue has ColorValueType | string. I know why, let's fix it!
        }
...

onValueChange = <T extends Filter>(filter: T, newValue: T["value"]) => {
        if (filter.type === "tickList") {
            // variable 'filter' has Filter type (wrong... i need tick list)
            // variable 'filter.value' has string | ColorValueType type (wrong, it should be string)
            // variable newValue has ColorValueType | string.
        }

我該如何解決? 我知道它為什么會發生,因為 TS 不能通過泛型類型來區分聯合。 但是有什么解決方法可以像我描述的那樣工作嗎?

我想使用與 switch-case 類似的結構(不是 if-else only)

啊,歡迎來到microsoft/TypeScript#13995microsoft/TypeScript#24085 目前,編譯器不使用控制流分析來縮小泛型類型參數或依賴於泛型類型參數的類型值。 從技術上講,縮小類型參數本身是錯誤的,因為有人可能會這樣調用泛型版本:

const filter: Filter = Math.random() < 0.5 ?
  { type: "tickList", value: "str" } : { type: "color", value: colorValue };
const newValue: string | ColorValueType = Math.random() < 0.5 ? "str" : colorValue;
onValueChange(filter, newValue); // no compiler error
// const onValueChange: <Filter>(filter: Filter, newValue: string | ColorValueType) => void

編譯器將推斷Filter for T ,根據通用簽名,這是“正確的”,但它會導致與您的非通用版本相同的問題。 在 function 內部,可能是newValuefilter不匹配。


上面的GitHub問題里面已經有很多關於如何處理這個問題的建議和討論。 如果類似microsoft/TypeScript#27808 ,您可以說T extends-exactly-one-member--of Filter ,這意味着T可以是TickListFilterColorFilter但它不能Filter 但是現在還沒有辦法說這個。


現在最簡單的方法(假設您不想實際檢查 function 的實現內部filternewValue相互匹配)將涉及類型斷言或類似的東西。 您需要充分放松類型檢查以允許代碼,並且只是期望/希望沒有人會像我上面那樣調用您的 function。

這是使用類型斷言的一種方法:

const onValueChange = <T extends Filter>(filter: T, newValue: T["value"]) => {
  const f: Filter = filter; // widen to concrete type
  if (filter.type === "tickList") {
    const v = newValue as TickListFilter["value"]; // technically unsafe narrowing
    f.value = v; // okay
  } else {
    const v = newValue as ColorFilter["value"]; // technically unsafe narrowing
    f.value = v; // okay
  }
}

Playground 代碼鏈接

如果你想要一個手動的解決方案,你可以求助於編寫自己的類型保護

// Either like this, simple but a lot of typing
const isColorFilter = (value: Filter): value is ColorFilter => value.type === 'color';

// Or write a small type guard generator if there are lots of these types
const createFilterTypeGuard = <T extends Filter>(type: T['type']) => (value: Filter): value is T => value.type === type;

// And then
const isColorFilter = createFilterTypeGuard<ColorFilter>('color');

然后在您的代碼中,您可以像這樣使用它們:

  if (isColorFilter(filter)) {
    // filter is ColorFilter
  }

但是,您將遇到newValue問題。 由於類型保護只會縮小filter的類型,因此newValue將保持不縮小:

onValueChange = <T extends Filter>(filter: T, newValue: T["value"]) => {
  // OK, filter is T and newValue is T['value']
  filter.value = newValue;

  if (isColorFilter(filter)) {
    // Not OK, filter is ColorFilter now but newValue is still Filter['value']
    filter.value = newValue;
  }
}

調用onValueChange時您仍然會得到正確的驗證,您只需要在 function 主體內進行一些類型轉換:(

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM