Learn TypeScript With Me in 14 Days Day 4

Posted on 4 October 2024 Reading time: 7 min read
Learn TypeScript With Me in 14 Days Day 4

Day 4: Functions in TypeScript

Welcome to Day 4 of the series, Learn TypeScript With Me in 14 Days. Today, we’ll explore how functions work in TypeScript. We’will dive into how to define function parameters, return types, and make use of TypeScript’s features like optional parameters and default values. By the end of this part, we will be able to confidently use functions in our Task Manager project and handle tasks more effectively.

Why Are Functions Important in TypeScript?

Functions are essential building blocks in any application. They encapsulate logic and allow you to reuse code efficiently. TypeScript enhances functions by adding type safety, helping to ensure that functions receive and return the correct data types. This reduces the chance of runtime errors and makes your code easier to maintain and understand.

Basic Function Definition

In TypeScript, you can define a function by specifying the types of the parameters and the return type. Let’s start with a simple example:

function add(a: number, b: number): number {
  return a + b;
}

console.log(add(5, 10)); // Output: 15

Explanation:

  • a and b are parameters of type number.
  • The return type of the function is also number.
  • TypeScript will throw an error if you try to pass anything other than numbers or if the return value is not a number.

Let’s try and call the add function and see what happens if we try to pass invalid arguments;

add('5', 10); // Error: Argument of type 'string' is not assignable to parameter of type 'number'

Optional Parameters and Default Values

Sometimes, you may not always need to provide all parameters to a function. TypeScript allows you to mark parameters as optional by adding a ? after the parameter name.

Let’s see an example:

function greet(name: string, greeting?: string): string {
  return `${greeting || 'Hello'}, ${name}!`;
}

console.log(greet('John')); // Output: Hello, John!
console.log(greet('Jane', 'Hi')); // Output: Hi, Jane!

Explanation:

  • The greeting parameter is optional. If not provided, the function defaults to ‘Hello’.

You can also define default values for function parameters:

function greetWithDefault(name: string, greeting: string = 'Hello'): string {
  return `${greeting}, ${name}!`;
}

console.log(greetWithDefault('John')); // Output: Hello, John!
console.log(greetWithDefault('Jane', 'Hi')); // Output: Hi, Jane!

Functions with Void and Return Types

In TypeScript, if a function doesn’t return anything, its return type is void. This is useful for functions that perform actions without returning a value.

Example:

function logMessage(message: string): void {
  console.log(message);
}

logMessage('This is a log message');

In this case, the function simply logs a message and does not return anything, so we explicitly declare the return type as void.

Function Types

You can use TypeScript to define types for functions themselves. This helps in scenarios where you want to ensure that a variable holds a function with a specific signature.

Example of a Function Type:

Let’s define a function that takes a TaskId and returns a Task or null. We can start by defining a type alias for this function:

type FindTask = (id: TaskId) => Task | null;

const findTask: FindTask = (id) => {
  return tasks.find(task => task.id === id) || null;
};

Explanation:

  • FindTask is a type alias that describes a function that accepts a TaskId and returns either a Task or null.
  • This makes it clear that findTask will follow this type definition, ensuring type safety in the code.

Applying Functions to the Task Manager Project

Let’s now extend our Task Manager project by adding new functionality using TypeScript’s typed functions.

1. Add a taskCount Function

The taskCount function does not receive any argument but returns the number of tasks. The return type is a number.

function taskCount(): number {
    console.log(`Number of tasks: ${tasks.length}`);
    return tasks.length;
}

2. Refactoring toggleTaskCompletion Function

Next, we’ll improve the toggleTaskCompletion function to ensure it has correct types and returns void.

function toggleTaskCompletion(id: TaskId): void {
  const task = tasks.find(task => task.id === id);
  if (task) {
    task.completed = !task.completed;
    console.log(`Task "${task.title}" marked as ${task.completed ? 'completed' : 'incomplete'}.`);
  } else {
    console.log(`Task with id: ${id} not found.`);
  }
}

toggleTaskCompletion(1);
console.log(tasks);

Here, TypeScript ensures:

  • The id passed is a valid TaskId (either number or string).
  • The function properly toggles the completed property and returns void.

3. Refactoring a removeTask Function

Let’s update our removeTask function. This function will allow us to remove a task based on its id which has a defined type of TaskId

function removeTask(id: TaskId): void {
  const taskIndex = tasks.findIndex(task => task.id === id);
  if (taskIndex !== -1) {
    tasks.splice(taskIndex, 1);
    console.log(`Task with id: ${id} has been removed.`);
  } else {
    console.log(`Task with id: ${id} not found.`);
  }
}

removeTask(3);
console.log(tasks);

Explanation:

  • We use findIndex() to locate the task by id.
  • If found, we remove the task using splice().

Functions Returning Complex Types

You can also define functions that return more complex types, such as objects or arrays. Let’s create a function that returns all completed tasks from the task array:

function getCompletedTasks(): Task[] {
  return tasks.filter(task => task.completed);
}

const completedTasks = getCompletedTasks();
console.log('Completed tasks:', completedTasks); // Returns all tasks where 'completed' is true

Explanation:

  • The getCompletedTasks function returns an array of Task objects that have been marked as completed.
  • The return type Task[] ensures TypeScript knows the function will return an array of Task objects.

Putting it all together:

// types.ts
export interface Task {
    id: number;
    title: string;
    completed: boolean;
  }

export type TaskId = number | string;
// task.ts
import { TaskId, Task } from './types';

let tasks: Task[] = [
  { id: 1, title: 'Learn TypeScript', completed: false },
  { id: 2, title: 'Build a Task Manager app', completed: false },
  { id: 3, title: 'Test Task Manager app', completed: false }
];

function addTask(title: string): void {
    const newTask: Task = {
      id: tasks.length + 1,
      title: title,
      completed: false
    };
    tasks.push(newTask);
    console.log(`Task "${title}" has been added.`);
  }

function toggleTaskCompletion(id: TaskId): void {
    const task = tasks.find(task => task.id === id);
    if (task) {
        task.completed = !task.completed;
        console.log(`Task "${task.title}" marked as ${task.completed ? 
        'completed' : 'incomplete'}.`);
    } else {
        console.log(`Task with id: ${id} not found.`);
    }
  }

function removeTask(id: TaskId): void {
    const taskIndex = tasks.findIndex(task => task.id === id);
    if (taskIndex !== -1) {
        tasks.splice(taskIndex, 1);
        console.log(`Task with id: ${id} has been removed.`);
    } else {
        console.log(`Task with id: ${id} not found.`);
    }
}

function getCompletedTasks(): Task[] {
    return tasks.filter(task => task.completed);
}
  
addTask('Review TypeScript functions');
console.log(tasks);

toggleTaskCompletion(1);
console.log(tasks);

removeTask(3);
console.log(tasks);

const completedTasks = getCompletedTasks();
console.log('Completed tasks:', completedTasks);

If all goes well, you should see the following output:

What We Covered

Today we have learnt how functions work in TypeScript. We’ve covered:

  • Basic function definitions.
  • Optional parameters and default values.
  • Function types and how to define them.
  • Real-world use of functions in the Task Manager project.

What’s Next?

In the next part of the series, we will dive into TypeScript Interfaces and Type Aliases to make our code more structured and reusable. We’ll explore how these tools can help us model data and make the code even more maintainable.

I hope today’s session gave you a good foundation for working with functions in TypeScript! Tomorrow, we’ll dive into Day 5: Interfaces and Type Aliases.

Useful Resources:

TypeScript Functions Documentation

TypeScript Playground (Test code snippets and see how TypeScript checks for errors)

That’s all for today! See you tomorrow for Day 5: Interfaces and Type Aliases. If you have any questions or run into any issues, you can contact me at steve [at] stephenpopoola.uk