DEV Community

Artur Czemiel
Artur Czemiel

Posted on • Originally published at blog.graphqleditor.com

TypeScript Tutorial - conditional types

Hello, This is the second article of the advanced typescript tutorial series. Today I'll cover the basic usage of

extends
Enter fullscreen mode Exit fullscreen mode

keyword and conditional types. How does conditional type looks like?

type StringOrArray<T> = T extends string[] ? 'array' : T extends string ? 'string' : never
const a:string = "hello"
const b:string[] = ["hello","world"]
const c:number = 1

const d:StringOrArray<typeof a> = "string"
const e:StringOrArray<typeof b> = "array"
let f:StringOrArray<typeof c> 
Enter fullscreen mode Exit fullscreen mode

So let's analyze this code:

  1. We check if our generic Type is a string array
  2. If it is array make type as string constant 'array'
  3. If it does not check the type. If it is string make type as string constant 'string'
  4. Else the type is never

To be the truth this code is useless but can give you some scope how extends keyword works. Next example will be a real-world example where we determine the type of the form field to give the user correct options.

type FieldType = "string" | "float" | "date"

type BaseField = {
    name:string
}

type Field<T extends FieldType> = BaseField & {
    value: T extends "string" ? string : T extends "float" ? number : T extends "date" ? Date : never
}

const stringField:Field<"string"> = {
    name:"myfield",
    value:"aaa"
}
const floatField:Field<"float"> = {
    name:"myfield",
    value:1.0
}

const dateField:Field<"date"> = {
    name:"myfield",
    value: new Date()
}
Enter fullscreen mode Exit fullscreen mode

This is a little bit more advanced. What's going on with FieldType? It just checks the string converted to generic type to return correct type.


type FieldType = "string" | "float" | "date";
type BaseFieldExtended = {
  name: string;
  type: FieldType;
};
const FieldExtended = <T extends BaseFieldExtended>(
  baseField: T & {
    value: T["type"] extends "string"
      ? string
      : T["type"] extends "date"
      ? Date
      : T["type"] extends "float"
      ? number
      : never;
  }
) => baseField;

FieldExtended({
  name: "a",
  type: "string",
  value: "bbb"
});

FieldExtended({
  name: "a",
  type: "float",
  value: 12
});

FieldExtended({
  name: "a",
  type: "date",
  value: new Date()
});

Enter fullscreen mode Exit fullscreen mode

And this is what typescript is made for. To provide complicated autocompletion stuff :). Wait for the next series of advanced typescript tutorial.

Top comments (2)

Collapse
 
andrekovac profile image
André Kovac

Very nice series and article! I have two questions though:

  1. In the first example of StringOrArray we definitely need it. Changing never to any there would falsely allow wrong things to happen.

But if I replace never with any in Field (your second example) or FieldExtended (your last example) it would all still be type safe.

So is it a form of convention to use never as the last not handled condition to be on the safe side?

  1. I see that FieldExtended is basically the identity function with type checks. I can understand that a code editor might use such a function. But as an app developer, what would be a use-case for me to actually use something like FieldExtended?
Collapse
 
aexol profile image
Artur Czemiel
  1. Use never to notify developer that the type is not supported.