带有鉴别器的 Typescript 通用类型 - 运行时访问鉴别器

Typescript generic type with discriminator - runtime access to discriminator

我认为这不可能,但我想问一下。我有带有鉴别器的类型,例如:

type Fish={
  type: "fish"
  canSwim: boolean
}
type Bird={
  type: "bird"
  canFly: boolean
}

我有一个状态对象,例如:

const state={
  fish: /* a fish */
  bird: /* a bird */
}

我想写一个函数:

function getFromState<T>():T {
  return state[T.type] as T
}

显然,解决方法是将类型作为参数传递,例如:

function getFromState<T>(type):T {
  return state[type] as T
}

但是你最终会重复一些事情:

const animal=getFromState<Fish>("fish")

正如我想您已经知道的那样,您不能在运行时引用类型,因为 type erasure

有很多替代路径,但这完全取决于您的实际用例。根据您所写的问题,很难知道您的真正需求是什么。我建议你用你的实际用例(状态)而不是抽象的动物)重写你的问题。我会在这个暂定答案中猜测你的需求,但如果你更新你的问题并在上面@我或在这个答案下面发表评论,我会更新它。

在下面的代码中,运行时“多态性”是通过您的示例 (type) 中的鉴别器 属性 和常规 if-elseswitch 逻辑。因为 Typescript 能够通过推理 narrow types ,所以您还可以获得编译时静态类型安全性。在您的解决方法中没有 DRY 违规。

你可以test both its static type checking and its runtime behavior in the Playground.

type Fish = {
    type: "fish"
    canSwim: boolean
}
type Bird = {
    type: "bird"
    canFly: boolean
}

type Animal = Fish | Bird

type AnimalTypes = Animal['type']

function getAnimal(type: AnimalTypes): Animal {
    return type === 'fish' ? { type: 'fish', canSwim: true } : { type: 'bird', canFly: true }
}

const state = {
    fish: { type: 'fish', canSwim: true } as Fish,
    bird: { type: 'bird', canFly: false } as Bird
}

function getFromState(type: AnimalTypes):Animal {
  return state[type]
}

function polymorphicFunction(animal: Animal) {
    if (animal.type === 'fish') {
        console.log(`It ${animal.canSwim ? 'can' : 'cannot'} swim!`)
    } else {
        console.log(`It ${animal.canFly ? 'can' : 'cannot'} fly!`)
    }
}

// both static type checking and runtime "polymorphism" works.
// Nothing has been repeated (DRY).
polymorphicFunction(getFromState('fish'))
polymorphicFunction(getFromState('bird'))

OP 编辑​​: 在@Inigo 的帮助和对另一个问题 () 的回答下,使用实用程序类型 Extract:

找到了更完整的解决方案
type Fish = {
    type: "fish"
    canSwim: boolean
}
type Bird = {
    type: "bird"
    canFly: boolean
}

type Animal = Fish | Bird

const state = {
    fish: [{ type: 'fish', canSwim: true }, { type: 'fish', canSwim: false }] as Fish[],
    bird: [{ type: 'bird', canFly: true }, { type: 'bird', canFly: false }] as Bird[]
}

function getFromState<C extends Animal["type"]>(type: C): Extract<Animal, {type: C}> {
    return state[type][0] as Extract<Animal, {type: C}>
}

getFromState("fish").canSwim // works
getFromState("bird").canFly // also works
getFromState("fish").canFly // error

Playground link

提供的答案给了我一些灵感,我想出了:

type Fish = {
    type: "fish"
    canSwim: boolean
}
type Bird = {
    type: "bird"
    canFly: boolean
}

type Animal = Fish | Bird

const state = {
    fish: [{ type: 'fish', canSwim: true }, { type: 'fish', canSwim: false }] as Fish[],
    bird: [{ type: 'bird', canFly: true }, { type: 'bird', canFly: false }] as Bird[]
}

function getFromState<C extends Animal["type"]>(type: C): Extract<Animal, {type: C}> {
    return state[type][0] as Extract<Animal, {type: C}>
}

getFromState("fish").canSwim // works
getFromState("bird").canFly // also works
getFromState("fish").canFly // error

Playground link