Это первый урок по TypeScript из нашего большого курса, рассчитанного на 40 подробных занятий. Сегодня мы не будем писать сложный код. Наша главная задача, понять, зачем вообще нужен TypeScript, если у нас уже есть JavaScript, который отлично работает в браузере и на сервере.
Я сам прошел путь от скептического «зачем усложнять?» до абсолютного понимания, что TypeScript это инструмент для современной веб-разработки. И я хочу поделиться с вами этим, чтобы вы сразу начали учиться с правильным настроем и пониманием конечной цели. Давайте разбираться!
Проблема масштабируемости JavaScript
JavaScript это феноменальный язык. Он гибкий, динамичный и прощает начинающим многие ошибки. Вы можете объявить переменную, присвоить ей число, а через три строки еще строку и интерпретатор лишь пожмет плечами и скажет: «Окей, boss, как скажешь». Это называется динамическая типизация и на этапе prototyping’а, когда нужно быстро набросать идею, это суперспособность.
Но давайте представим, что наша маленькая идея превратилась в большой, серьезный проект. Над ним работает не один человек, а целая команда из 5, 10 или 50 разработчиков. Проект состоит из сотен модулей и тысяч строк кода.
Вот типичный сценарий из жизни JavaScript-разработчика в большом проекте. Вы пишете функцию, которая, по вашей задумке, должна принимать на вход два числа и возвращать их сумму.
// mathUtils.js function add(a, b) { return a + b; }
Вы экспортируете эту функцию. Ваш коллега в другом файле, работая над своим модулем, импортирует ее. Он видит название add и, не заглядывая в вашу документацию (если она, конечно, есть), решает, что эта функция идеально подойдет для конкатенации (сложения) строк имени и фамилии пользователя.
// userProfile.js import { add } from './mathUtils.js'; const fullName = add(firstName, lastName); // Передаем две строки
И знаете что? В JavaScript это сработает! Оператор + работает и с числами и со строками. Ваш коллега получит строку "ИмяФамилия", будет доволен и пойдет дальше. Ошибки не произошло. Пока что.
Теперь представьте, что где-то в коде эта функция add все же вызывается с числами и возвращает их сумму. Все работает. Проходит месяц. Вы решаете, что функция add должна делать проверку на целые числа и кидать ошибку, если пришло что-то другое. Вы меняете ее код:
// mathUtils.js (новая версия) function add(a, b) { if (!Number.isInteger(a) || !Number.isInteger(b)) { throw new Error('Arguments must be integers!'); } return a + b; }
Вы запускаете все тесты, которые у вас есть для математического модуля и они проходят. Вы публикуете новую версию модуля. И внезапно… приложение вашего коллеги, которое собирало полное имя пользователя, падает с криком Error: Arguments must be integers! в самом неожиданном месте.
В чем проблема? Проблема в том, что контракт функции, ее предназначение, был описан только в ее названии и (если повезет) в комментариях. На этапе выполнения кода (runtime) JavaScript обнаружил несоответствие. Но это уже слишком поздно! Приложение уже работает у пользователей и оно сломалось.
Масштабируемость это не про количество строк кода, а про возможность развивать большую кодовую базу силами большой команды, не ломая при этом уже работающую функциональность на каждом шагу. JavaScript, со всей своей свободой, плохо справляется с этой задачей. Он откладывает обнаружение многих классов ошибок до момента запуска программы, что делает процесс разработки непредсказуемым и нервирующим на больших проектах. TypeScript был создан, чтобы решить именно эту проблему.
Что такое статическая типизация?
Давайте проведем аналогию. Строительство дома по JavaScript это когда у вас есть куча кирпичей (переменные), балок (функции) и окон (объекты). Вы можете прикрутить окно к фундаменту, а балку воткнуть в крышу. Строителям (разработчикам) это может показаться удобным, эта полная свобода! Но когда дом достигает этажа 50-го, он может неожиданно сложиться, потому что кто-то в 25-м этаже использовал оконную раму вместо несущей балки и это прошло незамеченным.
TypeScript это архитектор и прораб, который стоит у вас за спиной с чертежами (типами) и постоянно проверяет вашу работу до того, как вы что-то куда-то встроите. Он скажет: «Эй, дружище, это же оконная рама, а не балка! Ты не можешь использовать ее здесь!» прямо в момент написания кода (на этапе разработки), а не тогда, когда жильцы уже заехали (runtime).
Это и есть статическая типизация. «Статическая», потому что проверка типов происходит статически, т.е. до запуска кода, в момент его написания и компиляции.
Давайте вернемся к нашему примеру с функцией add. В TypeScript мы бы описали ее так:
// mathUtils.ts function add(a: number, b: number): number { return a + b; }
Мы явно указали: параметр a должен быть типа number, параметр b тоже number и функция возвращает значение типа number.
Теперь, когда наш коллега попытается использовать эту функцию для сложения строк, TypeScript немедленно отреагирует. Его среда разработки (IDE) подчеркнет код красной волнистой линией, а компилятор выдаст четкую ошибку:
// userProfile.ts import { add } from './mathUtils'; const firstName: string = "Имя"; const lastName: string = "Фамилия"; const fullName = add(firstName, lastName); // <- ЗДЕСЬ ОШИБКА!
Ошибка компиляции (еще до запуска кода!):
Argument of type 'string' is not assignable to parameter of type 'number'.
Архитектор (TypeScript) посмотрел на чертеж для функции add, увидел, что для параметра a разрешены только кирпичи (числа), а коллега пытается подсунуть оконную раму (строку) и сразу же закричал: «Стоп! Так нельзя!».
Теперь коллега не сможет допустить эту ошибку. Он либо найдет правильную функцию для конкатенации строк, либо, если он уверен, что firstName и lastName это числа, преобразует их явно, осознанно взяв на себя ответственность. Ошибка, которая в JavaScript-мире проявилась бы через месяц в продакшене, была устранена на самом раннем этапе в момент написания кода.
Статическая типизация это система правил и контрактов, которые вы определяете для своего кода. Она не ограничивает вашу свободу, а направляет ее в конструктивное русло, делая код предсказуемым, надежным и понятным для всей команды.
Преимущества TypeScript: раннее обнаружение ошибок
Самое очевидное и весомое преимущество мы уже затронули, но давайте разберем его более системно. Раннее обнаружение ошибок это killer-фича TypeScript.
Подавляющее большинство глупых, обидных и сложно отлавливаемых ошибок связаны с типами:
-
undefined is not a function(попытка вызвать не функцию) -
Cannot read property 'X' of undefined/null(попытка обратиться к свойству несуществующего объекта) -
Неожиданные результаты арифметических операций (сложение строк вместо чисел)
В JavaScript эти ошибки всплывают только когда выполнение программы дойдет до роковой строки. Чтобы поймать их, нужно обеспечить 100% coverage тестами, что сложно и дорого или постоянно вручную проверять работу приложения в браузере после каждого изменения.
TypeScript переносит обнаружение этих ошибок на этап компиляции (или даже прямо в редакторе кода). Компилятор TS это ваш самый придирчивый и быстрый код-ревьюер, который проверяет тысячи потенциальных проблем за миллисекунды.
Практический пример:
Допустим, мы описываем объект пользователя.
// JavaScript const user = { name: "Max", age: 30 }; console.log(user.nmae); // Опечатка! Вместо 'name' написали 'nmae'
В JavaScript этот код выполнится без ошибок, а в консоль выведется undefined. Программа продолжит работать с undefined вместо имени и ошибка проявится где-то совсем в другом месте, гораздо позже. Отлаживать такое, это сущий кошмар.
Теперь то же самое в TypeScript. Мы можем явно описать, как должен выглядеть объект User, с помощью interface.
// TypeScript interface User { name: string; age: number; } const user: User = { name: "Max", age: 30 }; console.log(user.nmae); // <- ОШИБКА КОМПИЛЯЦИИ!
TypeScript немедленно сообщит: Property 'nmae' does not exist on type 'User'. Did you mean 'name'?. Он не только нашел опечатку, но и предложил правильный вариант! Эта одна подсказка может сэкономить вам 30 минут отладки. Умножьте это на сотни таких мелочей в проекте и вы получите колоссальную экономию времени и нервов.
Преимущества TypeScript: Самодокументирование кода
Хороший код должен быть понятным без комментариев. TypeScript делает эту максиму реальностью благодаря системе типов. Ваш код становится самодокументирующимся.
Взгляните на эту функцию на чистом JavaScript:
function processUserData(user, config, options) { // ...какая-то сложная логика }
Чтобы понять, как ей пользоваться, вам обязательно нужно:
-
Найти место, где эта функция определяется и прочитать комментарии (если они есть).
-
Или найти места, где ее уже вызывают и понять, что туда передают.
-
Или запустить код, передать что-то не то, поймать ошибку и методом тыка понять сигнатуру.
Это медленно и неэффективно.
Теперь посмотрим на то же самое в TypeScript:
interface User { id: number; email: string; isActive: boolean; } interface Config { sendEmail: boolean; loggingLevel: 'debug' | 'info' | 'warn'; } interface ProcessOptions { retryCount: number; timeoutMs: number; } function processUserData(user: User, config: Config, options: ProcessOptions): void { // ...логика }
Даже не видя тела функции и не читая ни единого комментария, вы понимаете:
-
Первый аргумент — это объект пользователя с конкретными полями.
-
Второй аргумент — объект конфигурации, который определяет, нужно ли отправлять письмо и какой уровень логирования использовать (причем сразу видно, что допустимы только три строковых значения).
-
Третий аргумент — опции процесса, с четкими числовыми параметрами.
-
Функция ничего не возвращает (
void).
Такой код не нуждается в пояснениях. Типы сами по себе являются превосходной документацией, которая всегда актуальна (в отличие от комментариев, которые могут устареть). Если вы измените interface Config, добавив новое поле, компилятор заставит вас обновить все места, где создается объект Config, иначе код просто не соберется. Это гарантирует, что документация никогда не расходится с реальностью.
Преимущества TypeScript: улучшенные возможности IDE
Система типов это не только про поиск ошибок. Это еще и про невероятно мощную помощь в процессе написания кода. Современные IDE (Visual Studio Code, WebStorm, IntelliJ IDEA) используют информацию о типах из TypeScript, чтобы предоставить разработчику суперспособности.
1. Умное автодополнение (IntelliSense):
IDE точно знает, какие методы и свойства есть у переменной. Вы начинаете печатать user. и среда разработки показывает вам полный список: user.id, user.email, user.isActive. Вам не нужно помнить точные названия полей или лазить в документацию. Это ускоряет разработку в разы и сокращает количество опечаток.
2. Навигация по коду и рефакторинг:
Хотите найти все места, где используется определенный интерфейс User? Просто кликните по нему правой кнопкой и выберите «Find All References». Хотите переименовать свойство email на emailAddress во всем проекте? В JavaScript это рискованно, вы можете случайно переименовать не то. TypeScript гарантирует, что благодаря типам это изменение будет произведено точно и безопасно только в тех местах, где речь идет именно о свойстве объекта User. Рефакторинг становится быстрым и нестрашным.
3. Встроенная документация:
Наведите курсор на функцию или переменную и IDE покажет вам всплывающую подсказку с ее сигнатурой, типами параметров и возвращаемого значения. Это моментальный доступ к документации, не покидая текущего файла.
Практический пример в IDE:
Допустим, вы импортируете библиотеку для работы с HTTP. В JavaScript вы можете гадать, какие методы есть у ее API. В TypeScript, после установки типов для этой библиотеки (@types/package-name), вы получаете полное автодополнение. Вы начинаете писать http. и IDE предлагает http.get(), http.post() и т.д. Вы выбираете http.get() и IDE сразу показывает, что этот метод ожидает URL строку и объект опций. Вы видите все доступные опции (заголовки, таймауты) без необходимости гуглить мануал.
Практические задачи и примеры кода для закрепления
Давайте немного попрактикуемся, чтобы закрепить теорию. Не переживайте, если что-то покажется сложным, мы будем разбирать все по кирпичикам в следующих уроках. Сейчас главное почувствовать разницу.
Задача 1: Найдите ошибку на этапе компиляции
Перед вами код на TypeScript. В нем есть как минимум две ошибки, которые TypeScript обнаружит еще до запуска. Ваша задача прочитать код, найти эти ошибки и объяснить, почему они возникли с точки зрения системы типов.
interface Product { id: number; title: string; price: number; inStock: boolean; } function calculateTotal(products: Product[]): number { let total = 0; for (const product of products) { total += product.prcie; // Ошибка 1 (подсказка: опечатка) } return total; } const myProducts: Product[] = [ { id: 1, title: "Носки", price: 100, inStock: true }, { id: 2, title: "Футболка", price: 500, inStock: true }, { id: 3, title: "Кепка", price: "300", inStock: false } // Ошибка 2 ]; const result = calculateTotal(myProducts);
Решение:
-
Ошибка 1:
total += product.prcie;Опечатка в имени свойства. У объектаProductнет свойстваprcie, естьprice. TypeScript выдаст ошибку:Property 'prcie' does not exist on type 'Product'. Did you mean 'price'?. -
Ошибка 2: В объекте
{ id: 3, title: "Кепка", price: "300", inStock: false }для поляpriceпередана строка"300", в то время как в интерфейсеProductуказан типnumber. TypeScript выдаст ошибку:Type 'string' is not assignable to type 'number'.
Задача 2: Сделайте код самодокументирующимся
Перед вами JavaScript-функция. Перепишите ее на TypeScript, добавив аннотации типов (type annotations) для аргументов и возвращаемого значения. Постарайтесь угадатьIntent (замысел) функции по ее названию и тому, что она делает. Создайте интерфейс для объекта book.
// Исходный JavaScript function getBookAuthorNames(book) { return book.authors.map(author => `${author.firstName} ${author.lastName}`).join(', '); } const myBook = { title: "TypeScript для чайников", authors: [ { firstName: "Максим", lastName: "Габов" }, { firstName: "Анна", lastName: "Петрова" } ], year: 2024 }; console.log(getBookAuthorNames(myBook));
Пример решения на TypeScript:
// Ваше решение на TypeScript interface Author { firstName: string; lastName: string; } interface Book { title: string; authors: Author[]; // Массив объектов типа Author year: number; } function getBookAuthorNames(book: Book): string { return book.authors.map(author => `${author.firstName} ${author.lastName}`).join(', '); } const myBook: Book = { title: "TypeScript для чайников", authors: [ { firstName: "Максим", lastName: "Габов" }, { firstName: "Анна", lastName: "Петрова" } ], year: 2024 }; console.log(getBookAuthorNames(myBook)); // Выведет: "Максим Габов, Анна Петрова"
Теперь любой разработчик, взглянув на объявление функции getBookAuthorNames(book: Book): string, сразу поймет, что она принимает объект книги и возвращает строку. А взглянув на интерфейсы Book и Author, он поймет структуру ожидаемых данных без единого комментария.
Заключение
Вот и подошел к концу наш первый, вводный урок. Мы не написали ни строчки сложного кода, но заложили фундаментальное понимание философии TypeScript. Мы разобрались, почему большие проекты на JavaScript сложно масштабировать, что такое статическая типизация и какие ключевые преимущества она дает: раннее обнаружение ошибок, самодокументирование кода и невероятный буст производительности в IDE.
TypeScript это надстройка, ваш верный и умный помощник, который делает вашу JavaScript-разработку предсказуемой, профессиональной и, в конечном счете, более быстрой за счет сокращения времени на отладку и понимание чужого кода.
В следующем уроке мы практично подойдем к делу и научимся устанавливать TypeScript, настраивать его в своем проекте и запустим наш первый .ts-файл. До встречи!
Это был лишь первый урок из большого и подробного курса. Если вы хотите пройти весь путь от начинающего до уверенного TypeScript-разработчика, то жду вас на полном курсе: TypeScript для начинающих: полное руководство.
Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.


