DEV Community

Khaled Hosseini
Khaled Hosseini

Posted on • Updated on

Master typescript types in 7 minutes.

Types in C# and Java

You may be familiar with the types in managed-type languages like Java or C#. In these languages, we have two kinds of types.

primitive types

  • These types are value-based
  • These types are predefined by the language
  • Examples are int, string, float, bool, byte, double,
  • In c#, you can use typeof operator to get the type of a type and GetType() method to get the type of a variable.
Console.WriteLine(typeof(int)); // System.Int32
Console.WriteLine(typeof(byte)); // System.Byte
Console.WriteLine(typeof(double)); // System.Double
Console.WriteLine(typeof(string)); // System.String
var str = "string";
Console.WriteLine(str.GetType()); // System.String
Enter fullscreen mode Exit fullscreen mode
  • In java, you can use instanceof operator to check if an object is a type or not
Object d = 0.0;
System.out.println(d instanceof Double);//true
Enter fullscreen mode Exit fullscreen mode

Non-primitive types

  • These types are generally referenced based (except Enums and struct in c#)
  • These types are user-defined types and users can define and create them using class, enum, struct, interface, delegate, etc.
  • In c# you can use typeof operator to get the type of a Type and GetType() method to get the type of a variable.
class Program {
    public static void Main() {
       Console.WriteLine(typeof(Program));//Program
       var program = new Program();
       Console.WriteLine(program.GetType());//Program
   }
}

Enter fullscreen mode Exit fullscreen mode
  • In Java, you can use instanceof operator.
class Car{
   interface IExample {
         public void print();
   }
   public static void main(String args[]){
     Car c=new Car();
     System.out.println(c instanceof Car);//true

     Object obj = new IExample() {
            public void print() {

            }
        };
     System.out.println(obj instanceof IExample);//true
   }
}
Enter fullscreen mode Exit fullscreen mode

Types in typescript

primitive types

  • Typescript's primitive types are string, number, boolean, bigint, undefined
  • These types are value-based
  • These types are predefined by the language
  • You can use typeof operator to get the types of values.
    let big1 = 9007199254740991n;
    console.log(typeof(big1)) // "bigint"

    let bool = true
    console.log(typeof bool) // "boolean"

    let undef
    console.log(typeof undef)//"undefined"
Enter fullscreen mode Exit fullscreen mode
  • You can consider primitive types in Typescript to work similarly to those in Java and C#, but actually they do not! Javascript types have more to offer. You will see in a moment.

Non-primitive types

  • These types are generally referenced based (except Enums)
  • These types are user-defined types and users can define and create them using class, type, interface, function, etc.
  • Do not confuse the general word type which is a programming concept with Typescript's type keyword which is one way of creating types.
  • You can use typeof operator to get the types of values.
    type Obj = { x: string }
    const obj: Obj = { x: "" }
    console.log(typeof obj) // "object"

    class Class {  constructor(public a: number){} }
    const cls: Class = new Class(1)
    console.log(typeof cls)// "object"

    interface Interface { i: number }
    const obj2: Interface = {  i: 1 }
    console.log(typeof obj2)// "object"

    let nl = null
    console.log(typeof nl)// "object"
Enter fullscreen mode Exit fullscreen mode
  • As you may have noticed, when it comes to the typeof operator in TypeScript, the results for non-primitive types are all "object". Here, it's important to adjust our understanding of types in TypeScript. In Typescript Instead of thinking of a type as a rigid definition, consider it as a set (Unique values) that encompasses multiple values. This characteristic makes TypeScript types incredibly flexible and powerful. In languages like C# and Java, a variable can only have a single type, and you can create new types by utilizing techniques like inheritance or interface implementation. However, in TypeScript, you not only have access to those same techniques, but you also have a wide range of additional methods to manipulate types like combining them, filtering a type to create a new type, map an existing type to another one and so on. Below I introduce the patterns that you can use to work with types in Typescript and see how they are different from managed-type languages.

Unions of primitive types / values

In languages like C# and java, a variable can only be one type at the same time. For example either a string or number and so on. In Typescript you can use the union of multiple types as a new type or union of multiple values as a new type.

    //union of two primitive types
    type stringOrNumber = string | number;
    let son: stringOrNumber = "text"
    console.log(typeof son) // "string"
    console.log(son) // "text"
    son = 1
    console.log(typeof son) //number
    console.log(son) // 1

    //union of multiple number values
    type DiceNumber = 1 | 2 | 3 | 4 | 5 | 6;
    let diceNumber: DiceNumber = 1
    //let diceNumber: DiceNumber = 10 // error: Type '10' is not assignable to type 'DiceNumber'
    console.log(typeof diceNumber) // "number"
    console.log(diceNumber) // 1

    //template union types
    type Langs = 'en' | 'fr'
    type texts = 'footer' | 'header'
    type combine = `${Langs}_${texts}`
    /*
        type combine = "en_footer" | "en_header" | "fr_footer" | "fr_header"
    */

Enter fullscreen mode Exit fullscreen mode

Extend object types

You can extend type, class or interface types to create new types. For classes and interfaces, aside from minor differences, it is what you are also doing in languages like C# or Java.

    type Person = { name: string; age: number;};
    //Create a new type by removing the name
    type PersonWithoutName = Omit<Person,'name'>
    //create new type by adding id to Person type
    type PersonWithId = {id: string; } & Person;
    //create a new type using intersection of two other types
    type Dim1 = {x: number}
    type Dim2 = {y: number, z: number}
    type Dim3 = Dim1 &  Dim2

    interface IPersion {name: string}
    //create new type by extending IPersion
    interface IPersopnWithId extends IPersion { id: number;}
    // Add properties to an existing interface by redeclaring the interface name. (You cannot do this for `type`)
    interface IPersion {age: number}

    class Parent {
        constructor(public a: number) {}
    }
    // Create a new type by extending Parent class
    class Child extends Parent {
        constructor(public b: number, public a: number)
        {
            super(a);
        }
    } 
Enter fullscreen mode Exit fullscreen mode

Map an existing type to another by applying a mapper type

You can create a new type from an existing type by changing the properties of the old one using a mapper.

    type Artist = {
        id: number;
        name: string;
        bio: string;
    }
    // a mapper that makes all fileds of the old type optional and add a mandatory id filed
    type MyPartialTypeForEdit<Type> = {
    [Property in keyof Type]?: Type[Property];
    } & { id: number };
    // this type contains all fileds from Artist type but optional
    type ArtistOptionalFields = MyPartialTypeForEdit<Artist>;
        /*
        type ArtistOptionalFields = {
            id?: number | undefined;
            name?: string | undefined;
            bio?: string | undefined;
        } & {
            id: number;
        } 
        */
Enter fullscreen mode Exit fullscreen mode

Filter types by applying a conditional type

You can create a new type by setting some rules. Then apply this conditional type to a compound type to filter it.

    // If A extends this type-> {legs: 4}, then A has four legs, otherwise never.
    type HasFourLegs<A> = A extends {legs: 4} ? A : never
    type Bird = { legs: 2, wings: number };
    type Dog = { legs: 4, breed: string};
    type Ant = { legs: 12};
    type Lion = { legs: 4};
    type Animals = Bird | Dog | Ant | Lion
    type four_legs = HasFourLegs<Animals> // Dog | Lion
Enter fullscreen mode Exit fullscreen mode

Mix classes using mixins

In typescript, like most other programming languages, a class can only inherit from one parent class. But what if we want to have the capabilities of multiple classes in one class (just like C++). Do not worry. Typescript allows you create a new type by mixing classes.

    class Jumpable { jump() { console.log("jump");} }
    class Duckable { duck() {console.log("duck");} }
    //Our mixed class
    class Sprite { x = 0; y = 0; }
    // Then you create an interface which merges
    // the expected mixins with the same name as your base
    interface Sprite extends Jumpable, Duckable {}
    // Apply the mixins into the base class via the JS at runtime
    applyMixins(Sprite, [Jumpable, Duckable]);

    let player = new Sprite();
    player.jump();
    player.duck();

    // This can live anywhere in your codebase:
    function applyMixins(derivedCtor: any, constructors: any[]) {
        constructors.forEach((baseCtor) => {
            Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
            Object.defineProperty(
                derivedCtor.prototype,
                name,
                Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
                Object.create(null)
            );
            });
        });
    }
Enter fullscreen mode Exit fullscreen mode

Picking properties from another type

You can use Pick<> to pick certain properties from another type and create a new type.

Type User = {
    id: string,
    name: string,
    age: string,
    email: string,
    phone: number,
    password: string
}

Type LoginUser = Pick<User,'email' | 'password'>

Enter fullscreen mode Exit fullscreen mode

What else you can add to this list?

Top comments (0)