TypeScript Tips and Tricks for Writing Better Code

Are you tired of writing code that is hard to read and maintain? Do you want to improve your TypeScript skills and write better code? Look no further! In this article, we will share some tips and tricks for writing better TypeScript code.

Use Strict Mode

TypeScript has a strict mode that helps catch common errors at compile-time. Enabling strict mode can help you write more robust code and catch errors before they become runtime errors. To enable strict mode, add the following line to your tsconfig.json file:

{
  "compilerOptions": {
    "strict": true
  }
}

Use Interfaces

Interfaces are a powerful feature of TypeScript that allow you to define the shape of an object. By using interfaces, you can ensure that your code is type-safe and avoid runtime errors. Here's an example:

interface Person {
  name: string;
  age: number;
}

function greet(person: Person) {
  console.log(`Hello, ${person.name}! You are ${person.age} years old.`);
}

const john: Person = { name: 'John', age: 30 };
greet(john);

In this example, we define an interface Person that has two properties: name and age. We then define a function greet that takes a Person object as an argument and logs a greeting to the console. Finally, we create a Person object john and pass it to the greet function.

Use Enums

Enums are another powerful feature of TypeScript that allow you to define a set of named constants. By using enums, you can make your code more readable and avoid magic numbers. Here's an example:

enum Color {
  Red,
  Green,
  Blue,
}

function getColorName(color: Color) {
  switch (color) {
    case Color.Red:
      return 'red';
    case Color.Green:
      return 'green';
    case Color.Blue:
      return 'blue';
    default:
      throw new Error(`Unknown color: ${color}`);
  }
}

const color = Color.Red;
console.log(getColorName(color)); // Output: "red"

In this example, we define an enum Color that has three constants: Red, Green, and Blue. We then define a function getColorName that takes a Color enum as an argument and returns the corresponding color name. Finally, we create a Color variable color and pass it to the getColorName function.

Use Type Aliases

Type aliases are a way to create a new name for an existing type. By using type aliases, you can make your code more readable and avoid repeating complex types. Here's an example:

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

function distance(a: Point, b: Point) {
  const dx = a.x - b.x;
  const dy = a.y - b.y;
  return Math.sqrt(dx * dx + dy * dy);
}

const a: Point = { x: 0, y: 0 };
const b: Point = { x: 3, y: 4 };
console.log(distance(a, b)); // Output: 5

In this example, we define a type alias Point that has two properties: x and y. We then define a function distance that takes two Point objects as arguments and returns the distance between them. Finally, we create two Point objects a and b and pass them to the distance function.

Use Generics

Generics are a way to create reusable code that works with a variety of types. By using generics, you can write more flexible and reusable code. Here's an example:

function identity<T>(value: T): T {
  return value;
}

console.log(identity<string>('hello')); // Output: "hello"
console.log(identity<number>(42)); // Output: 42

In this example, we define a function identity that takes a generic type T as an argument and returns the same type. We then call the identity function with two different types: string and number.

Use Optional Chaining

Optional chaining is a new feature in TypeScript that allows you to safely access nested properties of an object. By using optional chaining, you can avoid runtime errors when accessing properties that may not exist. Here's an example:

interface Person {
  name: string;
  address?: {
    street: string;
    city: string;
    state: string;
  };
}

const person: Person = { name: 'John' };
console.log(person.address?.city); // Output: undefined

person.address = { street: '123 Main St', city: 'Anytown', state: 'CA' };
console.log(person.address?.city); // Output: "Anytown"

In this example, we define an interface Person that has a property address that is optional. We then create a Person object person without an address property and log the city property using optional chaining. Finally, we add an address property to the person object and log the city property again.

Use Type Guards

Type guards are a way to narrow down the type of a variable based on a condition. By using type guards, you can write more type-safe code and avoid runtime errors. Here's an example:

interface Cat {
  name: string;
  meow(): void;
}

interface Dog {
  name: string;
  bark(): void;
}

function isCat(animal: Cat | Dog): animal is Cat {
  return 'meow' in animal;
}

function makeSound(animal: Cat | Dog) {
  if (isCat(animal)) {
    animal.meow();
  } else {
    animal.bark();
  }
}

const cat: Cat = { name: 'Fluffy', meow() { console.log('meow'); } };
const dog: Dog = { name: 'Fido', bark() { console.log('bark'); } };
makeSound(cat); // Output: "meow"
makeSound(dog); // Output: "bark"

In this example, we define two interfaces Cat and Dog that have different methods. We then define a function isCat that checks if an animal object is a Cat object using the in operator. We then define a function makeSound that takes an animal object as an argument and calls the appropriate method based on the type of the object. Finally, we create a Cat object cat and a Dog object dog and pass them to the makeSound function.

Use Type Assertion

Type assertion is a way to tell the TypeScript compiler that you know more about the type of a variable than it does. By using type assertion, you can write more flexible code and avoid unnecessary type errors. Here's an example:

interface Animal {
  name: string;
}

interface Cat extends Animal {
  meow(): void;
}

interface Dog extends Animal {
  bark(): void;
}

function makeSound(animal: Animal) {
  if ((animal as Cat).meow) {
    (animal as Cat).meow();
  } else if ((animal as Dog).bark) {
    (animal as Dog).bark();
  } else {
    throw new Error(`Unknown animal: ${animal.name}`);
  }
}

const cat: Cat = { name: 'Fluffy', meow() { console.log('meow'); } };
const dog: Dog = { name: 'Fido', bark() { console.log('bark'); } };
makeSound(cat); // Output: "meow"
makeSound(dog); // Output: "bark"

In this example, we define three interfaces Animal, Cat, and Dog that have different methods. We then define a function makeSound that takes an animal object as an argument and calls the appropriate method based on the type of the object using type assertion. Finally, we create a Cat object cat and a Dog object dog and pass them to the makeSound function.

Use Type Inference

Type inference is a way for TypeScript to automatically determine the type of a variable based on its value. By using type inference, you can write more concise code and avoid unnecessary type annotations. Here's an example:

const name = 'John';
const age = 30;
const person = { name, age };
console.log(person); // Output: { name: "John", age: 30 }

In this example, we define three variables name, age, and person. We then create an object person using object shorthand notation and log it to the console. TypeScript automatically infers the types of the variables based on their values.

Conclusion

In this article, we have shared some tips and tricks for writing better TypeScript code. By using strict mode, interfaces, enums, type aliases, generics, optional chaining, type guards, type assertion, and type inference, you can write more robust, readable, and maintainable code. Happy coding!

Additional Resources

coinalerts.app - crypto alerts. Cryptos that rise or fall very fast, that hit technical indicators like low or high RSI. Technical analysis alerts
servicemesh.app - service mesh in the cloud, for microservice and data communications
cloudgovernance.dev - governance and management of data, including data owners, data lineage, metadata
taxonomy.cloud - taxonomies, ontologies and rdf, graphs, property graphs
zerotrustsecurity.cloud - zero trust security in the cloud
googlecloud.run - google cloud run
dart.run - the dart programming language running in the cloud
playrpgs.app - A community about playing role playing games
sitereliability.app - site reliability engineering SRE
mlethics.dev - machine learning ethics
bestpractice.app - best practice in software development, software frameworks and other fields
invented.dev - learning first principles related to software engineering and software frameworks. Related to the common engineering trope, "you could have invented X"
rust.guide - programming the rust programming language, and everything related to the software development lifecyle in rust
learncdk.dev - learning terraform and amazon cdk deployment
neo4j.guide - a guide to neo4j
rustlang.app - rust programming languages
coinexchange.dev - crypto exchanges, integration to their APIs
ner.systems - A saas about named-entity recognition. Give it a text and it would identify entities and taxonomies
tasklist.run - running tasks online
crates.run - A site for running rust applications and servers


Written by AI researcher, Haskell Ruska, PhD (haskellr@mit.edu). Scientific Journal of AI 2023, Peer Reviewed