TypeScript’s static typing allows developers to define the types of variables, function parameters, and return values. This helps catch errors at compile - time rather than at runtime. For example:
// Define a function with typed parameters and return value
function add(a: number, b: number): number {
return a + b;
}
// This will compile without errors
const result = add(3, 5);
// This will cause a compile - time error
// const wrongResult = add('3', 5);
Interfaces in TypeScript are used to define the structure of an object. They act as a contract that an object must adhere to.
interface Person {
name: string;
age: number;
}
function greet(person: Person) {
return `Hello, ${person.name}! You are ${person.age} years old.`;
}
const john: Person = { name: 'John', age: 30 };
console.log(greet(john));
Enums are used to define a set of named constants. They make the code more readable and maintainable.
enum Color {
Red,
Green,
Blue
}
let myColor: Color = Color.Green;
console.log(myColor); // Output: 1
Function overloading in TypeScript allows a function to have multiple call signatures. This is useful when a function can accept different types of parameters.
function reverse(input: string): string;
function reverse(input: number[]): number[];
function reverse(input: string | number[]): string | number[] {
if (typeof input === 'string') {
return input.split('').reverse().join('');
} else {
return input.slice().reverse();
}
}
const reversedString = reverse('hello');
const reversedArray = reverse([1, 2, 3]);
Generics provide a way to create reusable components that can work with different types.
function identity<T>(arg: T): T {
return arg;
}
const output1 = identity<string>('myString');
const output2 = identity<number>(100);
userName
, calculateTotal
.UserProfile
, PersonInterface
.MAX_LENGTH
.Each function or class should have a single, well - defined responsibility. For example:
// Bad practice
function handleUserInputAndSaveToDatabase(input: string) {
// Validate input
if (input.length < 3) {
throw new Error('Input is too short');
}
// Save to database
// Assume we have a Database class
const db = new Database();
db.save(input);
}
// Good practice
function validateInput(input: string): boolean {
return input.length >= 3;
}
function saveToDatabase(input: string) {
const db = new Database();
db.save(input);
}
function handleUserInput(input: string) {
if (validateInput(input)) {
saveToDatabase(input);
}
}
Use try - catch blocks to handle errors gracefully. Also, create custom error classes for better error handling.
class CustomError extends Error {
constructor(message: string) {
super(message);
this.name = 'CustomError';
}
}
function divide(a: number, b: number): number {
if (b === 0) {
throw new CustomError('Cannot divide by zero');
}
return a / b;
}
try {
const result = divide(10, 0);
} catch (error) {
if (error instanceof CustomError) {
console.log(error.message);
}
}
Global variables can lead to naming conflicts and make the code harder to understand and maintain. Instead, use local variables and pass them around as needed.
Use a code formatter like Prettier to keep the code consistent. Most IDEs support integrating Prettier, which can automatically format the code on save.
Writing clean code in TypeScript is a combination of understanding the fundamental concepts, using the right usage methods, following common practices, and adhering to best practices. By doing so, developers can create more maintainable, readable, and bug - free code. Static typing, interfaces, enums, and other TypeScript features provide powerful tools to achieve clean code, but it is up to the developers to use them effectively.
This blog provides a comprehensive overview of writing clean code in TypeScript. By following these principles and patterns, developers can improve the quality of their TypeScript projects.