简体   繁体   中英

TypeScript: function overload doesn't work with generics

I am trying to get function overloads working with a function that takes a generic type param.

Originally I have a function foo

interface Props<T, S> {
  fn?: (response: S) => T[];
  enable?: boolean;
}

type Result = {
  irrelevant?: any;
};

function foo<T, S>(props: Props<T, S>): Result {
  
}

I omit the implementation details of this function because it is not relevant to this question.

Now I want this function to return two different types according to whether enable in Props is true or false / undefined

The idea is that, if the param the user gives to the function has enable: true in it, then the returned value from foo should have a property called update , and the user can safely descructure that from the returned value.

My attempt for that is to use function overload,

export interface Updater {
  updater: () => void;
}

export type Result = {
  irrelevant?: any;
};

export type CombinedResult = Result & Updater;

export function foo<T, S, U extends Props<T, S>>(
  props: U
): U extends { enable: true } ? CombinedResult : Result;

export function foo<T, S>(props: Props<T, S>): CombinedResult | Result {
  if (props.enable === true) {
    return {
      updater: () => {
        console.log("updater!");
      }
    };
  }

  return {};
}

Now the problem is, with this function overload, foo will ask for a third generic argument U in addition to T and S . So I will have this error

Expected 3 type arguments, but got 2.ts(2558)

const { updater } = foo<string, string[]>({ // 🚨 Expected 3 type arguments, but got 2.ts(2558)
  fn: (strings) => strings, 
  enable: true
});

Here is a live demo you can play with https://codesandbox.io/s/overload-with-generics-gbsok?file=/src/index.ts

Also please feel free to suggestion any other approach that you think can achieve the goal here

I didn't found a way to fix this design, mainly because of this third type argument. Adding type arguments to achieve such goals is usually pain in the neck, because typescript accepts either no type arguments at all, or all of them. Making typescript infer everything correctly is very hard, so there will be cases where you will want to specify type parameters yourself, but then you will have to write the third one manually which is very annoying.

So another solution would be to create 2 prop types like this:

// Creating this type to prevent repeating code
interface CommonProps<T, S> {
  fn?: (response: S) => T[];
}
interface DisabledProps<T, S> extends CommonProps<T, S> {
  enabled?: false
}
interface EnabledProps<T, S> extends CommonProps<T, S> {
  enabled: true
}
export type Props<T, S> = DisabledProps<T, S> | EnabledProps<T, S>

Now create overloads for disabled and enabled types separately:

export function foo<T, S>(props: EnabledProps<T, S>): CombinedResult;
export function foo<T, S>(props: DisabledProps<T, S>): Result;
export function foo<T, S>(props: Props<T, S>): CombinedResult | Result {
  // ...
}

This should work

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