TypeScript vs JavaScript — Детальное сравнение

Источник: «TypeScript vs JavaScript - A Detailed Comparison»
В этой статье приводится глубокое сравнение и противопоставление TypeScript и его предшественника, стандартизированного ECMA, JavaScript.

Оглавление

Введение

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

В этой статье мы прольём свет на важные концепции, связанные со статической типизацией, и узнаем о возможностях и преимуществах, которые они дают TypeScript по сравнению с JavaScript. Мы рассмотрим роль определений типов, аннотаций типов и проверки типов с помощью структурной системы типов. При этом мы вспомним примитивные типы (number, string, boolean) в JavaScript, закладывающие основы для более сложных определений типов в TypeScript. Мы также поговорим о литеральных типах (string, array и object) и дополнительных типах (any, unknown, void и т. д.), добавляемых TypeScript в свой набор инструментов, а также о наборе утилитарных типов (Awaited<>, Pick<>, Omit<>, Partial<>, Record<> и т. д.), используемых для выведения новых типов из существующих.

Во второй половине мы исследуем инструменты, запускающие процессы, способствующие статической типизации в TypeScript. Мы получим краткий отчёт о том, как TypeScript-код транспилируется с помощью компилятора TypeScript (tsc) в исполняемый JavaScript-код. Мы также узнаем, что в tsc интегрирована проверка типов TypeScript, выполняющий проверку типов и выдающий ошибки, помогающие разработчикам писать типобезопасный код, исправляя ошибки, связанные с типами, на ранних этапах разработки. Мы получим краткий обзор инструментальных функций средства проверки типов TS: а именно, редактора и лингвистической поддержки с завершением кода, быстрыми предложениями по исправлению ошибок, форматированием/реорганизацией кода, рефакторингом кода и навигацией по коду. Мы также узнаем, как все возможности компилятора TypeScript интегрированы в VS Code с помощью запуска фоновых задач, что улучшает работу разработчиков, помогая избежать повторных запусков tsc.

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

Концепции TypeScript

Статическая и динамическая типизация

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

Microsoft создала TypeScript, чтобы добавить систему статической типизации поверх JavaScript. Она была представлена с открытым исходным кодом в 2012 году, чтобы помочь в написании защищённых от ошибок, стабильных, поддерживаемых и масштабируемых веб-приложений. Статическая типизация — это реализация, при которой типы выражений определяются до начала выполнения. В частности, в TypeScript статическая типизация происходит до компиляции, выполняемой tsc, компилятором TypeScript.

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

Определения типов

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

Примитивные типы данных(number, string, boolean, null и undefined), уже являющиеся частью JavaScript, обычно закладывают основу для более сложных, вложенных определений типов в TypeScript. TypeScript добавляет в свой арсенал ещё несколько статических типов, таких как any, unknown, void, never и т. д., чтобы учесть различные сценарии.

Помимо них, TypeScript предлагает множество утилит типов, помогающих преобразовать один тип в другой. Примеры таких утилит включают Awaited<>, Pick<>, Omit<>, Partial<>, Record<> и т. д. Такие утилиты не актуальны в JavaScript, но в TypeScript они удобны для получения полезных вариантов из базового типа. Их использование повышает стабильность кодовой базы JavaScript и помогает сделать большие веб-приложения легко настраиваемыми и поддерживаемыми.

Аннотации и выведение типов

Добавление соответствующих аннотаций типов к выражениям — это, пожалуй, самая важная часть статической типизации в TypeScript. Она необходима для последующей проверки соответствия типов, выполняемой проверкой типов TypeScript.

Аннотации типов в TypeScript

Аннотации типов делаются явно для выражений, использующих примитивные типы данных и/или — как уже говорилось выше — типы, определённые с помощью алиасов, интерфейсов, перечислений и классов.

Целевыми выражениями для аннотации типов являются объявления переменных, объявления функций, параметры функций и возвращаемые типы функций. Аннотации также делаются для полей класса и других членов, таких как методы и аксессоры — вместе с их ассоциированными параметрами и возвращаемыми типами.

Выведение типов в TypeScript

Если аннотации типов не добавлены явно, TypeScript выводит тип из примитивного типа, литералов или объектной формы самого значения.

Выведение типа может следовать двум следующим принципам:

Проверка типов и структурная система типов

Определённое значение выражения проверяется на соответствие его аннотированному или выведенному типу проверкой типов TypeScript. Совместимость типов зависит от того, совпадает ли структура или форма значения со структурой или формой аннотированного типа. Другими словами, TypeScript имеет структурную систему типов.

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

Пример совместимости форм в структурной системе типов TypeScript

Например, ниже приведён stereoTypicalJoe, введённый в User:

type User = {
username: string;
email: string;
firstName: string;
lastName: string;
};

type Person = {
username: string;
email: string;
firstName: string;
lastName: string;
};

type Admin = {
username: string;
email: string;
firstName: string;
lastName: string;
role?: string;
};

// `stereotypicalJoe` совместим с `User`
const stereoTypicalJoe: User = {
username: "stereotypical_joe",
email: "joe_stereo@typed.com",
firstName: "Joe Stereo",
lastName: "Typed",
};

Благодаря структурной системе типов TypeScript, он также совместим с Person, поскольку Person структурно идентичен User:

// Он также совместим с `Person`, поскольку `Person` структурно идентичен `User`
const stereoTypicalJoe: Person = {
username: "stereotypical_joe",
email: "joe_stereo@typed.com",
firstName: "Joe Stereo",
lastName: "Typed",
};

TypeScript также позволяет stereoTypicalJoe быть совместимым с типом Admin, поскольку типы эквивалентны (role — необязательное свойство в Admin):

// Система структурных типов позволяет `stereoTypicalJoe` быть совместимым с `Admin`, который эквивалентно типизирован в `User`.
const stereoTypicalJoe: Admin = {
username: "stereotypical_joe",
email: "joe_stereo@typed.com",
firstName: "Joe Stereo",
lastName: "Typed",
};

Структурная совместимость в TypeScript — практически подходящий вариант для аннотирования и проверки типов выражений JavaScript, поскольку формы объектов в веб-приложениях остаются одинаковыми или похожими, в то время как их состав сильно варьируется. Это отличается от системы номинальных типов, в которой соответствие типам определяется строго на основе конкретно именованных типов.

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

Инструменты TypeScript

tsc, компилятор TypeScript

Центральным инструментом, используемым TypeScript для выполнения процессов, связанных со статической типизацией, является компилятор TypeScript, tsc. Основная задача компилятора TS — преобразовать статически типизированный код в готовый к выполнению чистый JavaScript код. Это означает, что определения типов и аннотации, которые мы добавляем в файл .ts или .tsx, стираются после компиляции. Другими словами, в выходные файлы .js или .jsx не передаётся статическая типизация, которую мы изначально добавили в соответствующие файлы TS.

Пример транспиляции TS

Например, следующий TypeScript код:

//hello.ts
function greet(person: string, date: Date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}

greet("Joe", new Date());

компилируется в JS-скрипт, приведённый ниже:

//hello.js
"use strict";
function greet(person, date) {
console.log("Hello ".concat(person, ", today is ").concat(date.toDateString(), "!"));
}
greet("Joe", new Date());

Обратите внимание, что аннотации типов, применённые в .ts-файле, не выводятся в .js-версию. Но из проверки типов TypeScript мы узнаем, что они были проверены на соответствие типу.

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

Конфигурация компилятора TS

Компилятор TS обычно настраивается с набором стандартных опций по умолчанию в файле tsconfig.json. Но мы можем настроить его в соответствии с нашими потребностями и предпочтениями. В частности, в объекте compilerOptions мы можем задать опции для целевой версии ECMAScript, проверки типов, сканирования модулей, экспериментальных возможностей и т. д.

{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"experimentalDecorators": true
},
"include": ["src"]
}

Проверка типов TypeScript

tsc оснащён статической системой проверки типов, сверяющей значение выражения с его аннотированным или выведенным типом. В случае неудачного совпадения он выдаёт ошибку(и) типа. Основная цель проверки типов — проверка соответствия типам. Более широкая цель — обеспечить безопасность типа в кодовой базе, отлавливая и предлагая исправления для всех возможных видов ошибок типа в процессе разработки.

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

Ошибки выводятся компилятором в консоль командной строки при запуске файла командой tsc:

tsc hello.ts

Проверка типов TS отслеживает информацию из определений типов и аннотаций в кодовой базе. Затем он использует эти описания для проверки соответствия структуре/форме или выдачи ошибки. Проверка типов выполняется во время изменений кода и перед запуском компиляции.

Лингвистический инструментарий

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

Основываясь на сохранённых описаниях типов, проверка типов также может помочь нам с завершением кода, быстрыми предложениями по исправлению, рефакторингом, форматированием/реорганизацией, навигацией по коду и т. д.

Поддержка TypeScript в VS Code

Visual Studio Code от Microsoft, или сокращённо VS Code, поставляется с интегрированной поддержкой компилятора TypeScript, его статической проверки типов и других лингвистических инструментов, упомянутых выше. Компилятор tsc и статическая проверка типов запускаются в редакторе кода с помощью фоновых задач в режиме watch.

Например, IntelliSense VS Code запускает статическую проверку типов TypeScript в фоновом режиме, обеспечивая завершение кода для типизированных выражений:

VS Code с IntelliSense
Автозавершение кода в VS Code с IntelliSense

Ниже приведён список других основных функций VS Code, помогающих TypeScript разработчикам в повседневной работе:

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

Определения типов TypeScript / Пакеты объявлений

TypeScript поставляется со встроенными определениями для всех стандартных API JavaScript. Они включают определения типов для таких типов объектов, как Math, Object, связанных с браузером DOM API и т. д. Доступ к ним можно получить из любой точки проекта без необходимости импортирования типов.

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

Объявление типов TypeScript — .d.ts файлы

Объявления типов для конкретных приложений обычно собираются в файле с суффиксом .d.ts. Обычно все типы и интерфейсы объявляются в одном файле index.d.ts и экспортируются из него.

Пример файла объявления типов TypeScript

//src/interfaces/index.d.ts
export interface User {
username: string;
email: string;
firstName: string;
lastName: string;
}

export interface Person {
username: string;
email: string;
firstName: string;
lastName: string;
}

export interface Admin {
username: string;
email: string;
firstName: string;
lastName: string;
role?: string;
}

При аннотировании мы должны импортировать каждый тип в используемый файл:

import { User, Person, Admin } from "src/interfaces/index.d.ts";

const anyUser: User = {
username: "stereo_joe",
email: "joe@typed.com",
firstName: "Joe",
lastName: "Typed",
};

const typicalJoe: Person = {
username: "typical_joe",
email: "joe_typical@typed.com",
firstName: "Joe Structure",
lastName: "Typed",
};

const stereoTypicalJoe: Admin = {
username: "stereotypical_joe",
email: "joe_stereo@typed.com",
firstName: "Joe Stereo",
lastName: "Typed",
};

Пакеты типов TypeScript — DefinitelyTyped @types

Существующие библиотеки JavaScript, поддерживающие TypeScript, предлагают пакеты определений типов для использования с TypeScript. DefinitelyTyped — популярный репозиторий определений типов, хранящий коллекции пакетов определений типов для основных JS библиотек, которые также поддерживают TypeScript.

Пакеты определения типов, размещённые на DefinitelyTyped, находятся в каталоге @types/. Мы можем получить необходимые пакеты определений с помощью npm или yarn. Например, определения типов react могут быть добавлены в node_modules с помощью следующего пакета:

npm install --save-dev @types/react

Затем мы можем использовать типы в нашем приложении. Важно отметить, что в отличие от использования файлов декларации .d.ts, нам не нужно импортировать типы из их файлов node_modules. Это происходит потому, что они становятся доступными автоматически благодаря npm.

Расширенные возможности TypeScript

Помимо реализации статической системы типов для создания устойчивой к ошибкам, поддерживаемой и масштабируемой кодовой базы, TypeScript расширяет язык дополнительными возможностями с соответствующим синтаксисом. TypeScript enums — это дополнение, которое вводит объекты типов в JavaScript. Классы TypeScript реализованы таким образом, что создают типы. Некоторые аспекты классов TS, такие, как приватность членов и декораторы, реализованы иначе, чем в JavaScript.

В следующих разделах мы попытаемся понять, чем они отличаются.

Перечисления

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

Перечисления служат эффективной заменой объектов, которые в противном случае хранились бы в таблице базы данных. Они генерируют типы, которые можно использовать для аннотирования выражений или свойств объектов. Подробный пример использования TypeScript перечислений можно найти в этой статье A Detailed Guide on TypeScript Enum with Examples.

Классы как типы

В TypeScript классы также генерируют типы из своей функции конструктора. Экземпляр данного класса по умолчанию при статической проверке типов генерирует тип из класса. Подробный пример можно посмотреть статье Essentials of TypeScript Classes.

В отличие от этого, экземпляры классов в JavaScript помечаются своими типами во время выполнения.

Видимость членов класса

TypeScript поддерживает видимость членов класса начиная с ES2015. В нем реализована приватность членов на трёх уровнях: public, protected и private. Приватность членов класса в TypeScript моделируется в соответствии с прототипическим наследованием, основанным на объектно-ориентированных концепциях.

Например, public члены доступны отовсюду, как в экземплярах, в самом классе, так и в подклассах. protected члены не доступны из экземпляров, они доступны только из класса и его подклассов. private члены доступны только изнутри класса.

В отличие от этого, начиная с ES2022, JavaScript реализует приватность свойств класса с помощью синтаксиса #. Доступ к свойствам в классах JavaScript может быть либо полностью публичным, либо полностью приватным. Кроме того, приватность свойства класса в JavaScript не наследуется, поскольку она недоступна из цепочки прототипов экземпляра класса.

Декораторы классов

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

Однако в TypeScript есть предложение Stage 3, которое предлагает декораторы классов со специальным синтаксисом @. Это значительно отличается от обычной реализации декораторов в JavaScript и TypeScript. Декораторы класса в TypeScript позволяют декорировать классы и их члены с помощью поведения во время выполнения. Декорировать можно сам класс, а также поля, методы и аксессоры. Полный пример декораторов классов можно найти в статье TypeScript Decorators in Brief.

Продвинутые возможности TypeScript

Другие расширенные возможности TypeScript связаны с итераторами и генераторами, миксинами, модулями, пространством имён, поддержкой JSX и т. д.

Большинство этих продвинутых концепций требуют особых соображений для облегчения соответствующей статической типизации. Например, итераторы и генераторы TypeScript должны реализовывать свойство Symbol.iterator и должны быть аннотированы интерфейсом Iterable. Миксины TypeScript используют сложные отношения между типами экземпляров класса, подтипами, несколькими реализациями интерфейсов, приватностью членов класса, прототипическим наследованием и выражениями класса. Слишком много, но и слишком хорошо…

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

Подведение итогов

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

Дополнительные материалы

Предыдущая Статья

Пора заменить MySQL и PostgreSQL на SQLite

Следующая Статья

Пользовательские события CustomEvent