boolean
, number
, string
, object
, Array
, enum
, any
, void
, never
, unknown
They are basically ES6 classes with additional features like access modifiers, indexers etc.
Yes to both. The interface cannot have any implementation of it’s methods in contrast to the abstract class. Additionally the abstract class can have fields.
private
, public
and protected
Same as ES6 inheritance.
Using the implements
keyword.
Using the super
keyword.
Using the exports
keyword. Same as ES6.
Not in a native way, but you can have a single implementation with multiple signatures.
A mix of multiple types using the |
or &
operators.
&
creates a new type type holds all the common properties between type A and type B.|
creates a new type that holds all the properties for both type A and type B.Ability to infer the type from the existing code. There are 2 strategies:
For more information: Type Inference
Mapped Types in TypeScript are a powerful feature that enables the creation of new types based on existing ones. They allow you to iterate over the keys of an existing type and transform them to construct a new type. This feature is highly useful for creating types that are variations of existing ones, such as making all properties of a type optional or readonly.
Suppose you have an interface representing a User
, and you want to create a new type where all the properties of User
are optional. Mapped Types make this task straightforward:
interface User {
id: number;
name: string;
email: string;
}
// Creating a Mapped Type where all properties of User are optional
type PartialUser = {
[Property in keyof User]?: User[Property];
};
// PartialUser now has all properties of User, but they are optional
Template Literal Types in TypeScript are a sophisticated feature introduced in TypeScript 4.1. They allow us to define types using template literal strings, offering more flexibility and control over string types. This feature is especially useful when working with string patterns that follow a specific format.
Imagine you need to define a type for a string that must start with ‘http://’ or ‘https://’. With Template Literal Types, you can do this easily:
type URL = `http://${string}` | `https://${string}`;
Index Accessed Types, also known as Lookup Types, are a feature in TypeScript that allow for accessing the type of a property in an object or an interface. This feature is particularly useful for maintaining type safety in dynamic code structures.
Suppose you have an interface representing a user and you want to extract the type of a specific property, say ‘name’, from this interface. With Index Accessed Types, you can do this effortlessly:
interface User {
id: number;
name: string;
email: string;
}
type UserNameType = User['name']; // UserNameType is now of type string
Conditional Types in TypeScript allow us to define types that can change based on certain conditions. This feature adds a level of logic to type definitions, enabling more dynamic and adaptable type behaviors. It’s akin to using ‘if’ statements at the type level.
Imagine you have a generic function that should return different types based on the input. Conditional Types can be used to define this behavior precisely:
type Numeric = number;
type Textual = string;
// Define a Conditional Type
type ResponseType<T> = T extends number ? Numeric : Textual;
// Usage in a function
function processInput<T>(input: T): ResponseType<T> {
if (typeof input === 'number') {
return (input * 2) as ResponseType<T>; // Returns a Numeric type
}
return `Processed: ${input}` as ResponseType<T>; // Returns a Textual type
}
Recursive Types in TypeScript allow for the definition of types that can refer to themselves, directly or indirectly. This is particularly useful for modeling data structures that are naturally recursive, like trees, linked lists, or any hierarchical data.
Consider a scenario where you need to represent a file system with folders and files, where each folder can contain more folders and files. This is a perfect use case for Recursive Types:
// Define a Recursive Type for a file system structure
type FileSystemItem = {
name: string;
type: 'file' | 'folder';
children?: FileSystemItem[]; // Recursive reference
};
// Example usage
const fileSystem: FileSystemItem = {
name: 'root',
type: 'folder',
children: [
{
name: 'Documents',
type: 'folder',
children: [
{
name: 'resume.docx',
type: 'file'
}
]
},
{
name: 'photo.jpg',
type: 'file'
}
]
};
Conditional Recursive Types in TypeScript are an advanced feature that blends the flexibility of conditional types with the self-referencing nature of recursive types. This combination allows for defining types whose structure can change based on certain conditions and can reference themselves recursively.
Imagine a scenario where you need to model a navigation menu. This menu has items that can either be simple links or dropdowns containing more items. Conditional Recursive Types can elegantly represent this:
// Define a Conditional Recursive Type for a navigation menu
type MenuItem = {
label: string;
type: 'link' | 'dropdown';
href?: string;
items?: MenuItem[]; // Recursive reference
} extends infer T ? (T extends { type: 'link' } ? Omit<T, 'items'> : T) : never;
// Example usage
const menu: MenuItem[] = [
{ label: 'Home', type: 'link', href: '/home' },
{
label: 'About',
type: 'dropdown',
items: [
{ label: 'Team', type: 'link', href: '/team' },
{ label: 'History', type: 'link', href: '/history' }
]
}
];
Type Narrowing in TypeScript is a key concept where the compiler refines the type of a variable within a certain scope based on type checks or guards. This process reduces the possible types a value can be, making it easier to work with and more predictable in terms of its behavior and attributes.
Consider a function that takes an input of a union type (string | number
) and you want to perform different operations based on the actual type of the input:
function processInput(input: string | number) {
if (typeof input === 'string') {
console.log('Input is a string:', input.toUpperCase());
} else {
console.log('Input is a number:', input.toFixed(2));
}
}
in
operator?The in
operator in TypeScript is used for type narrowing and to check if a property exists on an object. This operator is particularly useful when you’re dealing with types that might have different sets of keys, such as in union types. It helps in ensuring that the code behaves correctly by verifying the existence of properties before accessing them.
in
operator enhances type safety by checking for the existence of a property on an object before performing operations on it.Imagine you have a union type representing either a Circle
or a Square
, and you need to write a function that behaves differently based on the shape provided:
type Circle = {
radius: number;
};
type Square = {
sideLength: number;
};
type Shape = Circle | Square;
function getArea(shape: Shape): number {
if ('radius' in shape) {
// Type of shape is narrowed to Circle
return Math.PI * shape.radius ** 2;
} else {
// Type of shape is narrowed to Square
return shape.sideLength ** 2;
}
}