Изучение TypeScript

Изучение TypeScript

По мере увеличения размера веб-приложений и их экспоненциальной сложности произошли значительные изменения в инструментах и языках разработки, которые мы используем. В центре этого находится 'TypeScript', который появился для преодоления ограничений JavaScript, изначально разработанного для простого динамического сценария в прошлых веб-браузерах. Эта статья глубоко погрузится в основные концепции TypeScript и тщательно исследует различные характеристики и меры предосторожности, с которыми сталкиваются на практике.

1. Причины использовать TypeScript

JavaScript является одним из наиболее распространенных языков программирования в мире, но его присущая гибкость может привести к серьезным недостаткам в крупных проектах. TypeScript был разработан для решения этих недостатков JavaScript. Ключевые причины для принятия TypeScript можно резюмировать следующим образом.

1.1. Обеспечение стабильности через обнаружение ошибок на этапе компиляции

JavaScript является 'языком с динамической типизацией'. Поскольку типы переменных определяются во время выполнения, разработчикам очень трудно распознать ошибки типов при написании кода. Например, даже если строка случайно передается функции, ожидающей число, JavaScript выполняет код без каких-либо предупреждений, что приводит к сбоям или неправильным результатам, отображаемым на экране.

С другой стороны, TypeScript является 'языком со статической типизацией', который проверяет типы всех переменных и функций на этапе компиляции (или сборки) до выполнения кода. Если неверный тип назначен или отсутствует обязательный параметр, компилятор немедленно генерирует ошибку, чтобы уведомить разработчика. Это позволяет заранее блокировать значительное количество потенциальных ошибок, которые могут возникнуть во время выполнения в процессе разработки.

1.2. Высокий уровень опыта разработчика (DX) и поддержка инструментов

Когда современные редакторы кода (такие как VS Code) объединяются с TypeScript, производительность разработчиков резко возрастает. Поскольку информация о типах четко определена, редактор предлагает список доступных методов и свойств в реальном времени через функцию 'IntelliSense' по мере того, как разработчик вводит код.

Более того, без необходимости запоминать внутреннюю структуру объектов или заглядывать в спецификации API каждый раз, наведение указателя мыши сразу показывает структуру, тип и документированные комментарии соответствующих данных. При проведении крупных задач по рефакторингу даже изменение названий переменных или функций в комплексе позволяет системе TypeScript безопасно отслеживать и модифицировать все связанные файлы, значительно уменьшая страх перед изменениями в коде.

1.3. Улучшенная документация кода и сопровождаемость

Одним из самых больших вызовов в поддержании старых проектов или кода, написанного другими разработчиками, является определение 'какие данные получает эта функция и что она возвращает'. В JavaScript это требует анализа всей внутренней логики функции или обращения к отдельной документации API, что может привести к серьезной путанице, если документация не обновлена.

В TypeScript сам код служит полными спецификациями. Поскольку параметры и типы возвращаемых значений явно указаны в сигнатурах функций, можно интуитивно понять намерения системы и поток данных, просто прочитав код, без необходимости обращения к отдельным текстовым документам. Это приводит к значительной экономии затрат с точки зрения командной работы и долгосрочного сопровождения.

2. Компилятор TypeScript

TypeScript — это язык, который не может быть выполнен напрямую браузерами или Node.js, так как браузеры могут понимать только JavaScript. Поэтому процесс преобразования кода TypeScript в код JavaScript необходим для выполнения, и ключевым инструментом, отвечающим за эту роль, является 'Компилятор TypeScript (TSC)'.

2.1. Двойная роль компилятора (транспайлер и проверка типов)

Традиционный компилятор (например, компилятор C, Java) переводит исходный код в двоичный код (машинный код) или байт-код, который компьютер может понять. Однако компилятор TypeScript преобразует один высокоуровневый язык в другой высокоуровневый язык (JavaScript), поэтому его более точно можно назвать 'транспилером.' TSC выполняет две совершенно независимые задачи.

  • 1. Проверка типов: она анализирует определения типов написанного исходного кода, чтобы убедиться, что нет ошибок.

  • 2. Транспиляция: она очищает всю синтаксическую структуру типов (интерфейсы, типы, аннотации типов и т.д.) из файлов источника TypeScript (.ts) и преобразует их в чистые файлы JavaScript (.js).

Интересно, что эти два процесса не влияют друг на друга. Даже если в коде есть ошибки типов, пока нет синтаксических ошибок, TSC успешно выведет файл JavaScript. Это намеренный дизайн для разделения проверки типов и этапа сборки для большего удобства разработчиков.

2.2. Процесс компиляции TypeScript и AST

Подробная внутренняя структура конвейера, которую TSC следует для анализа символов файла кода и создания конечного JavaScript, состоит из следующих этапов.

  • 1. Сканер: он считывает текст из исходного файла и разбивает его на значимые минимальные единицы, известные как 'токены.'

  • 2. Парсер: на основе массива токенов он генерирует 'Абстрактное синтаксическое дерево (AST),' которое представляет структуру кода в форме дерева.

  • 3. Связыватель: он проходит по узлам AST, чтобы сопоставить, к какому контексту принадлежат каждая переменная, функция и класс и какие символы они представляют.

  • 4. Проверщик: Это основной движок, который выполняет фактическую проверку типов, используя символы, создаваемые связывателем и AST. Он тщательно проверяет присваиваемость, аргументы вызова функции и многое другое.

  • 5. Эмиттер: После завершения проверки типов он удаляет синтаксис типа из AST и генерирует исходный код и карты источников (.js.map), совместимые с целевой версией JavaScript (например, ES5, ES6 и т.д.).

2.3. Анализ основных параметров в TSConfig.json

Поведение компилятора TypeScript точно контролируется через файл TSConfig.json, расположенный в корне проекта. Ключевые параметры, которые необходимо понять при создании практических проектов, следующие:

{
  "compilerOptions": {
    "target": "ES2022", // 컴파일 후 생성될 자바스크립트 버전 지정
    "module": "CommonJS", // 모듈 시스템 결정 (CommonJS, ESNext 등)
    "lib": ["DOM", "ES2022"], // 컴파일에 포함될 런타임 환경의 내장 타입 정의
    "allowJs": true, // 자바스크립트 파일도 컴파일 대상으로 허용
    "strict": true, // 강력한 타입 검사 옵션 활성화 (추천)
    "noImplicitAny": true, // 명시적 타입이 없는 경우 any 추론 금지
    "strictNullChecks": true, // null과 undefined를 고유한 타입으로 엄격히 취급
    "outDir": "./dist", // 컴파일된 자바스크립트 파일이 저장될 경로 
    "esModuleInterop": true // CommonJS와 ES 모듈 간의 호환성 확보 
   }
 }

В частности, параметр 'strict': true является важной настройкой для написания крупномасштабного, качественного кода. Включение этого параметра активирует noImplicitAny, strictNullChecks и т.д., что заставляет компилятор TypeScript работать наиболее строго и безопасно.

3. Как TypeScript выполняет проверку типов

Способ, которым TypeScript проверяет типы и определяет совместимость, принципиально отличается от традиционных объектно-ориентированных языков (таких как Java, C++ и т.д.). Понимание этой уникальной системы необходимо для написания кода в стиле TypeScript.

3.1. Структурная типизация

Языки, такие как C++ и Java, используют 'Именную Типизацию'. В системе именной типизации типы совместимы только тогда, когда имена классов или их отношения наследования совпадают точно. Другими словами, даже если внутренние структуры совпадают на 100%, если имена классов различаются, они считаются совершенно разными типами.

Напротив, TypeScript поддерживает 'Структурную Типизацию'. Структурная типизация означает, что важен не название типа, а то, 'имеет ли этот тип на самом деле необходимые свойства и методы (структура)' для совместимости. Это можно рассматривать как статическую версию 'Типизации уток', часто упоминаемую в динамических языках.

interface Point {
    x: number;
    y: number;
}

function printPosition(p: Point) {
    console.log(`위치: ${p.x}, ${p.y}`);
}

// 명시적으로 Point 인터페이스를 구현하지 않은 일반 객체
const myObj = { x: 10, y: 20, z: 30 };
printPosition(myObj); // 정상 작동!

В приведенном выше коде myObj неявно не объявил и не реализовал интерфейс Point. Более того, у него также есть дополнительное свойство z. Однако функция printPosition принимает myObj без каких-либо ошибок, потому что myObj полностью включает структуру 'x и y типа number', требуемую интерфейсом Point. Благодаря этой структурной совместимости TypeScript может продвигать гибкие паттерны использования объектных литералов, уникальные для JavaScript.

3.2. Вывод типов

Начинающие разработчики, которые только познакомились с TypeScript, часто неправильно понимают, что они должны указывать типы с помощью двоеточия (:) для каждого объявления переменной. Однако TypeScript обладает очень мощным механизмом 'Вывода типов'. Даже если разработчик явно не объявляет тип, компилятор автоматически анализирует контекст, в котором выполняется код, и значения, которые присваиваются, чтобы определить наиболее подходящий тип.

let message = "안녕하세요"; // string 타입으로 자동 추론
let count = 42;            // number 타입으로 자동 추론

function add(a: number, b: number) {
    return a + b;          // 반환 타입이 number로 자동 추론
}

В приведенном выше примере, в момент, когда строка присваивается переменной message, TypeScript навсегда фиксирует тип этой переменной как строку внутри. Поэтому, если кто-то попытается присвоить число, например message = 123;, это вызовет ошибку. Таким образом, четкие области могут быть оставлены для вывода, в то время как явные объявления типов делаются только в сложной бизнес-логике или областях интерфейса данных API, поддерживая читаемый и чистый код.

4. Ошибки времени выполнения, которые TypeScript не может обнаружить на этапе компиляции

Является очевидным фактом, что TypeScript предоставляет высокую стабильность, но это отнюдь не всепроникающий щит. Все проверки типов в TypeScript действуют только на этапе 'компиляции' в среде разработки, и, как было объяснено выше, весь код типов испаряется в тот момент, когда он преобразуется в JavaScript, фактический исполняемый файл. В результате возникают серьезные ошибки, которые крашут систему на 'времени выполнения', когда фактический пользователь запускает приложение, даже несмотря на то, что оно прошло этап компиляции без проблем.

4.1. Внешние данные и сетевое взаимодействие (данные ответа API)

Самая распространенная причина ошибок времени выполнения — это взаимодействие API с сервером. Разработчики фронтенда объявляют и применяют интерфейсы следующим образом, предвосхищая структуру данных, которую предоставит сервер.

interface UserProfile {
    id: number;
    name: string;
    email: string;
}

async function fetchUser(): Promise<UserProfile> {
    const response = await fetch('/api/user/1');
    return response.json(); // 컴파일러는 이것이 무조건 UserProfile 구조라고 믿음
}

Проблема возникает, когда сервер изменяет структуру данных во время выполнения или возвращает неверные данные, такие как { "message": "Внутренняя ошибка" }, из-за ошибки. Компилятор TypeScript не может знать фактическое состояние сервера во время сборки, поэтому он распознает приведенный выше код как безопасный без каких-либо проблем. Однако в момент выполнения кода, такого как user.name.toUpperCase(), в реальном браузере возникает фатальная ошибка 'TypeError', утверждающая, что невозможно прочитать свойства неопределенного, и приложение крашится.

4.2. Неправильное использование утверждения типа

TypeScript имеет ключевое слово 'as' (утверждение типа), которое позволяет разработчикам заявить, что они знают тип конкретного элемента данных более точно, чем компилятор. Это опасный инструмент, который принудительно аннулирует предупреждения компилятора.

const rawData = &quot;특정 문자열&quot; as any;
const numberData = rawData as number; // 컴파일러는 숫자로 인정

console.log(numberData.toFixed(2)); // 런타임 에러 발생

Поскольку компилятор подтверждает, что разработчик утверждает 'as number', он полностью рассматривает переменную numberData как число и позволяет компиляцию. Однако, поскольку фактические данные все еще являются строкой, она будет крашиться без разбора во время выполнения. Небрежное использование типа any или неполные утверждения типов являются основными виновниками, подрывающими надежность всей системы типов.

4.3. Ошибки доступа к индексам массива (выход за границы)

TypeScript разработан так, чтобы быть в целом снисходительным к доступу к индексам за границами, что делает более вероятным возникновение ошибок.

const numbers: number[] = [1, 2, 3];
const fourthNumber = numbers[5];

console.log(fourthNumber.toFixed(2)); // 런타임에 undefined 에러 발생

Поскольку на 5-м индексе массива ничего нет, фактическое извлеченное значение является неопределенным. Тем не менее, TypeScript проходит компиляцию, предполагая, что поскольку массив чисел состоит из чисел, результат всех обращений к индексам также будет числом. Чтобы преодолеть это ограничение, необходимо включить опцию noUncheckedIndexedAccess в TSConfig.json, чтобы заставить систему учитывать, что неопределенные значения могут смешиваться при доступе к индексам.

5. Сравнение с JavaScript

Последний шаг к полному анализу TypeScript — четко провести количественное и качественное сравнение с его родителем, JavaScript. Ясное понимание различий между двумя языками позволит правильно использовать инструменты в нужных местах.

Сравнительные элементы

JavaScript (JavaScript)

TypeScript

Типовая система

Динамический тип (определяется во время выполнения)

Статический тип (определяется во время компиляции)

Время обнаружения ошибок

Во время выполнения

Во время компиляции

Автозавершение кода

Ограниченный (на основе вывода)

Очень мощный и точный (на основе типов)

Инструменты и экосистема

Прямое выполнение в браузере/Node.js

Необходим процесс трансформации через компилятор (TSC)

Соответствие проекту

Малые скрипты, быстрая прототипизация

Масштабные приложения, проекты многопользовательского сотрудничества

5.1. Различия между парадигмой и философией

JavaScript имеет философию «быть как можно более гибким, но не останавливать выполнение в первую очередь». Существует сильная тенденция обрабатывать исключения довольно грубо внутренне, чтобы гарантировать, что вся веб-страница не остановится, даже при незначительных ошибках. В отличие от этого, TypeScript придерживается строгой философии «если вы не уверены, даже не выполняйте». Хотя это может повлечь за собой более высокие начальные затраты на написание, существует убеждение, что строгость обеспечивает гораздо большие преимущества для стабильности масштабных систем.

6. Заключение

В заключение, TypeScript не является заменой JavaScript, а является большим расширением и дополнением. Он идеально наследует экосистему и стандарты JavaScript, при этом плотно интегрируя механизмы безопасности, необходимые для масштабных корпоративных окружений.

Признавая ограничение в том, что ошибки времени выполнения нельзя предотвратить на 100%, если правильно установить параметры TSConfig и объединить их с библиотеками валидации времени выполнения, такими как Zod, вы можете создать почти пуленепробиваемый безопасный код. Мы настоятельно рекомендуем продвигаться от парадигмы разработки, оставшейся на уровне JavaScript, к TypeScript, чтобы создавать более надежные и элегантные приложения.

Май

Site footer