Кратко
СкопированоВ JavaScript слабая динамическая типизация. Это означает две вещи:
- При операции с переменными разных типов они будут автоматически приведены к одному типу.
- Любая переменная может произвольно менять свой тип во время выполнения программы. (За исключением строго типизированных массивов, например
Uint8Clamped
)Array
Эти свойства языка часто усложняют разработку больших и надёжных приложений. Поэтому появились решения, которые добавляют в JavaScript строгую статическую типизацию. «Строгая» означает запрет автоматического приведения типов, «статическая» значит, что проверка типов происходит на этапе сборки приложения или написания кода.
Самое популярное решение в этой области — TypeScript. Другие, менее популярные — Rescript, Flow и Hegel.
Как пользоваться
СкопированоНастройка
СкопированоTypeScript — это язык, очень похожий на JavaScript. Браузеры и Node.js не могут выполнять TypeScript, поэтому его необходимо преобразовать в JavaScript.
Современные системы сборки умеют работать с TypeScript: Vite и Parcel поддерживают его из коробки, в Webpack и Rollup нужно будет добавить дополнительные плагины или лоадеры. Настройки TypeScript задаются в специальном файле — tsconfig.json. Структура файла tsconfig.json описана на сайте TypeScriptLang.org.
{ "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('Игорь')// Привет, Игорь// Выдаст ошибкуsayMyName(42)
function sayMyName(name: string) { console.log(`Привет, ${name}`) } // Будет работать sayMyName('Игорь') // Привет, Игорь // Выдаст ошибку 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: 'Петр Сергеевич Никольский' })// Редактор кода или линтер укажет ошибку примерно такого содержания:// Argument of type '{ fullName: string; }' is not assignable to parameter of type '{ name: string; }'.
// Аргумент функции всегда должен быть объектом, // у которого есть строковое поле name function generateEmail(user: { name: string }) { return `${user.name}@mycompany.com` } // При вызове функции программист передает другой объект, // происходит ошибка во время сборки, программист её замечает и исправляет generateEmail({ fullName: 'Петр Сергеевич Никольский' }) // Редактор кода или линтер укажет ошибку примерно такого содержания: // Argument of type '{ fullName: string; }' is not assignable to parameter of type '{ name: string; }'.
Чем раньше обнаруживается ошибка, тем легче её исправить. Поэтому статическая типизация, статический анализ и написание автоматизированных тестов — это правила хорошего тона в современной разработке приложений. По сути, статическая типизация (как и статический анализ) добавляет в процесс разработки программы ещё один шаг — проверку типов, который выполняется перед сборкой проекта и может помочь найти ошибки на раннем этапе.
На первый взгляд кажется, что явное объявление типов увеличивает время разработки, так как требует писать много лишнего. Но это иллюзия. Аннотации типов — способ обеспечить большую надёжность написанного кода. Кроме этого, строгая типизация помогает компилятору эффективнее оптимизировать код.
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 внедряется туда постепенно, строгий режим может сильно замедлить этот процесс.