簡體   English   中英

從 typescript 中的部分可區分聯合中檢測類型

[英]Detecting a type from a partial of a discriminated unions in typescript

我在檢測 function 的類型時遇到問題,該類型從可區分的聯合中獲取部分字段並從聯合中返回匹配類型。 我有一個create() function,它采用不包括時間戳和 ID 的字段,然后我返回帶有 ID 和時間戳的字段。 我們在參數中有type字段並返回 object 但它不用於確定返回類型。

游樂場鏈接

interface Node {
  type: string
  id: string
  createdAt: number
  updatedAt: number
}

interface Pokemon extends Node {
  type: 'Pokemon'
  name: string;
}

interface Trainer extends Node {
  type: 'Trainer'
  name: string;
  pokemon: string[]
}


type CreateNode<T extends Node> = T extends unknown
    ? Omit<T, 'id' | 'createdAt' | 'updatedAt'>
    : never

function create<R extends Node>(obj: CreateNode<R>): R {
  return {
    ...obj,
    id: 'ds',
    createdAt: 4,
    updatedAt: 5,
  } // give as error "... as is assignable to the constraint of type 'R', but 'R' could be instantiated with a different subtype of constraint 'Node'."
}

const pokemon = create<Pokemon|Trainer>({
  type: 'Pokemon',
  name: 'Garalaxapon'
})

pokemon //should be Pokemon but is Pokemon | Trainer

我不明白退貨中的錯誤,但我確信這是它的症結所在。 謝謝!

我將忽略實現內部的問題,這是與您所詢問的問題不同的問題。 現在我將假設實現工作,並使用declare語句只關注調用簽名問題。


您面臨的問題是您對單個R泛型類型期望過高。 您手動指定它是為了讓編譯器知道您想要區分哪些特定的Node子類型聯合。 但是,您還希望編譯器以某種方式將R縮小到該工會的成員之一。 這行不通。 一旦您手動將R指定為聯合,它就是永遠的聯合。

您可以使用兩個通用參數來解決這個問題; 一個( R )您為區分聯合指定,一個( T )編譯器從傳入的參數推斷。 不幸的是,您不能在單個 function 簽名中執行此操作; 要么您需要手動指定RT ,否則編譯器將嘗試推斷RT TypeScript 中沒有部分類型參數推斷 (microsoft/TypeScript#26242)

有時在這種情況下,我使用類型參數柯里化將兩個類型參數的單個 function 拆分為多個單類型參數函數。 在您的情況下,它看起來像這樣:

declare function create<R extends Node>(): <T extends CreateNode<R>>(obj: T) => Extract<R, T>;

注意R是如何對應全可區分聯合的,而T指的是傳入的obj參數的類型。 您使用Extract實用程序類型區分RT :僅返回可分配給TR的元素:

const createPokemonOrTrainer = create<Pokemon | Trainer>();

const pokemon = createPokemonOrTrainer({
  type: 'Pokemon',
  name: 'Garalaxapon'
}); // Pokemon

這里, create()返回另一個 function,並create<Pokemon | Trainer>() create<Pokemon | Trainer>()返回您之前嘗試制作的 function:接受部分Pokemon | Trainer Pokemon | Trainer並將其PokemonTrainer


但也許你實際上根本不需要R 您是否打算每次都使用不同的歧視聯合來調用create() 如果你只打算使用單一的聯合類型,比如Pokemon | Trainer Pokemon | Trainer ,那么您可以對其進行硬編碼。 本質上,不要打擾過度通用的create() ,只需手動編寫createPokemonOrTrainer()

type DiscrimUnion = Pokemon | Trainer;
declare function createDiscrimUnion<T extends CreateNode<DiscrimUnion>>(obj: T): Extract<DiscrimUnion, T>;

const pokemonAlso = createDiscrimUnion({
  type: 'Pokemon',
  name: 'Garalaxapon'
}); // Pokemon

好的,希望有幫助; 祝你好運!

Playground 代碼鏈接

pokemon //should be Pokemon but is Pokemon | Trainer

這是對的。 原因:

你有const pokemon = create<Pokemon|Trainer> where create<R extends Node>(obj: CreateNode<R>): R 由於create返回傳入的R ,因此 pokemon 將是您傳入的R 。您正在傳入Pokemon|Trainer所以pokemon: Pokemon|Trainer

解決方案

如果這是您想要的,請使用Pokemon ,即create<Pokemon>

暫無
暫無

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

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