Home > front end >  TypeScript: Conditional types not working with optional parameters
TypeScript: Conditional types not working with optional parameters

Time:01-26

I have a data source, let's call it getData(), that returns objects. Sometimes it returns objects of known type (e.g. Person, Animal), but sometimes the returned object has an unknown shape.

(Link to TypeScript Playground)

type Person = { name: string; age: number };
type Animal = { species: string };

/** 
 * This interface stores all known object types returned by `getData()`.
 * I'm storing it as an interface instead of `Person | Animal` because I
 * need to store the "code" of a known type (e.g. `"person"` for `Person` type).
 */
interface DataCategory {
  person: Person;
  animal: Animal;
}

/** Our data source */
const getData: Person | Animal | any = () => {
  return {};  // Mocked value
};

Now I wish to write a helper function useData() to narrow down getData()'s return value. It accepts an optional parameter of type keyof DataCategory and returns the corresponding type. I wish to make this function return any if we don't pass a parameter.

const person = useData("person");  // const person: Person
const animal = useData("animal");  // const animal: Animal
const notKnown = useData();   // const notKnown: any

I've tried the following implementation:

function useData<T extends keyof DataCategory>(category?: T) {
  const data: any = getData();
  return data as T extends undefined ? any : DataCategory[T];
}

const animal = useData("animal");
//    ^ const animal: Animal

const notKnown = useData();
//    ^ const notKnown: Person | Animal
// However, I want the above to be `const notKnown: any`

That doesn't work because useData() returned Person | Animal instead of any. How can I fix this problem?

CodePudding user response:

By default TS will use the constraint as the type for a type argument it can't infer (and in this case there is no location to infer T from). You could change this by using any as the default:

function useData<T extends keyof DataCategory = any>(category?: T) {
  const data: any = getData();
  return data as T extends undefined ? any : DataCategory[T];
}

Playground Link

You can also use overloads as Michael Rose points out in his answer and they might be easier for future readers to understand.

CodePudding user response:

An alternative to trying to use generics would be to just overload your useData function and specify the category:

function useData(category: "animal"): Animal;
function useData(category: "person"): Person;
function useData(): any;
function useData(category?: string): any {
  return getData();
}

const animal = useData("animal");
//    ^ const animal: Animal

const notKnown = useData();
//    ^ const notKnown: any
  • Related