Основы. Типизация примитивов и функций
Упражнение 1: Примитивы
function greet(name) {
return "Hello, " + name;
}
let age = 30;
let isStudent = false;
Теория: TypeScript умеет выводить типы (inference), но лучше их указывать явно. Для примитивов используем: string, number, boolean.
Задание: Добавьте явные аннотации типов к параметру функции и переменным.
Решение:
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.