Typescript простые задачи

typescript

Основы. Типизация примитивов и функций

Упражнение 1: Примитивы

function greet(name) {
    return "Hello, " + name;
}
let age = 30;
let isStudent = false;

Теория: TypeScript умеет выводить типы (inference), но лучше их указывать явно. Для примитивов используем: stringnumberboolean.

Задание: Добавьте явные аннотации типов к параметру функции и переменным.

Решение:

function greet(name: string): string {
    return "Hello, " + name;
}
let age: number = 30;
let isStudent: boolean = false;


Упражнение 2: Массивы

Код (JS):

const numbers = [1, 2, 3, 4];
const names = ["Alice", "Bob"];
const mixed = [1, "hello", true]; // Попробуйте типизировать это!

Теория: Существует два синтаксиса для массивов: Тип[] или Array<Тип>. Для смешанных массивов используются юнионы (union types) — (string | number)[].

Задание: Явно укажите типы для всех трех массивов.

const numbers: number[] = [1, 2, 3, 4];
const names: string[] = ["Alice", "Bob"];
const mixed: (number | string | boolean)[] = [1, "hello", true];


Упражнение 3: Функции с опциональными параметрами

Код (JS):

function sendMessage(user, message, isImportant) {
    if (isImportant) {
        console.log("ВАЖНО: " + message + " для " + user);
    } else {
        console.log(message + " для " + user);
    }
}
sendMessage("Анна", "Привет"); // isImportant не передан

Теория: Необязательные параметры помечаются знаком вопроса ?. Они всегда должны идти после обязательных. Тип такого параметра — это переданный тип или undefined.

Задание: Добавьте типы. Сделайте параметр isImportant необязательным.

function sendMessage(user: string, message: string, isImportant?: boolean): void {
    if (isImportant) {
        console.log("ВАЖНО: " + message + " для " + user);
    } else {
        console.log(message + " для " + user);
    }
}
sendMessage("Анна", "Привет"); // isImportant не передан


Упражнение 4: Объекты (инлайн)

Код (JS):

function printPoint(point) {
    console.log(`x: ${point.x}, y: ${point.y}`);
}
printPoint({ x: 10, y: 20 });

Теория: Объекты типизируются "инлайн" или через interface/type. Инлайн: { x: number, y: number }.

Задание: Опишите тип параметра point инлайн.

Решение:

function printPoint(point: { x: number, y: number }): void {
    console.log(`x: ${point.x}, y: ${point.y}`);
}
printPoint({ x: 10, y: 20 });


Упражнение 5: Type Alias для объектов

Код (JS):

const alice = { name: "Alice", age: 25 };
const bob = { name: "Bob", age: 30, email: "bob@mail.com" };

function getAdultAge(person) {
    return person.age;
}

Теория: Когда структура объекта повторяется, удобно создать для нее имя (alias) через type. Поля могут быть опциональными (?).

Задание: Создайте тип Person, где email необязателен. Примените его.

type person = {
    name: string,
    age: number,
    email?: string
}
const alice: person = { name: "Alice", age: 25 };
const bob: person = { name: "Bob", age: 30, email: "bob@mail.com" };

function getAdultAge(person: person): number {
    return person.age;
}


Блок 2: Интерфейсы, расширение и объединение (Уровень 6-10)

Упражнение 6: Interface

Код (JS):

const car = { brand: "Toyota", model: "Camry", year: 2020 };
function describeVehicle(vehicle) {
    return `${vehicle.brand} ${vehicle.model} (${vehicle.year})`;
}

Теория: interface — еще один способ описания формы объекта. В отличие от type, интерфейсы могут быть переобъявлены (declaration merging) и чаще используются для объектов и классов.

Задание: Опишите car с помощью interface Vehicle.

Решение:

interface vehicle {
    brand: string;
    model: string;
    year: number;
}
const car: vehicle = { brand: "Toyota", model: "Camry", year: 2020 };
function describeVehicle(vehicle: vehicle): string {
    return `${vehicle.brand} ${vehicle.model} (${vehicle.year})`;
}


Упражнение 7: Наследование интерфейсов (extends)

Код (JS):

const user = { id: 1, name: "John" };
const admin = { id: 2, name: "Jane", permissions: ["read", "write"] };

Теория: Интерфейсы могут расширять другие интерфейсы, наследуя их поля.

Задание: Создайте базовый интерфейс User и расширьте его до Admin.

interface Iuser {
    id: number,
    name: string
}
interface Iadmin extends Iuser {
    permissions: string[]
}

const user: Iuser = { id: 1, name: "John" };
const admin: Iadmin = { id: 2, name: "Jane", permissions: ["read", "write"] };


Упражнение 8: Intersection Types (&)

Код (JS):

const basicInfo = { name: "Alex" };
const contactInfo = { email: "alex@mail.com", phone: "12345" };
const fullProfile = { ...basicInfo, ...contactInfo };

Теория: В отличие от extends (для интерфейсов), type позволяет объединять типы через оператор & (intersection).

Задание: Создайте типы Basic и Contact, затем получите тип FullProfile как их пересечение.

type Basic = {
    name: string
}
type Contact = {
    email: string,
    phone: string
}

type FullProfile = Basic & Contact;


const basicInfo: Basic = { name: "Alex" };
const contactInfo: Contact = { email: "alex@mail.com", phone: "12345" };
const fullProfile: FullProfile = { ...basicInfo, ...contactInfo };


Упражнение 9: Типизация функций как значений

Код (JS):

const sayHello = function() {
    console.log("Hello!");
};

const mathOperation = (a, b) => a + b;

Теория: Функции тоже имеют тип. Можно описать сигнатуру: (параметр: тип) => тип_результата

Задание: Опишите типы для колбэков.

Решение:

const sayHello = function (): void {
    console.log("Hello!");
};

const mathOperation = (a: number, b: number): number => a + b;


Упражнение 10: Readonly и литеральные типы

Код (JS):

const CONFIG = {
    apiUrl: "https://api.site.com",
    retries: 3
};
// CONFIG.apiUrl = "new url"; // Этого не должно происходить!

let direction = "up"; // Должны быть только "up", "down", "left", "right"

Теория: readonly запрещает изменение поля. Литеральные типы (literal types) позволяют задать конкретное значение как тип.

Задание: Сделайте поля CONFIG доступными только для чтения. Ограничьте direction строковыми литералами.

Решение:

type config = {
    readonly apiUrl: string;
    readonly retries: number
}

const CONFIG: config = {
    apiUrl: "https://api.site.com",
    retries: 3
};
// CONFIG.apiUrl = "new url"; // Этого не должно происходить!

let direction: "up" | "down" | "left" | "right" = "up"; // Должны быть только "up", "down", "left", "right"


Блок 3: Продвинутая типизация (Уровень 11-15)

Упражнение 11: Union Types (Защитники типов)

Код (JS):

function getFirstElement(arr) {
    if (typeof arr === "string") {
        return arr[0]; // Первая буква
    } else {
        return arr[0]; // Первый элемент массива
    }
}

Теория: Если функция принимает union тип (string | number[]), TS не позволит просто обратиться к arr[0], т.к. у string и number[] разные индексы. Нужно сузить тип с помощью typeof или других проверок (type guards).

Задание: Опишите функцию, принимающую string | number[], и реализуйте логику.

Решение:

function getFirstElement(arr: string | number[]): string | number {
    if (typeof arr === "string") {
        return arr[0] ?? ''; // Первая буква
    } else {
        return arr[0] ?? 0; // Первый элемент массива
    }
}


Упражнение 12: Type Guard "in"

Код (JS):

type Dog = { bark: () => void };
type Cat = { meow: () => void };

function makeSound(animal) {
    // Как понять, кто есть кто?
}

Теория: Для объектов, у которых нет общего поля, используем оператор in для проверки существования свойства.

Решение:

type Dog = { bark: () => void };
type Cat = { meow: () => void };

function makeSound(animal: Dog | Cat): void {
    // Как понять, кто есть кто?
    if ('bark' in animal) {
        animal.bark()
    }
    if ('meow' in animal) {
        animal.meow()
    }
}


Упражнение 13: Never тип (Исчерпывающие проверки) ?

Код (JS):

type Shape = "circle" | "square" | "triangle";

function getArea(shape) {
    switch (shape) {
        case "circle": return Math.PI;
        case "square": return 1;
        // Забыли про triangle!
    }
}

Теория: Тип never означает, что значение никогда не возникнет. Его используют для проверки, что все возможные варианты union обработаны.

Задание: Допишите функцию так, чтобы если мы забудем обработать "triangle", TS выдал ошибку.

type Shape = "circle" | "square" | "triangle";

function getArea(shape: Shape): number {
    switch (shape) {
        case "circle": return Math.PI;
        case "square": return 1;
        case "triangle": return 0.5;
        default:
            // Если мы не обработали все случаи, shape будет не never, и будет ошибка
            const _exhaustiveCheck: never = shape;
            return _exhaustiveCheck;
    }
}


Упражнение 14: Типизация "this" в функциях

Код (JS):

const user = {
    name: "Alice",
    greet: function() {
        console.log(`Hello, I'm ${this.name}`);
    }
};
const greetFn = user.greet;
greetFn(); // Ошибка! this потерян

Теория: В TS можно указать тип для this как первый "псевдо-параметр" функции.

Задание: Исправьте типы так, чтобы код компилировался, но мы знали, что this должен быть объектом с полем name.