- Главная
- Typescript
- Практическая типизация в TypeScript. Примеры
Практическая типизация в TypeScript. Примеры
Теория — это хорошо, но настоящие навыки приходят только с практикой. Сегодня мы полностью посвятим урок практическому упражнению. Я подготовил для вас реальный JavaScript-код со множеством скрытых проблем. Ваша задача — превратить его в TypeScript, добавив все необходимые аннотации типов, исправив ошибки и сделав код безопасным.
Это упражнение объединит всё, что мы изучили за предыдущие уроки:
- Примитивные и литеральные типы
- Массивы и объекты
- Тип-объединение (Union Types)
- Защитники типа (Type Guards)
- Псевдонимы типа (Type Aliases)
- Работу с опциональными свойствами
- Вывод типов (type inference)
Подготовка
Создайте новый файл exercise.ts и скопируйте в него следующий код. Мы будем работать с ним на протяжении всего урока.
// exercise.ts — Исходный JavaScript код
// Здесь много скрытых проблем. Ваша задача — добавить типы!
// 1. Простые переменные
const appName = "Task Manager";
const version = 1.0;
const isProduction = false;
const config = null;
// 2. Функция для создания задач
function createTask(title, description, priority, dueDate) {
return {
id: Date.now(),
title: title,
description: description,
priority: priority,
dueDate: dueDate,
completed: false,
tags: []
};
}
// 3. Функция для обновления задачи
function updateTask(task, updates) {
return { ...task, ...updates };
}
// 4. Функция для фильтрации задач
function filterTasks(tasks, filterType) {
if (filterType === "completed") {
return tasks.filter(task => task.completed === true);
} else if (filterType === "pending") {
return tasks.filter(task => task.completed === false);
} else if (filterType === "high") {
return tasks.filter(task => task.priority === "high");
} else {
return tasks;
}
}
// 5. Функция для форматирования задачи
function formatTask(task) {
const status = task.completed ? "✅" : "⏳";
const priorityEmoji = task.priority === "high" ? "🔴" :
task.priority === "medium" ? "🟡" : "🟢";
return `${status} ${priorityEmoji} ${task.title} (${task.dueDate})`;
}
// 6. Функция для подсчёта статистики
function getTaskStats(tasks) {
const total = tasks.length;
const completed = tasks.filter(t => t.completed).length;
const pending = total - completed;
const highPriority = tasks.filter(t => t.priority === "high").length;
return {
total,
completed,
pending,
highPriority
};
}
// 7. Создаём несколько задач
const task1 = createTask(
"Изучить TypeScript",
"Пройти уроки по TypeScript и выполнить упражнения",
"high",
"2024-01-30"
);
const task2 = createTask(
"Купить продукты",
"Молоко, хлеб, яйца",
"low",
null
);
const task3 = createTask(
"Написать отчёт",
"Подготовить еженедельный отчёт для команды",
"medium",
"2024-01-25"
);
// 8. Выполняем операции
const tasks = [task1, task2, task3];
console.log("Все задачи:");
tasks.forEach(task => console.log(formatTask(task)));
console.log("\nСтатистика:");
console.log(getTaskStats(tasks));
console.log("\nТолько высокий приоритет:");
const highTasks = filterTasks(tasks, "high");
highTasks.forEach(task => console.log(formatTask(task)));
// 9. Обновляем задачу
const updatedTask = updateTask(task1, { completed: true, priority: "medium" });
console.log("\nОбновлённая задача:");
console.log(formatTask(updatedTask));
Ваше задание
Теперь ваша очередь. Выполните следующие шаги:
- Добавьте типы для всех переменных — используйте явные аннотации или позвольте TypeScript вывести их.
- Создайте Type Aliases для повторяющихся структур (
Task,TaskPriority,FilterType). - Добавьте аннотации для всех параметров функций — укажите, какие типы принимают функции.
- Добавьте аннотации для возвращаемых значений — укажите, что возвращает каждая функция.
- Используйте Union Types для параметров, которые могут быть разных типов.
- Используйте защитник типа для проверки на
nullиundefined. - Исправьте ошибки — TypeScript укажет на проблемные места.
Попробуйте сделать это самостоятельно. Не читайте решение, пока не попытаетесь решить задачу сами!
Пошаговый разбор решения
Давайте теперь пройдём по всем проблемам и исправим их.
Шаг 1: Типы для переменных
Начнём с простых переменных. Здесь TypeScript может вывести типы автоматически:
// TypeScript выведет типы самостоятельно
const appName = "Task Manager"; // string
const version = 1.0; // number
const isProduction = false; // boolean
const config = null; // null
Но если мы хотим быть явными, можем добавить аннотации:
const appName: string = "Task Manager";
const version: number = 1.0;
const isProduction: boolean = false;
const config: null = null;
Шаг 2: Type Aliases для Task
Структура задачи используется во многих местах, поэтому создадим псевдонимы типов:
// Приоритеты задач
type TaskPriority = "low" | "medium" | "high";
// Тип задачи
type Task = {
id: number;
title: string;
description: string;
priority: TaskPriority;
dueDate: string | null; // может быть null
completed: boolean;
tags: string[];
};
// Тип для обновлений задачи (все поля опциональны)
type TaskUpdate = Partial<Task>;
// Тип для фильтрации
type FilterType = "completed" | "pending" | "high" | "all";
Шаг 3: Функция createTask
Добавляем типы для параметров и возвращаемого значения. Обратите внимание на dueDate — он может быть null:
function createTask(
title: string,
description: string,
priority: TaskPriority,
dueDate: string | null
): Task {
return {
id: Date.now(),
title: title,
description: description,
priority: priority,
dueDate: dueDate,
completed: false,
tags: []
};
}
Шаг 4: Функция updateTask
Используем Partial<Task> для параметра updates:
function updateTask(task: Task, updates: Partial<Task>): Task {
return { ...task, ...updates };
}
Шаг 5: Функция filterTasks
Здесь важно использовать Type Guard для проверки типа filterType:
function filterTasks(tasks: Task[], filterType: FilterType): Task[] {
// Используем switch для более чистого кода
switch (filterType) {
case "completed":
return tasks.filter(task => task.completed === true);
case "pending":
return tasks.filter(task => task.completed === false);
case "high":
return tasks.filter(task => task.priority === "high");
case "all":
default:
return tasks;
}
}
Шаг 6: Функция formatTask
Здесь нужно добавить проверку на null для dueDate:
function formatTask(task: Task): string {
const status = task.completed ? "✅" : "⏳";
const priorityEmoji = task.priority === "high" ? "🔴" :
task.priority === "medium" ? "🟡" : "🟢";
// Обрабатываем случай, когда dueDate === null
const dueDateStr = task.dueDate ?? "No due date";
return `${status} ${priorityEmoji} ${task.title} (${dueDateStr})`;
}
Шаг 7: Функция getTaskStats
Типизируем возвращаемое значение:
type TaskStats = {
total: number;
completed: number;
pending: number;
highPriority: number;
};
function getTaskStats(tasks: Task[]): TaskStats {
const total = tasks.length;
const completed = tasks.filter(t => t.completed).length;
const pending = total - completed;
const highPriority = tasks.filter(t => t.priority === "high").length;
return {
total,
completed,
pending,
highPriority
};
}
Шаг 8: Улучшаем типизацию с помощью Type Inference
Заметим, что TypeScript может вывести тип tasks автоматически, но лучше указать его явно:
// TypeScript выведет Task[]
const tasks = [task1, task2, task3];
// Или явно указываем
const tasks: Task[] = [task1, task2, task3];
Шаг 9: Финальный код
Вот как выглядит итоговый код после всех исправлений:
// ============================================
// 1. Type Aliases
// ============================================
type TaskPriority = "low" | "medium" | "high";
type Task = {
id: number;
title: string;
description: string;
priority: TaskPriority;
dueDate: string | null;
completed: boolean;
tags: string[];
};
type TaskUpdate = Partial<Task>;
type FilterType = "completed" | "pending" | "high" | "all";
type TaskStats = {
total: number;
completed: number;
pending: number;
highPriority: number;
};
// ============================================
// 2. Переменные
// ============================================
const appName: string = "Task Manager";
const version: number = 1.0;
const isProduction: boolean = false;
const config: null = null;
// ============================================
// 3. Функции
// ============================================
function createTask(
title: string,
description: string,
priority: TaskPriority,
dueDate: string | null
): Task {
return {
id: Date.now(),
title: title,
description: description,
priority: priority,
dueDate: dueDate,
completed: false,
tags: []
};
}
function updateTask(task: Task, updates: TaskUpdate): Task {
return { ...task, ...updates };
}
function filterTasks(tasks: Task[], filterType: FilterType): Task[] {
switch (filterType) {
case "completed":
return tasks.filter(task => task.completed === true);
case "pending":
return tasks.filter(task => task.completed === false);
case "high":
return tasks.filter(task => task.priority === "high");
case "all":
default:
return tasks;
}
}
function formatTask(task: Task): string {
const status = task.completed ? "✅" : "⏳";
const priorityEmoji = task.priority === "high" ? "🔴" :
task.priority === "medium" ? "🟡" : "🟢";
const dueDateStr = task.dueDate ?? "No due date";
return `${status} ${priorityEmoji} ${task.title} (${dueDateStr})`;
}
function getTaskStats(tasks: Task[]): TaskStats {
const total = tasks.length;
const completed = tasks.filter(t => t.completed).length;
const pending = total - completed;
const highPriority = tasks.filter(t => t.priority === "high").length;
return {
total,
completed,
pending,
highPriority
};
}
// ============================================
// 4. Использование
// ============================================
const task1: Task = createTask(
"Изучить TypeScript",
"Пройти уроки по TypeScript и выполнить упражнения",
"high",
"2024-01-30"
);
const task2: Task = createTask(
"Купить продукты",
"Молоко, хлеб, яйца",
"low",
null
);
const task3: Task = createTask(
"Написать отчёт",
"Подготовить еженедельный отчёт для команды",
"medium",
"2024-01-25"
);
const tasks: Task[] = [task1, task2, task3];
console.log("Все задачи:");
tasks.forEach(task => console.log(formatTask(task)));
console.log("\nСтатистика:");
console.log(getTaskStats(tasks));
console.log("\nТолько высокий приоритет:");
const highTasks = filterTasks(tasks, "high");
highTasks.forEach(task => console.log(formatTask(task)));
const updatedTask = updateTask(task1, { completed: true, priority: "medium" });
console.log("\nОбновлённая задача:");
console.log(formatTask(updatedTask));
Что мы улучшили?
- Безопасность — теперь ни одна функция не может быть вызвана с неправильным типом.
- Читаемость — псевдонимы типов делают код самодокументируемым.
- Поддержка
null— мы явно обработали случай, когдаdueDateравенnull. - Переиспользуемость — типы можно экспортировать и использовать в других файлах.
- Рефакторинг — использование
switchвместоif/elseсделало код чище.
Оживи свой сайт. Освой JavaScript!
Статичная верстка — это только скелет. Наш онлайн-курс "JavaScript с нуля до профи" даст твоим страницам мышцы и нервы. Научись создавать слайдеры, формы, интерактивные карты и получать данные с сервера.
От теории — к реальным скриптам в твоём портфолио.
Запуск и проверка
Чтобы проверить ваше решение, запустите компилятор:
tsc exercise.ts --noEmit --strict
Если ошибок нет — поздравляю, вы успешно прошли упражнение!
Альтернативно, вы можете запустить код с помощью TSX:
tsx exercise.ts
Бонус: дополнительные улучшения
Если вы хотите пойти дальше, вот несколько идей для улучшения:
- Добавьте валидацию — используйте защитник типа для проверки, что
titleне пустая строка. - Создайте фабрику задач — напишите функцию, которая генерирует задачи с случайными данными.
- Добавьте метод для добавления тегов — расширьте функциональность работы с тегами.
- Используйте utility types — попробуйте использовать
Pick,Omit,Readonlyдля разных сценариев.
Итог
Сегодня мы на практике применили все знания, полученные за предыдущие уроки:
- Примитивные и литеральные типы для переменных и параметров.
- Псевдонимы для переиспользуемых структур данных.
- Объединение для параметров, которые могут быть разных типов.
- Защитники для безопасной работы с
nullиundefined. - Аннотации типов для всех функций и их параметров.
- Утилитарные типы (
Partial) для более гибкой типизации.
Это упражнение — хороший пример того, как выглядит реальная миграция кода на TypeScript. В следующем уроке мы перейдём к более техническим аспектам — научимся компилировать TypeScript-файлы и разберёмся с инструментами TSC и TSX.