简体   繁体   中英

Passing generic enum type as a parameter to a function in Typescript

I have two string enums that have the same variants, but have different string values.

They get passed to the same initial function, which accepts the entire enum (which I think I can do using typeof , but each enum should have a different callback. What seems to be the best way to type the initial function?

This code that I have doesn't work, as it complains that I can't use typeof with my T generic parameter.

enum OpsA {
    Start = 'a_start',
    Stop = 'a_stop'
}

enum OpsB {
    Start = 'b_start',
    Stop = 'b_stop'
}

type EitherOps = OpsA | OpsB

function doSomething <T extends EitherOps, U extends typeof T>(action: U, callback: (a: T) => void) {
    console.log(action.Start)
    callback(action.Start)
    console.log(action.Stop)
    callback(action.Stop)
}

// does something special for OpsA
const callbackForA = (a: OpsA) => console.log(a)
// does something else special for OpsB
const callbackForB = (b: OpsB) => console.log(b)

doSomething(OpsA, callbackForA)
doSomething(OpsB, callbackForB)

Your problem is that T is a type and not a value, so typeof T is a category error. Your confusion probably stems from the fact that enum declarations bring into scope both a named value and a named type, and the names happen to be the same. But there's no general rule that a value and a type with the same name have any relationship to each other.

For example, enum OpsA { /* ... */ } declares both the value OpsA (an object with property keys Start and Stop ) and the type OpsA (a union of the types of the property values of the enum object). When you write typeof OpsA as a type you are talking about the type of the enum object, while when you write OpsA as a type you are talking about the type of the enum object's property values.

But the fact that the type OpsA is the property type of the object type typeof OpsA cannot be generalized the way you are presumably trying to do. There is no rule that says "given the type T there is a type typeof T whose properties are of type T ." See an answer to a related question for more information.


Okay, if you can't write U extends typeof T , what can you do? You want U to be an object type whose property keys are "Start" or "Stop" . Let's get a type for those keys first. Since both ObjA and ObjB value have these keys, we can write that as either keyof typeof ObjA or keyof typeof ObjB :

type OpsKeys = keyof typeof OpsA;
// type OpsKeys = "Start" | "Stop"

If we want to say that U should be an object type with keys of OpsKeys and values of type T , then we can use the Record<K, V> utility type : U extends Record<OpsKeys, T> . Like this:

function doSomething<
  T extends EitherOps,
  U extends Record<OpsKeys, T>
>(action: U, callback: (a: T) => void) {
  console.log(action.Start)
  callback(action.Start)
  console.log(action.Stop)
  callback(action.Stop)
}

Now everything works as desired inside doSomething() . Let's make sure that it also works for callers:

doSomething(OpsA, callbackForA) // okay
doSomething(OpsB, callbackForB) // okay
doSomething(OpsA, callbackForB) // error

Looks good. The compiler warns if you pass in a callback argument that's inappropriate for the action argument.

Playground link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM