Warm tip: This article is reproduced from serverfault.com, please click

How to set a type depends on function arguments in typescript?

发布于 2020-11-28 02:13:03

My test code

type Config = {
  id: string;
  password: string;
}

type TrueTypeConfig = Config & {
  emailId: number;
  paymentId: number;
}

type FalseTypeConfig = Config & {
  documentId: number;
  buyerId: number;
}

type GetTestConfig<T extends boolean> = (type: T, id: string, password: string)
  => T extends true
    ? TrueTypeConfig
    : FalseTypeConfig
    
function getTestConfig<T extends boolean> (type: T, id: string, password: string): T extends true ? TrueTypeConfig : FalseTypeConfig;
function getTestConfig (type: boolean, id: string, password: string): TrueTypeConfig | FalseTypeConfig {
  const config: TrueTypeConfig | FalseTypeConfig = {
    id: getRandomData(id),
    password: getRandomData(password),
  };
  if (type) {
    config.emailId = getRandomNumber(1, 100);
    config.paymentId = getRandomNumber(1, 100);
  } else {
    config.documentId = getRandomNumber(1, 100);
    config.buyerId = getRandomNumber(1, 100);
  }
  return config;
};

const res1 = getTestConfig(false, '1', '1'); // FalseTypeConfig
const res2 = getTestConfig(true, '1', '1'); // TrueTypeConfig

"getTestconfig" function has to return ConfigType True or False config. It depends on an argument "type". (It's working with the above test code) But const config: TrueTypeConfig | FalseTypeConfig is type error and also inside if/else codes too.

My question

  • Is the above test code common method for set a return type of function, when it depends on arguments?
  • I don't know how to fix the type issue const config: TrueTypeConfig | FalseTypeConfig

I wanna know better way "how to define return type"

Questioner
YouBeen Kim
Viewed
0
jcalz 2020-11-28 11:56:20

I think using a generic conditional type this way as a function return is perfectly fine. As you've noticed, the compiler is unfortunately unable to verify that a particular value inside the function implementation is assignable to such a conditional type dependent on an unspecified generic type parameter; this is the topic of microsoft/TypeScript#33912. Until and unless that issue is addressed, you will have to shift some of the burden of type safety from the compiler to you, such as with type assertions, or, as you've done, a single-call signature overload.


As for the implementation of getTestConfig(), you can't say that config is a TrueTypeConfig | FalseTypeConfig because it doesn't have enough properties to start with. At best you could say it's type Config and then later narrow it to one of the desired types.

Instead of initializing config in stages, which is hard for the compiler to verify, I would just use something like Object.assign() or object spread to initialize config all at once:

function getTestConfig<T extends boolean>(type: T, id: string, password: string): T extends true ? TrueTypeConfig : FalseTypeConfig;
function getTestConfig(type: boolean, id: string, password: string): TrueTypeConfig | FalseTypeConfig {
  return {
    id: getRandomData(id),
    password: getRandomData(password),
    ...(type ?
      {
        emailId: getRandomNumber(1, 100),
        paymentId: getRandomNumber(1, 100)
      } : {
        documentId: getRandomNumber(1, 100),
        buyerId: getRandomNumber(1, 100)
      }
    )
  };
}

That should compile cleanly.


The only other way I can imagine to do a return type that depends on input parameters more safely (which precludes overloads entirely) is to try to use a lookup type for the return. I tried this and it's really ugly/impenetrable and relies on coercing boolean values to their equivalent string via TS4.1's template literal types. I won't even include the code in the text here, since I don't recommend it. It's included in the Playground link below if you really care, and I'm happy to explain it if you want. But I'd say that a generic conditional type is probably the best solution for now.


Playground link to code