Spec-Zone.ru › TypeScript 5.1

Основы

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

// Accessing the property 'toLowerCase'
// on 'message' and then calling it
message.toLowerCase();

// Calling 'message'
message();

Если мы разберем это, первая исполняемая строка кода обращается к свойству, названному toLowerCase, а затем вызывает его. Вторая пытается вызвать message напрямую.

Но предположим, что мы не знаем значения message — и это довольно распространённая ситуация — мы не можем надёжно сказать, какие результаты мы получим, пытаясь запустить любой из этого кода. Поведение каждой операции полностью зависит от исходного значения.

  • Является ли message вызываемым?
  • Есть ли у него свойство, названное toLowerCase?
  • Если есть, является ли toLowerCase вызываемым?
  • Если оба эти значения вызываемые, что они возвращают?

Ответы на эти вопросы обычно мы держим в голове, когда пишем JavaScript, и надеемся, что все детали верны.

Предположим, что message было определено следующим образом.

const message = "Hello World!";

Как вы, вероятно, догадались, если мы попробуем запустить message.toLowerCase(), получим ту же строку, только в нижнем регистре.

Что насчет второй строки кода? Если вы знакомы с JavaScript, вы знаете, что это завершится исключением:

TypeError: message is not a function

Было бы здорово, если бы мы могли избежать таких ошибок.

Когда мы запускаем наш код, способ, которым наша среда выполнения JavaScript выбирает, что делать, заключается в определении типа значения — каких видов поведения и возможностей оно имеет. Это часть того, что подразумевает TypeError — это означает, что строка "Hello World!" не может быть вызвана как функция.

Для некоторых значений, таких как примитивы string и number, мы можем определить их тип во время выполнения, используя оператор typeof. Но для других вещей, таких как функции, нет соответствующего механизма во время выполнения для определения их типов. Например, рассмотрим эту функцию:

function fn(x) {
  return x.flip();
}

Мы можем наблюдать, прочитав код, что эта функция будет работать только если ей передать объект со вызываемым свойством flip, но JavaScript не предоставляет эту информацию таким образом, чтобы мы могли проверить её во время выполнения кода. Единственный способ в чистом JavaScript узнать, что fn делает с конкретным значением, это вызвать его и посмотреть, что произойдёт. Такое поведение затрудняет предсказание того, что код сделает до запуска, что затрудняет понимание, что ваш код сделает во время написания.

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

Альтернатива — использование статической системы типов для прогнозирования поведения кода до его запуска.

Статическая проверка типов

Вспомните о той TypeError ошибке, которую мы получили ранее, пытаясь вызвать string как функцию. Большинству людей не нравятся ошибки при запуске кода — это считается ошибкой! И когда мы пишем новый код, мы стараемся избегать введения новых ошибок.

Если мы добавим немного кода, сохраним файл, повторно запустим код и сразу увидим ошибку, мы сможем быстро изолировать проблему; но это не всегда так. Возможно, мы недостаточно тщательно протестировали функцию, поэтому мы никогда не столкнёмся с потенциальной ошибкой, которая была бы выброшена! Или, если нам повезло увидеть ошибку, мы могли бы сделать большие рефакторинги и добавить много разных кодов, через которые мы вынуждены пробираться.

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

const message = "hello!";
 
message();

Запуск последнего примера с TypeScript выдаст сообщение об ошибке до запуска кода.

Ошибки без исключений

До сих пор мы обсуждали определённые вещи, такие как ошибки во время выполнения — случаи, когда среда выполнения JavaScript сообщает нам, что считает что-то бессмысленным. Такие случаи возникают, потому что спецификация ECMAScript содержит явные инструкции о том, как язык должен вести себя, когда сталкивается с чем-то неожиданным.

Например, спецификация гласит, что попытка вызова чего-то, что не является вызываемым, должна привести к ошибке. Может быть, это звучит как «очевидное поведение», но можно представить, что обращение к свойству, которого нет в объекте, также должно привести к ошибке. Вместо этого JavaScript даёт нам другое поведение и возвращает значение undefined.

const user = {
  name: "Daniel",
  age: 26,
};

user.location; // returns undefined

В конечном счёте, статическая система типов должна принимать решения о том, какой код должен быть помечен как ошибка в своей системе, даже если это «валидный» JavaScript, который не сразу выбросит ошибку. В TypeScript следующий код выдаёт ошибку о том, что location не определено:

const user = {
  name: "Daniel",
  age: 26,
};
 
user.location;

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

Например: опечатки,

const announcement = "Hello World!";
 
// How quickly can you spot the typos?
announcement.toLocaleLowercase();
announcement.toLocalLowerCase();
 
// We probably meant to write this...
announcement.toLocaleLowerCase();

невызванные функции,

function flipCoin() {
  // Meant to be Math.random()
  return Math.random < 0.5;
}

или базовые ошибки в логике.

const value = Math.random() < 0.5 ? "a" : "b";
if (value !== "a") {
  // ...
} else if (value === "b") {
  // Oops, unreachable
}

Типы для инструментов

TypeScript может обнаруживать ошибки при возникновении ошибок в коде. Это отлично, но TypeScript также может предотвратить их возникновение.

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

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

import express from "express";
const app = express();
 
app.get("/", function (req, res) {
  res.sen
});
 
app.listen(3000);

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

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

Мы говорили о проверке типов, но ещё не использовали систему проверки типов. Давайте познакомимся с нашим новым другом tsc, компилятором TypeScript. Сначала нам нужно получить его через npm.

npm install -g typescript

Это устанавливает компилятор TypeScript tsc глобально. Вы можете использовать npx или аналогичные инструменты, если предпочитаете запускать tsc из локального пакета node_modules.

Теперь перейдём в пустую папку и попробуем написать нашу первую программу TypeScript: hello.ts.

// Greets the world.
console.log("Hello world!");

Здесь нет излишеств; эта программа «hello world» выглядит идентично тому, что вы написали бы для программы «hello world» на JavaScript. И теперь давайте проверим её на наличие ошибок, запустив команду tsc, которая была установлена для нас пакетом typescript.

tsc hello.ts

Готово!

Подождите, «готово» что конкретно? Мы запустили tsc и ничего не произошло! Ну, ошибок по типу не было, поэтому в консоли не было никакого вывода, так как не было ничего для отчёта.

Но посмотрите ещё раз — вместо этого мы получили вывод в файл. Если мы посмотрим в текущую директорию, увидим файл hello.js рядом с hello.ts. Это вывод из нашего файла hello.ts после того, как tsc скомпилировал или преобразовал его в обычный JavaScript-файл. И если мы проверим содержимое, мы увидим, что TypeScript выводит после обработки файла .ts:

// Greets the world.
console.log("Hello world!");

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

А что, если мы добавим ошибку проверки типов? Давайте перепишем hello.ts:

// This is an industrial-grade general-purpose greeter function:
function greet(person, date) {
  console.log(`Hello ${person}, today is ${date}!`);
}
 
greet("Brendan");

Если мы снова запустим tsc hello.ts, обратите внимание, что в командной строке появляется сообщение об ошибке!

Expected 2 arguments, but got 1.

TypeScript сообщает нам, что мы забыли передать аргумент в функцию greet, и это правильно. До сих пор мы писали стандартный JavaScript, и при этом проверка типов всё равно смогла найти проблемы в нашем коде. Спасибо, TypeScript!

Выдача с ошибками

Одна вещь, которую вы могли не заметить в последнем примере, заключалась в том, что наш файл hello.js снова изменился. Если мы откроем этот файл, то увидим, что содержимое по-прежнему в основном похоже на наш входной файл. Это может быть немного неожиданно, учитывая тот факт, что tsc сообщило об ошибке в нашем коде, но это основано на одном из основных принципов TypeScript: в большинстве случаев вы знаете лучше, чем TypeScript.

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

Поэтому TypeScript не мешает. Конечно, со временем вы можете захотеть быть более защищёнными от ошибок и заставить TypeScript действовать более строго. В этом случае вы можете использовать опцию компилятора noEmitOnError. Попробуйте изменить свой файл hello.ts и запустить tsc с этим флагом:

tsc --noEmitOnError hello.ts

Вы заметите, что hello.js не обновляется.

Явные типы

До сих пор мы не говорили TypeScript, что такое person или date. Давайте изменим код, чтобы сказать TypeScript, что person это string, а date должно быть объектом типа Date. Мы также будем использовать метод toDateString() для date.

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

Мы добавили аннотации типов к person и date, чтобы описать, какие типы значений можно передавать greet. Вы можете интерпретировать эту сигнатуру как «greet принимает person типа string и date типа Date».

Теперь TypeScript может сообщить нам об ошибках в вызовах greet. Например…

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

Что? TypeScript выдал ошибку для второго аргумента, но почему?

Возможно, неожиданно, вызов Date() в JavaScript возвращает string. С другой стороны, создание Date с помощью new Date() даёт нам ожидаемый результат.

В любом случае, мы можем быстро исправить ошибку:

function greet(person: string, date: Date) {
  console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
 
greet("Maddison", new Date());

Помните, что нам не всегда нужно писать явные аннотации типов. Во многих случаях TypeScript может вывести (или «определить») типы для нас, даже если мы их опустим.

let msg = "hello there!";

Несмотря на то, что мы не указали TypeScript тип msg как string, он смог это определить. Это функция, и лучше не добавлять аннотации, если система типов выведет тот же тип.

Примечание: сообщение в предыдущем примере кода отображается в вашем редакторе, если вы наводите курсор на слово.

Удаленные типы

Давайте посмотрим, что происходит, когда мы компилируем функцию greet с tsc в JavaScript:

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

Обратите внимание на два момента:

  1. У наших параметров person и date больше нет аннотаций типов.
  2. Наша «строка шаблона» — строка, использующая обратные кавычки (символ ` ) — была преобразована в обычные строки со склейкой.

Более подробно об этом втором моменте позже, но сейчас давайте сосредоточимся на первом. Аннотации типов не являются частью JavaScript (или ECMAScript, если быть педантичным), поэтому нет браузеров или других сред выполнения, которые могут запускать TypeScript без модификаций. Вот почему TypeScript нужен компилятор — ему нужно каким-то образом убрать или преобразовать любой код, специфичный для TypeScript, чтобы вы могли его запустить. Большая часть кода, специфичного для TypeScript, удаляется, и аналогично, здесь наши аннотации типов были полностью удалены.

Помните: Аннотации типов никогда не изменяют поведение вашей программы во время выполнения.

Низведение уровня

Ещё одно отличие от вышеприведённого примера заключалось в том, что наша строка шаблона была переписана из

`Hello ${person}, today is ${date.toDateString()}!`;

в

"Hello ".concat(person, ", today is ").concat(date.toDateString(), "!");

Почему это произошло?

Строки шаблонов — это функция из версии ECMAScript, называемой ECMAScript 2015 (также известная как ECMAScript 6, ES2015, ES6 и т. д. — не спрашивайте). TypeScript может переписывать код из более новых версий ECMAScript в более старые, такие как ECMAScript 3 или ECMAScript 5 (также известные как ES3 и ES5). Этот процесс перехода от более новой или «высшей» версии ECMAScript к более старой или «нижней» иногда называется низведением уровня.

По умолчанию TypeScript нацелен на ES3, очень старую версию ECMAScript. Мы могли бы выбрать что-то более современное, используя опцию target. Запуск с --target es2015 изменяет TypeScript так, что он нацелен на ECMAScript 2015, что означает, что код должен работать там, где поддерживается ECMAScript 2015. Таким образом, запуск tsc --target es2015 hello.ts даёт нам следующий результат:

function greet(person, date) {
  console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
greet("Maddison", new Date());

Хотя по умолчанию целевая версия — ES3, подавляющее большинство современных браузеров поддерживают ES2015. Поэтому большинство разработчиков могут безопасно указать ES2015 или выше в качестве целевой версии, если совместимость со старыми браузерами не является важной.

Строгость

Разные пользователи обращаются к TypeScript в поисках разных вещей в системе проверки типов. Некоторые ищут более гибкий, необязательный подход, который может помочь проверить только некоторые части программы, и при этом иметь приличные инструменты. Это поведение по умолчанию в TypeScript, где типы необязательны, вывод типов наиболее допускает, и нет проверок на потенциальные null/undefined значения. Так же, как tsc обрабатывает ошибки, эти настройки по умолчанию разработаны для того, чтобы не мешать вам. Если вы переходите с существующего JavaScript, это может быть желательным первым шагом.

Напротив, многие пользователи предпочитают, чтобы TypeScript проверял как можно больше сразу, и именно поэтому язык предоставляет настройки строгости. Эти настройки строгости превращают статическую проверку типов из переключателя (либо ваш код проверяется, либо нет) в нечто более похожее на регулятор. Чем дальше вы повернёте регулятор, тем больше TypeScript будет проверять за вас. Это может потребовать дополнительных усилий, но в целом это окупится в долгосрочной перспективе и позволит проводить более тщательные проверки и использовать более точные инструменты. Когда это возможно, любой новый проект должен включать эти проверки строгости.

TypeScript имеет несколько флагов строгости проверки типов, которые можно включить или выключить, и все наши примеры будут написаны с их включением, если не указано иное. Флаг strict в командной строке или "strict": true в файле tsconfig.json включает их все одновременно, но мы можем отключить их по отдельности. Две самые важные, которые вам нужно знать, это noImplicitAny и strictNullChecks.

noImplicitAny

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

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

strictNullChecks

По умолчанию значения, такие как null и undefined, могут быть присвоены любому типу. Это может упростить написание некоторого кода, но забывание обработки null и undefined является причиной бесчисленных ошибок в мире — некоторые считают это ошибкой в миллиарды долларов! Флаг strictNullChecks делает обработку null и undefined более явной и освобождает нас от беспокойства по поводу того, забыли ли мы обработать null и undefined.

© 2012-2023 Microsoft
Licensed under the Apache License, Version 2.0.
https://www.typescriptlang.org/docs/handbook/2/basic-types.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API