DEV Community

Cover image for 15 Advanced TypeScript Tips for Development
Lakshmanan Arumugam
Lakshmanan Arumugam

Posted on • Edited on • Originally published at lakshmananarumugam.com

15 Advanced TypeScript Tips for Development

1.Optional Chaining (?.):
Optional chaining allows you to safely access nested properties or methods without worrying about null or undefined values. It short-circuits the evaluation if any intermediate property is null or undefined.

const user = {
  name: 'John',
  address: {
    city: 'New York',
    postalCode: '12345'
  }
};


const postalCode = user.address?.postalCode;
console.log(postalCode); // Output: 12345

const invalidCode = user.address?.postalCode?.toLowerCase();
console.log(invalidCode); // Output: undefined
Enter fullscreen mode Exit fullscreen mode

2.Nullish Coalescing Operator (??):
The nullish coalescing operator provides a default value when a variable is null or undefined.

const name = null;
const defaultName = name ?? 'Unknown';
console.log(defaultName); // Output: Unknown

const age = 0;
const defaultAge = age ?? 18;
console.log(defaultAge); // Output: 0
Enter fullscreen mode Exit fullscreen mode

3.Type Assertion:
Type assertion allows you to explicitly define the type of a variable when TypeScript is unable to infer it.

const userInput: unknown = 'Hello World';
const strLength = (userInput as string).length;
console.log(strLength); // Output: 11
Enter fullscreen mode Exit fullscreen mode

4.Generics:
Generics enable you to create reusable components that can work with a variety of types.

function reverse<T>(items: T[]): T[] {
  return items.reverse();
}

const numbers = [1, 2, 3, 4, 5];
const reversedNumbers = reverse(numbers);
console.log(reversedNumbers); // Output: [5, 4, 3, 2, 1]

const strings = ['a', 'b', 'c'];
const reversedStrings = reverse(strings);
console.log(reversedStrings); // Output: ['c', 'b', 'a']
Enter fullscreen mode Exit fullscreen mode

5.keyof Operator:
The keyof operator returns a union of all known property names of a given type.

interface User {
  id: number;
  name: string;
  email: string;
}

function getUserProperty(user: User, property: keyof User) {
  return user[property];
}

const user: User = {
  id: 1,
  name: 'John Doe',
  email: 'john@example.com'
};

const name = getUserProperty(user, 'name');
console.log(name); // Output: John Doe

const invalidProperty = getUserProperty(user, 'age'); // Error: Argument of type '"age"' is not assignable to parameter of type '"id" | "name" | "email"'
Enter fullscreen mode Exit fullscreen mode

6.Type Guards:
Type guards allow you to narrow down the type of a variable within a conditional block, based on a certain condition.

function logMessage(message: string | number) {
  if (typeof message === 'string') {
    console.log('Message: ' + message.toUpperCase());
  } else {
    console.log('Value: ' + message.toFixed(2));
  }
}

logMessage('hello'); // Output: Message: HELLO
logMessage(3.14159); // Output: Value: 3.14
Enter fullscreen mode Exit fullscreen mode

7.Intersection Types:
Intersection types allow you to combine multiple types into a single type, creating a new type that has all the properties and methods of the intersected types.

interface Loggable {
  log: () => void;
}

interface Serializable {
  serialize: () => string;
}

type Logger = Loggable & Serializable;

class ConsoleLogger implements Loggable {
  log() {
    console.log('Logging to console...');
  }
}

class FileLogger implements Loggable, Serializable {
  log() {
    console.log('Logging to file...');
  }

  serialize() {
    return 'Serialized log data';
  }
}

const logger1: Logger = new ConsoleLogger();
logger1.log(); // Output: Logging to console...

const logger2: Logger = new FileLogger();
logger2.log(); // Output: Logging to file...
console.log(logger2.serialize()); // Output: Serialized log data
Enter fullscreen mode Exit fullscreen mode

8.Mapped Types:
Mapped types allow you to create new types by transforming the properties of an existing type.

interface User {
  id: number;
  name: string;
  email: string;
}

type PartialUser = { [K in keyof User]?: User[K] };

const partialUser: PartialUser = {
  name: 'John Doe',
  email: 'john@example.com'
};

console.log(partialUser); // Output: { name: 'John Doe', email: 'john@example.com' }
Enter fullscreen mode Exit fullscreen mode

9.String Literal Types and Union Types:
TypeScript supports string literal types and union types, which can be used to define specific sets of values for a variable.

type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';

function sendRequest(url: string, method: HttpMethod) {
  // send request logic here...
}

sendRequest('/users', 'GET');
sendRequest('/users', 'POST');
sendRequest('/users/1', 'PUT');
sendRequest('/users/1', 'DELETE');
Enter fullscreen mode Exit fullscreen mode

10.Decorators:
Decorators allow you to modify or extend the behavior of classes, methods, properties, and other declarations.

function uppercase(target: any, propertyKey: string) {
  let value = target[propertyKey];

  const getter = () => value;
  const setter = (newValue: string) => {
    value = newValue.toUpperCase();
  };

  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}

class Person {
  @uppercase
  name: string;
}

const person = new Person();
person.name = 'John Doe';
console.log(person.name); // Output: JOHN DOE
Enter fullscreen mode Exit fullscreen mode

11.Index Signatures:
Index signatures allow you to define dynamic property names and their corresponding types in an interface or type.

interface Dictionary {
  [key: string]: number;
}

const scores: Dictionary = {
  math: 90,
  science: 85,
  history: 95
};

console.log(scores['math']); // Output: 90
console.log(scores['english']); // Output: undefined
Enter fullscreen mode Exit fullscreen mode

12.Type Inference with Conditional Statements:
TypeScript can infer the types based on conditional statements, allowing for more concise code.

function calculateTax(amount: number, isTaxable: boolean) {
  if (isTaxable) {
    return amount * 1.1; // Type: number
  } else {
    return amount; // Type: number
  }
}

const taxableAmount = calculateTax(100, true);
console.log(taxableAmount.toFixed(2)); // Output: 110.00

const nonTaxableAmount = calculateTax(100, false);
console.log(nonTaxableAmount.toFixed(2)); // Output: 100.00
Enter fullscreen mode Exit fullscreen mode

13.Readonly Properties:
TypeScript provides the readonly modifier to define properties that can't be modified after initialization.

class Circle {
  readonly radius: number;

  constructor(radius: number) {
    this.radius = radius;
  }

  getArea() {
    return Math.PI * this.radius ** 2;
  }
}

const circle = new Circle(5);
console.log(circle.radius); // Output: 5

// circle.radius = 10; // Error: Cannot assign to 'radius' because it is a read-only property

console.log(circle.getArea()); // Output: 78.53981633974483
Enter fullscreen mode Exit fullscreen mode

14.Type Aliases:
Type aliases allow you to create custom names for existing types, providing more semantic meaning and improving code readability.

type Point = {
  x: number;
  y: number;
};

type Shape = 'circle' | 'square' | 'triangle';

function draw(shape: Shape, position: Point) {
  console.log(`Drawing a ${shape} at (${position.x}, ${position.y})`);
}

const startPoint: Point = { x: 10, y: 20 };
draw('circle', startPoint); // Output: Drawing a circle at (10, 20)
Enter fullscreen mode Exit fullscreen mode

15.Type Guards with Classes:
Type guards can also be used with classes to narrow down the type of an object instance.

class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class Dog extends Animal {
  bark() {
    console.log('Woof!');
  }
}

function makeSound(animal: Animal) {
  if (animal instanceof Dog) {
    animal.bark(); // Type: Dog
  } else {
    console.log('Unknown animal');
  }
}

const dog = new Dog('Buddy');
const animal = new Animal('Unknown');

makeSound(dog); // Output: Woof!
makeSound(animal); // Output: Unknown animal
Enter fullscreen mode Exit fullscreen mode

Top comments (14)

Collapse
 
vickodev profile image
Vıɔk A Hıƃnıʇɐ C.

Hey bro, great post!!
A small observation. For point 8 you can use the Partial Type like this:

interface User {
  id: number;
  name: string;
  email: string;
}

type PartialUser = Partial<User>;

const partialUser: PartialUser = {
  name: "Nikola Tesla",
  email: "nikola@teslaxcorp.com"
};
Enter fullscreen mode Exit fullscreen mode

Even, add new properties, for example:

type MyOwnUser = Partial<User> & { age: number };

const ownUser: MyOwnUser = {
  name: "Nikola Tesla",
  email: "nikola@teslaxcorp.com",
  age: 27
};
Enter fullscreen mode Exit fullscreen mode
Collapse
 
devstoriesplayground profile image
Devstories Playground

__

Collapse
 
samuel-braun profile image
Samuel Braun • Edited

Another one I enjoy quiet often is the const assertion. With them you can make stuff readonly outside of class members. For example:

With as const:

// Type '"hello"'
let x = "hello" as const;

// Type 'readonly [10, 20]'
let y = [10, 20] as const;

// Type '{ readonly text: "hello" }'
let z = { text: "hello" } as const;
Enter fullscreen mode Exit fullscreen mode

Without as const:

// Type 'string'
let x = "hello";

// Type 'number[]'
let y = [10, 20];

// Type '{ text: string }'
let z = { text: "hello" };
Enter fullscreen mode Exit fullscreen mode
Collapse
 
geminii profile image
Jimmy

Hey 👋 Nice post 😉

An improvement for point 3 :

let userInput: unknown
userInput = "Hello World"
const strLength = (userInput as string).length;
console.log(strLength); // Output: 11
Enter fullscreen mode Exit fullscreen mode

Most of the time, when we create something with unknown it's because you don't know how to provide. In your case, you specify a string value that could be confusing for some beginner developers.

What do you think ? 🙂

Collapse
 
algot profile image
AlgoT

You should avoid type assertion as much as possible. You're basically telling typescript to ignore itself and listen to you instead.

Instead of type assertion, use typeguards. You can even make a function to narrow down the type:

export function isString(param: unknown): param is string {
  return typeof param === 'string';
}

const userInput: unknown = 'Hello World';

if (isString(userInput)) {
  console.log(userInput); 
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
fruntend profile image
fruntend

Сongratulations 🥳! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up 👍

Collapse
 
duck18 profile image
duck

in the optional chaining
const invalidCode = user.address?.postalCode?.toLowerCase();
console.log(invalidCode); // Output: undefined

not log undefined

Collapse
 
brense profile image
Rense Bakker

Nice list 👍 One thing I would add to the type guards example is the is operator: typescriptlang.org/docs/handbook/a...

Collapse
 
fricardi profile image
FRicardi

Great post! I've never looked a lot into decorators until now, would've never thought they were so straightforward to implement

Collapse
 
davidyaonz profile image
David Yao

It's true no matter how experienced you are, you indeed need some good tools to make you shine. Thanks for writing such a great article.

Collapse
 
funkybob profile image
Curtis Maloney

A useful list, and very well presented -- even if the first two are native Javascript features, not specific to TS :)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.