Кратко
Секция статьи "Кратко"В JavaScript слабая динамическая типизация. Это означает две вещи:
- Любая переменная может произвольно менять свой тип во время выполнения программы.
- При операциях с переменными разных типов они будут автоматически приведены к одному типу.
Эти свойства языка часто мешают создавать большие надёжные приложения. Поэтому появились решения, которые расширяют язык, добавляя в него строгую статическую типизацию. «Строгая» означает запрет автоматического приведения типов, «статическая» значит, что переменные не меняют свой тип. Самое популярное решение в этой области — TypeScript. Другие, менее популярные — Flow, Hegel.
Как пользоваться
Секция статьи "Как пользоваться"Настройка
Секция статьи "Настройка"TypeScript — это язык, очень похожий на JavaScript. Браузеры и Node.js не умеют исполнять его, поэтому без этапа сборки пользоваться им нельзя.
Все современные системы сборки умеют работать с TypeScript: Parcel поддерживает его без дополнительных манипуляций, для Webpack и Rollup есть плагины. Их объединяет одно — необходимо создать файл tsconfig.json, который опишет, как превратить TypeScript-код в JavaScript-код. Правила создания конфигурационного файла описаны на сайте TypeScript.
{ "compilerOptions": { // Указываем папку, куда попадёт результат "outDir": "./dist", }, // Указываем, какие файлы следует компилировать "include": ["src/**/*"], // Внешние зависимости обычно исключают из списка компилируемых файлов "exclude": ["node_modules"]}
{ "compilerOptions": { // Указываем папку, куда попадёт результат "outDir": "./dist", }, // Указываем, какие файлы следует компилировать "include": ["src/**/*"], // Внешние зависимости обычно исключают из списка компилируемых файлов "exclude": ["node_modules"] }
После этого достаточно писать код в файлах с расширением .ts вместо .js.
Язык
Секция статьи "Язык"Главное отличие TypeScript от JavaScript — возможность добавлять аннотации типов к переменным, аргументам функций и их возвращаемым значениям.
// Теперь переменной age можно присвоить только числоlet age: number// Будет работатьage = 43// Выдаст ошибкуage = "34"
// Теперь переменной age можно присвоить только число let age: number // Будет работать age = 43 // Выдаст ошибку age = "34"
Важно заметить, что ошибки несовпадения типов будут заметны уже на стадии написания кода. То есть не нужно запускать программу, чтобы узнать, что в коде допущены ошибки с типизацией.
Проверка корректности типов — это разновидность статического анализа. Его можно провести, не запуская код.
Примерно таким же образом можно типизировать параметры функции:
function sayMyName(name: string) { console.log(`Привет, ${name}`)}// Будет работатьsayMyName("Igor")// Выдаст ошибкуsayMyName(42)
function sayMyName(name: string) { console.log(`Привет, ${name}`) } // Будет работать sayMyName("Igor") // Выдаст ошибку sayMyName(42)
Ошибка несовпадения типов будет заметна на этапе написания кода, и до запуска программы не дойдёт.
В TypeScript можно типизировать не только параметры функции, но и возвращаемое значение.
function getCurrentDate(): Date { // Будет работать return new Date()}function getCurrentDate(): Date { // Выдаст ошибку return "now"}
function getCurrentDate(): Date { // Будет работать return new Date() } function getCurrentDate(): Date { // Выдаст ошибку return "now" }
Другая особенность TypeScript — строгость типизации. Он запрещает операции с переменными разных типов, чтобы не допустить неоднозначности результата.
const age = 43const name = "Mary"// Выдаст ошибку, складывать числа и строки нельзяconst result = age + name
const age = 43 const name = "Mary" // Выдаст ошибку, складывать числа и строки нельзя const result = age + name
Как понять
Секция статьи "Как понять"TypeScript — надмножество JavaScript. На практике это означает, что любой JavaScript-код является корректным TypeScript-кодом. А вот обратное неверно.
Главное преимущество строгой статической типизации — возможность найти ряд ошибок ещё на этапе написания кода. Например, классическая ошибка, когда в переменной не оказывается значения, приводит в JavaScript к ошибке во время выполнения, когда код уже работает в браузере. Такая ошибка может затронуть пользователей.
function generateEmail(user) { return `${user.name}@mycompany.com`}// При вызове функции программист передает другой объект,// и происходит ошибка во время выполнения, пользователь её замечаетgenerateEmail({ fullName: "Пётр Сергеевич Никольский" })
function generateEmail(user) { return `${user.name}@mycompany.com` } // При вызове функции программист передает другой объект, // и происходит ошибка во время выполнения, пользователь её замечает generateEmail({ fullName: "Пётр Сергеевич Никольский" })
Если переписать этот пример на TypeScript, потенциальная проблема исчезнет:
// Аргумент функции всегда должен быть объектом,// у которого есть строковое поле namefunction generateEmail(user: { name: string }) { return `${user.name}@mycompany.com`}// При вызове функции программист передает другой объект,// происходит ошибка во время сборки, программист её замечает и исправляетgenerateEmail({ fullName: "Петр Сергеевич Никольский" })
// Аргумент функции всегда должен быть объектом, // у которого есть строковое поле name function generateEmail(user: { name: string }) { return `${user.name}@mycompany.com` } // При вызове функции программист передает другой объект, // происходит ошибка во время сборки, программист её замечает и исправляет generateEmail({ fullName: "Петр Сергеевич Никольский" })
Чем раньше обнаруживается ошибка, тем легче её исправить. Поэтому статическая типизация, статический анализ и написание автоматизированных тестов — это правила хорошего тона в современной разработке приложений. По сути, статическая типизация (как и статический анализ) добавляет в процесс разработки программы ещё один шаг — проверку типов, который выполняется перед сборкой проекта и может помочь найти ошибки на раннем этапе.
На первый взгляд кажется, что явное объявление типов заставляет писать много лишнего кода. На самом деле это иллюзия. Аннотации типов — способ обеспечить большую надёжность написанного кода.
TypeScript — это язык с опциональной типизацией. Он не заставляет программиста указывать типы, можно просто писать код как раньше. TypeScript постарается сам определить типы из контекста, и дать подсказки. Если контекст непонятен языку, он пометит тип переменной как any
. Это означает, что в ней может лежать значение любого типа.
// Видимо, переменная должна иметь тип numberconst age = 12// Язык не знает, какой тип имеет аргумент name,// он пометит его как anyfunction sayMyName(name) { console.log(`Привет, ${name}`)}
// Видимо, переменная должна иметь тип number const age = 12 // Язык не знает, какой тип имеет аргумент name, // он пометит его как any function sayMyName(name) { console.log(`Привет, ${name}`) }
Эта особенность языка называется выводом типов и присутствует во многих современных языках программирования. К сожалению, она не слишком развита в TypeScript, и чаще всего приходится всё-таки «подсказывать» компилятору типы. В других языках, например, в Scala, она развита сильнее, там типы приходится указывать значительно реже.
На практике
Секция статьи "На практике"Если у вас нет возможности использовать TypeScript, а типизировать код хочется, то можно использовать JavaScript с аннотациями типов в JSDoc.
/** @type {number} */let age;/** * @param {string} name */function sayMyName(name) {...}/** * @returns {Date} */function getCurrentDate() {...}
/** @type {number} */ let age; /** * @param {string} name */ function sayMyName(name) {...} /** * @returns {Date} */ function getCurrentDate() {...}
Это, конечно, не полноценная проверка типов, а только документация и удобное автодополнение в IDE:
В TypeScript существует «строгий режим» — он вынуждает указывать типы в тех случаях, когда язык не может определить их сам.
Если проект только стартует, и TypeScript был изначально выбран как основной язык, лучше включить строгий режим сразу.
{ "compilerOptions": { // Запрещает класть в переменную null без явного объявления "strictNullChecks": true, // Делает вызовы методов bind, call, apply строго типизированными "strictBindCallApply": true, // Делает более строгими типы функций "strictFunctionTypes": true, // Запрещает объявление непустого поля класса без инициализации "strictPropertyInitialization": true, },}
{ "compilerOptions": { // Запрещает класть в переменную null без явного объявления "strictNullChecks": true, // Делает вызовы методов bind, call, apply строго типизированными "strictBindCallApply": true, // Делает более строгими типы функций "strictFunctionTypes": true, // Запрещает объявление непустого поля класса без инициализации "strictPropertyInitialization": true, }, }
Если проект был написан на JavaScript, и TypeScript внедряется туда постепенно, строгий режим может сильно замедлить этот процесс.