Typescript Working With Multiple Types Complete Guide
Understanding the Core Concepts of TypeScript Working with Multiple Types
TypeScript Working with Multiple Types
TypeScript offers a powerful type system that allows developers to work with multiple types in a variety of ways, promoting flexibility and precision in code. Understanding how TypeScript handles multiple types is essential for writing robust, type-safe code. This section will delve into the details of how TypeScript allows different types to coexist and interact seamlessly.
Union Types
Description:
Union types allow you to express a value that can be one of several types. You use the pipe (|
) character to separate the different types. For example, number | string
means that the value can be either a number
or a string
.
Syntax:
let value: number | string;
value = 42; // Valid
value = "Hello"; // Valid
value = true; // Error, boolean is not a valid type
Use Cases:
Union types are particularly useful when dealing with functions that can accept multiple types:
function printId(id: number | string) {
console.log(`ID: ${id}`);
}
printId(101); // Valid
printId("user1"); // Valid
Intersection Types
Description:
Intersection types allow you to combine multiple types into a single type. This is achieved using the ampersand (&
) character. The resulting type will have all the properties and methods of the types being intersected.
Syntax:
interface Person {
name: string;
}
interface Employee {
jobTitle: string;
}
type EmployeeDetails = Person & Employee;
let employee: EmployeeDetails = {
name: "Alice",
jobTitle: "Developer"
};
Use Cases:
Intersection types are useful for creating flexible and reusable types that combine the properties of several related types:
function extendPerson(person: Person, employee: Employee): EmployeeDetails {
return {
...person,
...employee
};
}
Type Guards
Description:
Type guards are used to ensure that the type of a variable within a specific block of code is known. TypeScript provides several ways to create type guards, such as typeof
checks, instanceof
checks, and custom type predicates.
Syntax:
function printLength(value: string | number) {
if (typeof value === "string") {
console.log(`Length: ${value.length}`);
} else {
console.log(`Number: ${value}`);
}
}
Use Cases:
Type guards are crucial for working with union types, allowing you to safely perform operations based on the specific type of the variable:
function isString(value: any): value is string {
return typeof value === "string";
}
function processInput(input: string | number) {
if (isString(input)) {
console.log(`String: ${input.toUpperCase()}`);
} else {
console.log(`Number: ${input * 2}`);
}
}
Generics with Multiple Types
Description:
Generics in TypeScript are used to create components and functions that can work over a variety of types while also being type-safe. You can use generics with multiple types to create more flexible and reusable code.
Syntax:
function merge<T, U>(obj1: T, obj2: U): T & U {
return Object.assign({}, obj1, obj2);
}
const person = { name: "Alice" };
const employee = { jobTitle: "Developer" };
const result = merge(person, employee);
console.log(result.name); // "Alice"
console.log(result.jobTitle); // "Developer"
Use Cases:
Generics with multiple types are useful for creating utility functions and data structures that can handle various input types while maintaining type safety:
interface Tagged<T> {
tag: string;
data: T;
}
function createTagged<T>(tag: string, data: T): Tagged<T> {
return { tag, data };
}
const taggedNumber = createTagged<number>("number", 42);
const taggedString = createTagged<string>("string", "Hello");
Key Takeaways
- Union Types (
|
): Allow variables to hold values of multiple types, enhancing flexibility. - Intersection Types (
&
): Combine multiple types into one, useful for creating composite types. - Type Guards: Ensure type safety in union types by checking and narrowing down the type within control flow.
- Generics: Create reusable components and functions that can operate across various types while maintaining type safety.
Conclusion:
Online Code run
Step-by-Step Guide: How to Implement TypeScript Working with Multiple Types
Union Types
Union types allow a variable or a function parameter to hold multiple different types.
Example 1: Basic Union Type
Step 1: Define a function that accepts either a string or a number.
// Define a function that can accept either a string or a number
function printId(id: string | number) {
console.log("Your ID is: " + id);
}
// Call the function with different types
printId("ABC"); // Output: Your ID is: ABC
printId(123); // Output: Your ID is: 123
Step 2: Narrow the types inside the function.
Sometimes, it's necessary to know which type is being handled. You can do this using type guards.
function printIdWithNarrowing(id: string | number) {
if (typeof id === "string") {
console.log("Your ID is a string: " + id.toUpperCase());
} else {
console.log("Your ID is a number: " + id.toFixed(2));
}
}
// Call the function with different types
printIdWithNarrowing("abc"); // Output: Your ID is a string: ABC
printIdWithNarrowing(456); // Output: Your ID is a number: 456.00
Example 2: Union Type in Arrays
Step 1: Create a mixed-type array.
// Define an array that can hold either strings or numbers
const data: (string | number)[] = ["apple", 2, "banana", 4];
// Loop through the array and print each elements
data.forEach(item => {
console.log(item);
});
// Output:
// apple
// 2
// banana
// 4
Intersection Types
Intersection types combine multiple types into one, creating a new type that has all the properties of each of the types involved.
Example 1: Basic Intersection Type
Step 1: Define two separate interfaces.
interface Employee {
employeeId: number;
name: string;
}
interface Manager {
managerId: number;
department: string;
}
Step 2: Combine those interfaces into one intersection type.
// Define an intersection type that merges both Employee and Manager properties
type ManagerEmployee = Employee & Manager;
const ceo: ManagerEmployee = {
employeeId: 1,
name: "Alice Smith",
managerId: 1,
department: "General Management"
};
console.log(ceo.name + " works in the " + ceo.department);
// Output: Alice Smith works in the General Management
Example 2: Intersection Types in Classes
Step 1: Define two separate classes.
Using intersection types doesn’t directly work on classes, however, you can use the concept in inheritance.
class Vehicle {
drive() {
console.log("Driving...");
}
}
class Boat {
sail() {
console.log("Sailing...");
}
}
class Amphicar extends Vehicle implements Boat {
sail() {
console.log("Amphicar sailing...");
}
swim() {
console.log("Amphicar swimming...");
}
}
const myVehicle: Amphicar = new Amphicar();
myVehicle.drive(); // Output: Driving...
myVehicle.sail(); // Output: Amphicar sailing...
myVehicle.swim(); // Output: Amphicar swimming...
Step 2: Mix behaviors at runtime:
Instead, intersection types can be used when dealing with objects and functions at runtime.
const vehicleInstance: Vehicle = new Vehicle();
const boatInstance: Boat = {
sail: () => {
console.log("Boat sailing...");
}
}
const combinedVehicle = Object.assign({}, vehicleInstance, boatInstance);
function operate(vehicle: Vehicle & Boat) {
vehicle.drive();
vehicle.sail();
}
operate(combinedVehicle);
// Output:
// Driving...
// Boat sailing...
Type Guards
Type guards are a pattern of working with union types that leverages type predicates to narrow the type of variables during runtime.
Example 1: Type Guard Function
Step 1: Define an interface and a function that checks if an object adheres to that interface.
interface Fish {
swim: () => void;
}
interface Bird {
fly: () => void;
}
// Type guard function
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
Step 2: Use this type guard to safely work with the union types.
const fish: Fish = {
swim: () => {
console.log("Fish swimming...");
}
}
const bird: Bird = {
fly: () => {
console.log("Bird flying...");
}
}
function interactWithPet(pet: Fish | Bird) {
if (isFish(pet)) {
// Inside here, TypeScript knows that 'pet' is of type 'Fish'
pet.swim();
} else {
// Inside here, TypeScript knows that 'pet' is of type 'Bird'
pet.fly();
}
}
interactWithPet(fish); // Output: Fish swimming...
interactWithPet(bird); // Output: Bird flying...
Example 2: in
Keyword as Type Guard
You can also use the in
keyword as a type guard to check if a particular property exists in an object.
function move(pet: Fish | Bird) {
if ("swim" in pet) {
// Inside here, TypeScript knows that 'pet' must be of type 'Fish'
pet.swim();
} else {
// Inside here, TypeScript knows that 'pet' must be of type 'Bird'
pet.fly();
}
}
move(fish); // Output: Fish swimming...
move(bird); // Output: Bird flying...
Example 3: Discriminated Unions
A powerful pattern in TypeScript is to have a common field among union types that helps identify which variant of the type is currently being used.
Step 1: Define the discriminated union.
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
Step 2: Use the switch
statement to narrow down the type.
Top 10 Interview Questions & Answers on TypeScript Working with Multiple Types
Top 10 Questions and Answers: TypeScript Working with Multiple Types
1. What are union types in TypeScript, and how do you use them?
let id: number | string;
id = 101;
id = "TS101";
In this example, the variable id
can be either a number
or a string
.
2. Can you explain intersection types in TypeScript?
Answer:
Intersection types allow you to combine multiple types into one. An intersection type contains all members of the types it intersects. You use the &
operator to define intersection types:
type Employee = {
name: string;
id: number;
};
type Engineer = {
skills: string[];
};
type FullTimeEmployee = Employee & Engineer;
const emp: FullTimeEmployee = {
name: "John",
id: 202,
skills: ["TypeScript", "React"]
};
Here, FullTimeEmployee
requires a combination of properties from both Employee
and Engineer
.
3. How do you create a type guard in TypeScript?
Answer:
Type guards help TypeScript narrow down the type within a conditional block. You can implement a user-defined type guard by defining a function that returns a type predicate (parameterName is Type
):
function isString(input: any): input is string {
return typeof input === "string";
}
let sample: string | number = "Hello";
if (isString(sample)) {
// TypeScript now knows that 'sample' is a string within this block
console.log(sample.toUpperCase());
}
4. Can you provide an example of using the typeof
type guard in TypeScript?
Answer:
Yes, the typeof
operator can be used as a type guard to narrow down the type of a variable:
function printValue(value: string | number) {
if (typeof value === "string") {
console.log(`String value: ${value}`);
} else if (typeof value === "number") {
console.log(`Number value: ${value}`);
}
}
printValue("Hello"); // Output: String value: Hello
printValue(42); // Output: Number value: 42
5. What are literal types in TypeScript, and how do they work with union types?
Answer: Literal types restrict a variable to a specific value. When combined with union types, literals allow for defining a finite set of acceptable values:
type Direction = "Up" | "Down" | "Left" | "Right";
let move: Direction;
move = "Up"; // Valid
move = "Right"; // Valid
// move = "Diagonal"; // Error: Type '"Diagonal"' is not assignable to type 'Direction'
6. How can you use the in
operator as a type guard in TypeScript?
Answer:
The in
operator checks if a property exists within an object, serving as a type guard:
type Fish = {
swim: () => void;
};
type Bird = {
fly: () => void;
};
function move(animal: Fish | Bird) {
if ("swim" in animal) {
return animal.swim();
}
return animal.fly();
}
7. What are the benefits of using type aliases in TypeScript?
Answer: Type aliases provide a way to name a type for easier reference and readability. Benefits include:
- Reusability: Create a reusable definition.
- Simplification: Simplify complex types.
- Readability: Make type definitions more understandable.
Example:
type UserID = number | string;
function printUserId(id: UserID) {
console.log(id);
}
printUserId(101); // Number type
printUserId("TS101"); // String type
8. Can you explain the differences between interface
and type
in TypeScript and when to use each?
Answer:
Both interface
and type
in TypeScript can define object shapes, but they have key differences:
- Extending: Interfaces can be extended using the
extends
keyword, while types are extended using intersections (&
). - Declaration Merging: Interfaces can be declared multiple times and merged, while types cannot.
Interface Example:
interface User {
name: string;
}
interface Admin extends User {
role: string;
}
Type Example:
type User = {
name: string;
};
type Admin = User & {
role: string;
};
Use interfaces
when you need declaration merging or intend to use extends
. Use types
for utility types or when creating simple object shapes.
9. How do you work with optional types in TypeScript?
Answer:
Optional types are used to denote that a property or parameter may or may not be present. In TypeScript, you achieve this by appending a question mark (?
) to the property name in an interface or object type:
interface Person {
firstName: string;
lastName?: string;
}
const person1: Person = { firstName: "John" }; // Valid
const person2: Person = { firstName: "John", lastName: "Doe" }; // Also valid
10. Can you explain how to use tuple types in TypeScript, and why they are useful?
Answer: Tuple types allow you to define an array with a fixed number of elements, where each element may have a different type. Tuples are useful for arrays of fixed sizes with known types for each index:
type UserLogin = [string, number]; // [username, userId]
const johnDoe: UserLogin = ["john.doe", 101];
function loginUser(loginDetails: UserLogin) {
console.log(`Logged in user: ${loginDetails[0]}, ID: ${loginDetails[1]}`);
}
loginUser(johnDoe);
Tuple example:
type Response = [number, string, boolean];
const fetchData = (): Response => [200, "Success", true];
Tuples are particularly handy in APIs that return fixed-size arrays of varying types, enhancing readability and type safety.
Login to post a comment.