Getting Started with TypeScript - Union Intersection and Functions

by iamvkr on

So, you’re a JavaScript developer, right? You love its flexibility, its dynamic nature, the sheer joy of, just making things happen.. But let’s be honest, sometimes that flexibility can feel a bit too flexible.

Imagine this: you’re building a complex application, and suddenly, boom, a runtime error. Turns out you accidentally passed the wrong type of data somewhere, and now your app is throwing a tantrum. Sound familiar?

Well, that’s where our superhero, TypeScript, comes to the rescue!

Table of contents

Table of contents generated with markdown-toc

The Problem:

JavaScript, being dynamically typed, doesn’t really care what kind of data you’re dealing with until you actually try to use it. This can lead to:

  • Unexpected behavior: Imagine passing a string where a number should be. Your code might crash, or worse, produce incorrect results, and you’ll be scratching your head for hours trying to figure out why.

  • Debugging nightmares: Hunting down these sneaky type-related bugs can feel like a never-ending game of “Where’s Waldo?”

  • Maintainability issues: As your project grows, keeping track of data types across different parts of your code becomes a real headache.

TypeScript to the Rescue!

TypeScript is essentially JavaScript on steroids. It’s a superset of JavaScript that adds static typing. This means you tell TypeScript what type of data each variable, function, and property should be.

TypeScript is JavaScript with syntax for types.

Benefits of TypeScript:

  • Early bug detection: Catch those pesky type errors early on, during development, instead of at runtime.

  • Improved code readability: Type annotations make your code more self-documenting. You and your team will understand the code’s intent much more easily.

  • Better code maintainability: Refactoring and making changes become less risky, as TypeScript helps you understand the impact of your changes.

  • Enhanced tooling support: Many popular code editors and IDEs provide excellent TypeScript support, with features like autocompletion, code navigation, and refactoring tools.

What typescript does:

  • static checking
  • analyze the code.

Working:

The code written in typeScript is transpiled in js and js is what that at the end actually gets executed. Typescript is a development tool. The project still runs in js.

How Typescript Protects your code:

Assume You and Your friend are working in a project, and you declared a fuction to get sum of 2 numbers as the code in js:

function sum(a,b){
    return a+b;
}

Now Your friend uses the function:

const result = sum("Hello","World");

In the above situation, we know, it’s not a valid way to use that function, but Hey js doesn’t complain about it. Instead it gives an output as a concatenated string.

To Stop this quirky behavior, typescript was introduced. Using ts, it won’t let you write this kind of epic codes! This would instanly result in error.

Documentaion:

typescriptlang.org

Installation

Installation process required you to have Node js or Other Javascript Runtime to be installed. To instal typescript globally, use:

npm:

npm i typescript -g

yarn:

yarn global add typescript

check if installed:

tsc -v

Syntax:

let variabeName: type = value;

type should be always lowecase.

Creating your first TypeScript file

Create a new file named hello.ts with the following content:

console.log("Hello Ts"); 

Compiling ts to js:

Open your terminal, navigate to the directory where you saved hello.ts, and run the following command:

tsc hello.ts 

This will generate a new file named hello.js containing the compiled JavaScript code.

You can now execute the generated JavaScript file using Node.js:

node hello.js

This will output: Hello, World!

Types in ts

1.Basic Types

You must already have heard about these basic types available in js, that still works fine here in typescript.

Number, String, Boolean, Null, Undefined, Object, Array

We just need to define the type of the variable. For Example:

let message: string = "Hello Ts";
let userId: number = 1254;
let isLoggedIn: boolean = false;
let myNullVariable: null = null;
let myUndefinedVariable: undefined = undefined;
let myObjectVariable: object = {}; 
let myArrayVariable: string[] = ["apple", "banana", "orange"];

Type inference

Type inference means TypeScript is smart enough to figure out the type of a variable based on how you’re using it. for example:

let myName = "Alice";

Even though we didn’t explicitly declare myName as a string, TypeScript infers that it’s a string because we assigned a string value to it.

Fun with Arrays in Ts

Arrays are similar to array defined in js, with just type defined before [] for example:

const numbers: number[] = [1, 2, 3, 4, 5];
const words: string[] = ["apple", "banana", "cherry", "date"];

Typescript has also got another way to define arrays with angle brackets like:

let strArr: Array<string> = [];
strArr.push("abc");

You can use whatever suits you the best, knowing the syntax is essential so that you don’t get freak out when you find them in some codebases.

An interesting observation:

const fruits = [];

So what’s the type of item we can store in fruits?

let’s push something:

fruits.push("apple");

We get error: Argument of type 'string' is not assignable to parameter of type 'never'.

So default type of such array is never, of course we can change above statement to work as:

const fruits: string[] = [];
fruits.push("apple");

Storing Multiple types in Array

You might be wondring, how could I store both Number and string in an array, In js, we could do with ease. But How in typescript?

Could it be like: const arr: [string , number]=["name",10];

Yes, but there’s problem, you could store only two items now, also the position can’t be changed, the first item must be string and second must be number.

How do we fix that?

You can store both numbers and strings in an array in TypeScript using a union type:

const mixedArray: (string | number)[] = ["hello", 42, "world", 3.14]; 

That bring us to the next level we need to learn, i.e types beyond basics.

2.Beyond the basics

Typescript offers a rich type system that goes beyond simple primitives. Some of the more advanced types:

  • Tuples
  • Enums
  • Any
  • Never
  • Unknown
  • Union Types, Intersection Types, Generic Types

Tuples

Tuples allow you to define the exact types of each element in a collection, improving type safety and reducing the risk of runtime errors

Example:

let userTuple: [string, number];
userTuple = ["user1", 24]; /*==>correct*/
/* userTuple = [24,"user1"] ==>incorrect */

Consider the example:

let rgb: [number, number, number];
rgb = [255, 255, 255];
rgb[0] = 244;
rgb[1] = "hello"; /** not allowed */

/** unexpected behaviour  */
rgb.push("100");
console.log(rgb); /* [ 244, 255, 255, "100" ] */

Note that rgb throws error if there’s number in first index however you can still update the value by push or pop methods

Difference between array and tuples:

FeatureArrayTuple
DefinitionAn ordered collection of elements of the same data type.An ordered, immutable collection of elements of potentially different data types.
MutabilityMutable (elements can be added, removed, or modified).Immutable (elements cannot be added, removed, or modified after creation).
Type InferenceInferred based on the type of the first element.Explicitly defined with specific types for each element.
Examplelet numbers: number[] = [1, 2, 3];let user: [string, number] = ["John", 30];
Use Cases
  • Storing a collection of values of the same type.
  • Working with dynamic data structures.
  • Representing fixed-size data structures with specific types for each element.
  • Ensuring type safety for specific data combinations.

Enums

Enum are used to group things:

enum Direction{
	TOP = "top",
    DOWN ="down",
}
console.log(Direction.TOP + Direction.DOWN);

More with enums:

enum CHOICE { /*DEFAULT VALUE C1 = 0, C2=1,C3=2 */
    C1 = 10, /* Values can be overwritten, C2 becomes 11 and so on*/
    C2,
    C3
}
/* WHAT IF VALUE ARE STRING */
enum CHOICE2 {
    C1 = "10", /* needs to explicitly give values to all */
    C2 = "20",
    C3 = "30"
}
/** string and number combination */
enum CHOICE3 {
    C1 = "10", 
    C2 = 0,
    C3 /* c3 becomes 1 */
}

Any

Any is considered as not to be a used guy. Whenever we define a variable type as any, we are basically assigning the variable value that can hold any type of value, either be a string or number or boolean or something else.

In this case typescript doesn’t complain and behaves like javascript with no strict type checking. We won’t get a proper recommendation and code completion in vs code. Using any fails itself the purpose of the using typescript in the code. This is the reason why it is not recommended to use any in typescript, however any type exists for the specific or in rare case when notihing seems to fix the issue and must be used as a last option.

In short:

  • Disables type checking for the variable.
  • You can assign any value to an ‘any’ variable and perform any operation on it.
  • Can lead to runtime errors if the assigned value doesn’t support the operation.
  • Use with caution, as it defeats the purpose of TypeScript’s type system.

Never

This is kind of interesting types available in typescript. It is used for the code that will never return anything, for example infinite loop. Here in below example, the when the function abcd is executed, the control is trapped inside the infinite loop.:

function abcd():never {
    while(true){}
}
console.log("ABC");
abcd()
console.log("XYZ");

While writing the above code in VS Code, the console.log("XYZ") is dimmed out, which indicates this line won’t be executed ever.This makes debugging the code more easier.

Unknown

Similar to type any but has few differeneces.

let k:unknown;
k=12;
k="sam"
Featureanyunknown
PurposeDisables type checking entirely.Represents any value, but requires type checking before usage.
Type SafetyMinimal type safety. Can lead to runtime errors if not used carefully.Enforces type safety by requiring checks before using the value.
OperationsAllows any operation on the value without type checks.Restricts operations until the type is determined.
Use CasesSuitable for rapid prototyping or when interacting with untyped libraries.Preferable when working with dynamic or external values to ensure type safety.
RecommendationUse sparingly, if ever.Prefer unknown for better type safety.

In Short:

  • Represents any value, but requires type checking before usage.
  • You cannot directly access properties or call methods on an ‘unknown’ value.
  • Enforces type safety by preventing operations until the type is narrowed down.
  • Preferred over ‘any’ when dealing with values of uncertain or unspecified types.

Example:

let value1: any = "hello"; 
value1 = 10; // No compile-time error, but may cause runtime errors

let value2: unknown = "hello"; 
// value2.toUpperCase(); // Compile-time error: Cannot access 'toUpperCase' of unknown 

if (typeof value2 === "string") {
  value2.toUpperCase(); // Now compiles
}

Union Types

Union types are types which tells that either the variable can be of type A or type B. A variable can be of multiple type based on dirrerent use cases. For creating multiple types.

syntax: let v1: <type1> | <type2> | ....

let v1: string | number
v1 = 10;
v1 = "ten"

In the above example, we can store both string or number.

Intersection Types

Intersection are just opposite of union. These types are types with combination:

syntax: let v1: <type1> & <type2> & ....

let v1: string & number

In the above example, v1 becomes a type of varible that is common to both string and number! But something like that doesn’t exist. In this case it is by deafult treated as never

So How do we Implement Intersection?

For that we must know that in typescript, we can create our own custom type.

3.Custom Types

There are different ways to create custom types such as:

  • Type Alias
  • Literal types
  • Interface

Type Alias:

Example:

type Teacher = {
    name:string,
    isTeacher:boolean
}
type Student = {
    name:string,
    age:number,
}

let t1: Teacher & Student = {
    isTeacher:true,
    name:"teacher1",
    age:40
}

In above example, t1 will be an object that must have all three properties i.e name, age and isTeacher property.

We can also define type aliases for basic types as well. for example:

type STATUS_CODE = number;
let serverResponse:STATUS_CODE = 200;

More on type Alias

You can make a property of an object as read Only. Also we can use ? to define an optional property.

type User = {
    readonly _id: string,
    userName: string,
    age?: number
}

let myUser:User = {
    _id:"123",
    userName:"sam",
} /** note that we haven't used age property here */
myUser.userName = "SAM";
// myUser._id = "564"; /** not allowed */

Combining type alias

type fName = {
    firstName:string,
}
type lName = {
    lastName:string,
}
type Name = fName & {midName:string} & lName;

const person:Name = {
    firstName:"john",
    midName:"kate",
    lastName:"doe"
}

Arrays and type alias

/** creating a type */
type User = {
    name: string,
    age: number
}
/** creating an array with elements of type User */
let allUsers: User[] = [{
    name: "SAM",
    age: 14
},
{
    name: "MAN",
    age: 12
}];

Literal types

type color = "red" | "green" | "blue";
const myColor:color = "blue";

The color can be assigned between these three values only

Before moving to Interface, we need to learn about defining types for functions. So first let’s discuss about functions and its type defination.

Functions

Functions are block of code that is used to do a specific task.

syntax:

function fName(parameter:type):return_type {/** function defination */}

example:

function createUser(userName:string, age:number) {
    console.log(`HI ${userName}, You're ${age}`); 
}

call the function: createUser("sam")

How to return a value form function?

function sum(a:number, b:number) : number {
    return a+b;
}

call the function: sum(10,20);

Optional parameters

In javascript, we have the ability to optionally pass a parameter and in the function we could assign default value.The same can be achived using ts:

function connect(port:number=3000){
    console.log("connected to:" + port)
}
connect();

Passing objects

function connect(obj:{user:string,port:number}) {
    console.log(` ${obj.user} connected to : ${obj.port}`);
}
connect({user:"sam",port:3000})

Optional parameters

What if you need to pass an object but do not need to provide all values. To do the same you can use ? :

function connect(obj:{user:string,port?:number}) {
    obj.port = (obj.port != undefined ) ? obj.port : 3000; /* default value */
    console.log(`${obj.user} connected to : ${obj.port}`);
}
connect({user:"sam"})

Rest parameters

In ts we can also use the concept of rest parameter of javascript:

function names(firstName:string,...nameArr:string[]) {
    console.log(firstName); // sam
    
    nameArr.forEach(item => {
        console.log(item); // "rohan","raghav"
    });
}
names("sam","rohan","raghav");
function connect(obj:{user:string,port?:number}) {
    obj.port = (obj.port != undefined ) ? obj.port : 3000; /* default value */
    console.log(`${obj.user} connected to : ${obj.port}`);
}
connect({user:"sam"})

Fun stuff in functions:

const fruits = ["mango","guava","banana"];
fruits.map((fruit:string):string=>{
    return `the fruit is ${fruit}`
    // return 1; /** not allowed */
})

Here fruits is an array of string. Most of the time we use map, reduce and filter on arrays. The above map function has fruits as parameter of type string and return type of string as well.

function sendMsg(msg:string):void {
    console.log(msg);
    // return 1; /** not allowed */
}

We can also use void as a return type of a function.

What happens when a function throws a new error? What is it’s return type? ofcourse we could use void, but a better approach is using type never Why?

function throwError(msg:string):never {
    throw new Error(msg);
}

Hope you enjoyed reading this, Get to know the basics of typescript, Read more about more advance concepts like Interface & Classes in my next post. You can bookmark or share the post. Comment your thoughts below if you liked reading the post!