Conditional Types
Conditional Types provide a way to do simple logic in the
TypeScript type system. This is definitely an advanced
feature, and it's quite feasible that you won't need to
use this in your normal day to day code.
A conditional type looks like:
A extends B ? C : D
Where the condition is whether a type extends an
expression, and if so what type should be returned.
Let's go through some examples, for brevity we're
going to use single letters for generics. This is optional
but restricting ourselves to 60 characters makes it
hard to fit on screen.
// We can create a conditional type which lets extract
types which only conform to something which barks.
type Cat = { meows: true };
type Dog = { barks: true };
type Cheetah = { meows: true; fast: true };
type Wolf = { barks: true; howls: true };
// This becomes useful when you want to work with a
union of many types and reduce the number of potential
options in a union:
type ExtractDogish = A extends { barks: true } ? A : never;
// Then we can create types which ExtractDogish wraps:
// A cat doesn't bark, so it will return never
type NeverCat = ExtractDogish
// When you apply ExtractDogish to a union type, it is the
same as running the conditional against each member of
the type:
type Animals = Cat | Dog | Cheetah | Wolf;
// = ExtractDogish
type Dogish = ExtractDogish
// Then depending on how much the type-system knows about
the boolean, you will get different return types:
declare function getID
// In this case above TypeScript can know the return value
instantly. However, you can use conditional types in functions
where the type isn't known yet. This is called a deferred
conditional type.
Same as our Dogish above, but as a function instead
let stringReturnValue = getID(true);
let numberReturnValue = getID(false);
let stringOrNumber = getID(Math.random() < 0.5);
// There is an extra useful tool within conditional types, which
is being able to specifically tell TypeScript that it should
infer the type when deferring. That is the 'infer' keyword.
infer is typically used to create meta-types which inspect
the existing types in your code, think of it as creating
a new variable inside the type.
declare function isCatish
// Roughly:
- this is a conditional generic type called GetReturnValue
which takes a type in its first parameter
- the conditional checks if the type is a function, and
if so create a new type called R based on the return
value for that function
- If the check passes, the type value is the inferred
return value, otherwise it is the original type
type GetReturnValue
// This fails the check for being a function, and would
just return the type passed into it.
type getIDReturn = GetReturnValue
type getCat = GetReturnValue