Intermediate TypeScript: Unleashing the Full Potential

TypeScript has emerged as a powerful superset of JavaScript, adding static typing to the dynamic nature of JavaScript. While basic TypeScript knowledge is essential, understanding intermediate concepts can significantly enhance your development experience, making your code more robust, maintainable, and scalable. In this blog post, we will delve into the fundamental concepts of intermediate TypeScript, explore their usage methods, common practices, and best practices.

Table of Contents#

  1. Advanced Types
  2. Generics
  3. Decorators
  4. Namespaces and Modules
  5. Common Practices and Best Practices
  6. Conclusion
  7. References

Advanced Types#

Union Types#

Union types allow a variable to have one of several types. You can define a union type using the | operator.

let value: string | number;
value = "hello"; // valid
value = 10; // valid
// value = true; // invalid

Intersection Types#

Intersection types combine multiple types into one. An object of an intersection type must satisfy all the types in the intersection.

interface Person {
    name: string;
}
 
interface Employee {
    employeeId: number;
}
 
type PersonEmployee = Person & Employee;
 
const personEmployee: PersonEmployee = {
    name: "John",
    employeeId: 123
};

Type Guards#

Type guards are expressions that perform a runtime check that guarantees the type in a certain scope.

function printValue(value: string | number) {
    if (typeof value === "string") {
        console.log(value.toUpperCase());
    } else {
        console.log(value.toFixed(2));
    }
}

Generics#

What are Generics?#

Generics allow you to create reusable components that can work with different types. They provide a way to parameterize types.

Using Generics in Functions and Classes#

// Generic function
function identity<T>(arg: T): T {
    return arg;
}
 
const result = identity<string>("hello");
 
// Generic class
class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}
 
const myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = (x, y) => x + y;

Decorators#

Introduction to Decorators#

Decorators are a way to add metadata and behavior to classes, methods, accessors, properties, or parameters at design time.

Class Decorators#

function logClass(target: Function) {
    console.log(`Class ${target.name} was created`);
}
 
@logClass
class MyClass {
    constructor() {}
}

Method Decorators#

function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
        console.log(`Method ${propertyKey} was called with arguments: ${JSON.stringify(args)}`);
        const result = originalMethod.apply(this, args);
        console.log(`Method ${propertyKey} returned: ${result}`);
        return result;
    };
    return descriptor;
}
 
class MyClass {
    @logMethod
    add(a: number, b: number) {
        return a + b;
    }
}
 
const myObj = new MyClass();
myObj.add(1, 2);

Namespaces and Modules#

Namespaces#

Namespaces (formerly known as internal modules) are used to organize code into logical groups and avoid naming conflicts.

namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
 
    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5;
        }
    }
}
 
const validator: Validation.StringValidator = new Validation.ZipCodeValidator();
console.log(validator.isAcceptable("12345"));

Modules#

Modules are used to split code into separate files and manage dependencies.

// math.ts
export function add(a: number, b: number) {
    return a + b;
}
 
// main.ts
import { add } from './math';
 
console.log(add(1, 2));

Common Practices and Best Practices#

Type Assertion with Caution#

Type assertion should be used sparingly as it bypasses TypeScript's type checking.

const value: any = "hello";
const length = (value as string).length;

Keep Generics Simple#

Avoid over-complicating generic types. Use them only when necessary to improve code reuse.

Use Decorators Wisely#

Decorators can add complexity to the code. Use them when they provide significant benefits, such as logging, validation, or aspect-oriented programming.

Conclusion#

Intermediate TypeScript concepts like advanced types, generics, decorators, namespaces, and modules provide powerful tools to write more robust and maintainable code. By mastering these concepts and following the best practices, you can take your TypeScript development skills to the next level.

References#