Six Advanced TypeScript Techniques: Advanced Types, Decorators, Namespaces, Mixins, Type Guards, and Utility Types
This article introduces six advanced TypeScript techniques—advanced types, decorators, namespaces, mixins, type guards, and utility types—providing clear examples and code snippets that demonstrate how to improve code quality, modularity, and type safety in modern JavaScript projects.
1 — Advanced Types
Advanced types such as mapped types and conditional types let you construct new types from existing ones, giving your code greater flexibility and maintainability.
Mapped Types
Mapped types iterate over the properties of an existing type and apply a transformation, for example creating a readonly version of a type.
type Readonly
= { readonly [P in keyof T]: T[P]; };
interface Point { x: number; y: number; }
type ReadonlyPoint = Readonly
;The example defines a Readonly mapped type that takes a generic type T and makes all its properties readonly, then creates ReadonlyPoint based on the Point interface.
Conditional Types
Conditional types allow you to create new types based on a condition, using a syntax similar to the ternary operator with the extends keyword.
type NonNullable
= T extends null | undefined ? never : T;This defines a NonNullable type that removes null and undefined from T . The article then shows how a readonly type cannot be used where a mutable type is expected, preventing accidental side‑effects.
2 — Decorators
Decorators are powerful higher‑order functions that can add metadata or modify the behavior of classes, methods, properties, and parameters.
Class Decorator
function LogClass(target: Function) {
console.log(`Class ${target.name} was defined.`);
}
@LogClass
class MyClass {
constructor() {}
}The LogClass decorator logs the name of the class when it is defined.
Method Decorator
function LogMethod(target: any, key: string, descriptor: PropertyDescriptor) {
console.log(`Method ${key} was called.`);
}
class MyClass {
@LogMethod
myMethod() {
console.log("Inside myMethod.");
}
}
const instance = new MyClass();
instance.myMethod();The LogMethod decorator logs the method name each time the method is invoked.
Property Decorator
function DefaultValue(value: any) {
return (target: any, key: string) => {
target[key] = value;
};
}
class MyClass {
@DefaultValue(42)
myProperty: number;
}
const instance = new MyClass();
console.log(instance.myProperty); // 42The DefaultValue decorator assigns a default value to the decorated property.
Parameter Decorator
function LogParameter(target: any, key: string, parameterIndex: number) {
console.log(`Method ${key} parameter ${parameterIndex} was called.`);
}
class MyClass {
myMethod(@LogParameter value: number) {
console.log(`In myMethod, value = ${value}.`);
}
}
const instance = new MyClass();
instance.myMethod(5);The LogParameter decorator logs the index of the parameter when the method is called.
3 — Namespaces
Namespaces provide a way to organize related code and avoid naming collisions by grouping classes, interfaces, functions, and variables.
Defining a Namespace
namespace MyNamespace {
export class MyClass {
constructor(public value: number) {}
displayValue() {
console.log(`The value is: ${this.value}`);
}
}
}The MyNamespace namespace contains an exported MyClass that can be accessed from outside the namespace.
Using a Namespace
// Fully qualified name
const instance1 = new MyNamespace.MyClass(5);
instance1.displayValue(); // The value is: 5
// Namespace import
import MyClass = MyNamespace.MyClass;
const instance2 = new MyClass(10);
instance2.displayValue(); // The value is: 10Both fully qualified names and namespace imports are demonstrated.
Nested Namespaces
namespace OuterNamespace {
export namespace InnerNamespace {
export class MyClass {
constructor(public value: number) {}
displayValue() {
console.log(`The value is: ${this.value}`);
}
}
}
}
const instance = new OuterNamespace.InnerNamespace.MyClass(15);
instance.displayValue(); // The value is: 15Nested namespaces allow hierarchical organization of code.
4 — Mixins
Mixins let you compose classes from smaller reusable parts, promoting modularity and code reuse.
Defining a Mixin
class TimestampMixin
any>(Base: TBase) {
constructor(...args: any[]) {
super(...args);
}
getTimestamp() {
return new Date();
}
}The TimestampMixin adds a getTimestamp method that returns the current date and time.
Using a Mixin
class MyBaseClass {
constructor(public value: number) {}
displayValue() {
console.log(`The value is: ${this.value}`);
}
}
class MyMixedClass extends TimestampMixin(MyBaseClass) {
constructor(value: number) {
super(value);
}
}
const instance = new MyMixedClass(42);
instance.displayValue(); // The value is: 42
const timestamp = instance.getTimestamp();
console.log(`The timestamp is: ${timestamp}`);An instance of MyMixedClass has both the base class method and the mixin method.
5 — Type Guards
Type guards narrow the type of a variable within a code block, enabling safe access to type‑specific properties and methods.
Defining a Type Guard
function isString(value: any): value is string {
return typeof value === "string";
}The isString function returns a type predicate that narrows value to string when true.
Using a Type Guard
function processValue(value: string | number) {
if (isString(value)) {
console.log(`The length of the string is: ${value.length}`);
} else {
console.log(`The square of the number is: ${value * value}`);
}
}
processValue("hello"); // The length of the string is: 5
processValue(42); // The square of the number is: 1764The example shows how the guard enables different logic for strings and numbers.
6 — Utility Types
Utility types provide shortcuts for transforming existing types into new ones, improving reusability and type safety.
Using Utility Types
interface Person {
name: string;
age: number;
email: string;
}
type PartialPerson = Partial
;
type ReadonlyPerson = Readonly
;
type NameAndAge = Pick
;
type WithoutEmail = Omit
;Examples of Partial , Readonly , Pick , and Omit are shown.
Partial
const partialPerson: PartialPerson = {
name: "John Doe",
};Partial makes all properties optional.
Readonly
const readonlyPerson: ReadonlyPerson = {
name: "Jane Doe",
age: 30,
email: "[email protected]",
};
// readonlyPerson.age = 31; // Error: cannot assign to read‑only propertyReadonly makes all properties immutable.
Pick
const nameAndAge: NameAndAge = {
name: "John Smith",
age: 25,
};
// nameAndAge.email; // Error: property does not existPick creates a type with only the selected properties.
Omit
const withoutEmail: WithoutEmail = {
name: "Jane Smith",
age: 28,
};
// withoutEmail.email; // Error: property does not existOmit creates a type by removing specified properties.
Conclusion
The article covered advanced TypeScript topics—including namespaces, advanced types, decorators, mixins, type guards, and utility types—showing how they can be used to write more modular, reusable, and maintainable code while leveraging the language's powerful type system.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.