15 Must-Know TypeScript Features to Level Up Your Development Skills
TypeScript has become the go-to tool for developers building scalable, maintainable JavaScript applications. Its advanced features go far beyond basic typing, giving developers tools to create highly reusable and type-safe code. This article explores 15 advanced TypeScript concepts to take your skills to the next level.
But before we dive in, if you're looking for an in-depth resource to strengthen your JavaScript foundations, check out my eBook: JavaScript: From ES2015 to ES2023. It covers modern JavaScript features in detail, ensuring you're fully equipped to use TypeScript alongside the latest JavaScript standards.
👉 Download Javascript: from ES2015 to ES2023 - eBook
1. Mapped Types
Mapped types allow you to transform the properties of a type into a new type. This is particularly useful for creating variations of an existing type.
type ReadOnly<T> = { readonly [K in keyof T]: T[K] };
type Optional<T> = { [K in keyof T]?: T[K] };
interface User {
id: number;
name: string;
}
type ReadOnlyUser = ReadOnly<User>; // { readonly id: number; readonly name: string; }
type OptionalUser = Optional<User>; // { id?: number; name?: string; }
Mapped types are foundational for creating utility types and dynamic type manipulations.
2. Generics
Generics allow you to define reusable, type-safe components by introducing a placeholder type that can be specified later.
function identity<T>(value: T): T {
return value;
}
const stringResult = identity<string>("Hello");
const numberResult = identity<number>(42);
Generics make your functions, classes, and interfaces more versatile by adapting to various types without sacrificing type safety.
3. Generics with Type Constraints
You can constrain a generic to a certain shape or base type using the extends
keyword, ensuring the passed type meets specific criteria.
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): void {
console.log(arg.length);
}
logLength("Hello"); // Valid
logLength([1, 2, 3]); // Valid
// logLength(42); // Error: number does not have a length property
This ensures that your generic types meet specific requirements. This feature is particularly useful when working with objects that share a common property.
4. Generic Interfaces
Generic interfaces allow you to create reusable and flexible type definitions.
interface KeyValuePair<K, V> {
key: K;
value: V;
}
const pair: KeyValuePair<string, number> = { key: "age", value: 30 };
This approach is ideal for designing libraries or APIs where types can vary but maintain consistency. By defining placeholder types, you can ensure the interface adapts to various data types while maintaining consistency.
5. Conditional Types
Conditional types enable logic within types, making them dynamic based on conditions.
type IsString<T> = T extends string ? true : false;
type Result1 = IsString<string>; // true
type Result2 = IsString<number>; // false
This is particularly useful in type inference and narrowing down types dynamically. It is especially useful for building types that adapt to specific scenarios, such as APIs or utility types.Â
6. infer Keyword
The infer
keyword works within conditional types to extract or infer a type.
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function example(): string {
return "Hello, TypeScript!";
}
type ExampleReturn = ReturnType<typeof example>; // string
This is often used in libraries to extract types dynamically and improve type inference.
7. Type Variance
Type variance controls the relationships between more specific and broader types in generics. TypeScript supports covariance (subtypes flow downward) and contravariance (supertypes flow upward).
class Animal {}
class Dog extends Animal {}
let animalHandler: (animal: Animal) => void;
let dogHandler: (dog: Dog) => void;
dogHandler = animalHandler; // Valid (contravariance)
// animalHandler = dogHandler; // Error: Not all Animals are Dogs
Understanding type variance is crucial when working with generics, callbacks, and inheritance.
8. Utility Types
TypeScript provides built-in utility types like Partial
, Pick
, Omit
, and Record
that simplify type transformations.
interface User {
id: number;
name: string;
age: number;
}
type PartialUser = Partial<User>; // All fields optional
type UserIdName = Pick<User, "id" | "name">; // Only id and name
type NoIdUser = Omit<User, "id">; // All fields except id
type UserRecord = Record<string, User>; // Object with string keys and User values
These types are invaluable for reducing boilerplate and simplifying common type manipulations.
9. Keyof and Lookup Types
The keyof
operator generates a union of an object’s keys, while lookup types retrieve the type of a property by key.
interface User {
id: number;
name: string;
}
type UserKeys = keyof User; // "id" | "name"
type NameType = User["name"]; // string
These features make working with object types much more dynamic and flexible.
10. Reflections and Metadata
TypeScript doesn’t natively support runtime type reflection, but tools like reflect-metadata
enable it through decorators.
import "reflect-metadata";
function LogType(target: any, propertyKey: string) {
const type = Reflect.getMetadata("design:type", target, propertyKey);
console.log(`${propertyKey} type: ${type.name}`);
}
class Example {
@LogType
public value!: string;
}
// Output: value type: String
Reflection is widely used in frameworks like Angular and NestJS for dependency injection and runtime validation.
11. Dependency Injection
Dependency injection is a design pattern that promotes modularity by injecting dependencies instead of creating them directly. TypeScript's decorators and strong typing make this pattern seamless.
class Logger {
log(message: string) {
console.log(message);
}
}
class Service {
constructor(private logger: Logger) {}
doWork() {
this.logger.log("Work done!");
}
}
const logger = new Logger();
const service = new Service(logger);
service.doWork(); // Outputs: Work done!
This is fundamental in frameworks like Angular and NestJS.
12. Intersection and Union Types
Intersection (&
) types merge two or more types, while union (|
) types represent one of multiple types.
type A = { name: string };
type B = { age: number };
type C = A & B; // { name: string; age: number }
type D = A | B; // { name: string } | { age: number }
Union and intersection types are essential for modeling complex relationships in TypeScript.
13. Type Guards and in Operator
Type guards refine types during runtime checks, improving type safety.
function isString(value: unknown): value is string {
return typeof value === "string";
}
if (isString("Hello")) {
console.log("It's a string!");
}
type Animal = { name: string };
type Dog = { name: string; bark(): void };
function isDog(animal: Animal | Dog): animal is Dog {
return "bark" in animal;
}
Type guards are powerful for narrowing union types dynamically.
14. Template Literal Types
Template literal types allow you to construct new string literal types dynamically.
type Event = "click" | "focus" | "hover";
type EventHandlers = `on${Capitalize<Event>}`; // "onClick" | "onFocus" | "onHover"
This is useful for creating flexible and expressive types, especially for APIs and frameworks.
15. Decorators
Decorators are experimental features used for annotating and modifying classes, methods, or properties.
function Log(target: any, key: string) {
console.log(`${key} was accessed`);
}
class Example {
@Log
sayHello() {
console.log("Hello, world!");
}
}
Decorators are heavily used in frameworks like Angular.
A Final Note
Mastering these advanced TypeScript concepts will allow you to build type-safe, scalable applications. But don't stop there—if you want to strengthen your JavaScript fundamentals alongside modern TypeScript skills, consider my eBook: JavaScript: From ES2015 to ES2023. It’s the ultimate guide to mastering modern JavaScript, covering essential topics like async/await, modules, and advanced features like proxies and decorators.
Would you like to explore more about TypeScript, JavaScript, or how the two can be combined effectively? Let me know!
Please login or create new account to add your comment.