Пошаговое руководство по TypeScript для начинающих

TypeScript Изучение

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

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

Некоторый ошибочный код JavaScript

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

Однако в этом коде есть несколько опечаток, которые могут вызвать проблемы. Посмотрите, сможете ли вы их заметить:

const PEXELS_API_KEY = '...';

async function fetchImages(searchTerm, perPage) {
  const result = await fetch(`https://api.pexels.com/v1/search?query=${searchTerm}&per_page=${perPage}`, {
    headers: {
      Authorization: PEXELS_API_KEY,
    }
  });
  const data = await result.json();

  const imagesContainer = document.qerySelector('#images-container');
  for (const photo of data.photos) {
    const img = document.createElement('image');
    img.src = photo.src.medium;
    imagesContainer.append(img);
  }
}

fetchImages('dogs', 5);
fetchImages(5, 'cats');
fetchImages('puppies');

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

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

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

Запуск TypeScript из редактора

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

Если вы являетесь пользователем VS Code (не паникуйте, если это не так — мы свяжемся с вами!), Это сработает без дополнительных требований. Мы можем включить проверку TypeScript, добавив это в самый верх нашего файла JavaScript (важно, чтобы это была первая строка):

// @ts-check

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

После этого в вашем редакторе должно появиться несколько

Вы также должны увидеть крестик в нижнем левом углу с двойкой рядом с ним. При нажатии на нее будут обнаружены обнаруженные проблемы.

И то, что вы не используете VS Code

И то, что вы не используете VS Code, не означает, что вы не можете получить такой же опыт с ошибками выделения TypeScript. Большинство редакторов в наши дни поддерживают протокол языкового сервера (обычно называемый LSP), который VS Code использует для интеграции TypeScript.

Стоит поискать в Интернете свой редактор и рекомендуемые плагины для его настройки.

Установка и запуск TypeScript локально

Если вы не используете VS Code или хотите получить общее решение, вы также можете запустить TypeScript в командной строке. В этом разделе я покажу вам, как это сделать.

Сначала создадим новый проект. Этот шаг предполагает, что на вашем компьютере установлены Node и npm :

mkdir typescript-demo
cd typescript demo
npm init -y

Затем добавьте TypeScript в свой проект:

npm install --save-dev typescript

Примечание: вы можете установить TypeScript глобально на свой компьютер, но мне нравится устанавливать его для каждого проекта. Таким образом, я могу точно контролировать, какую версию TypeScript использует каждый проект. Это полезно, если у вас есть проект, к которому вы не прикасались какое-то время; вы можете продолжать использовать старую версию TS в этом проекте, в то время как в новом проекте используется более новая версия.

После его установки вы можете запустить компилятор TypeScript ( tsc), чтобы получить те же ошибки (не беспокойтесь об этих дополнительных флагах, мы скоро поговорим о них подробнее):

npx tsc index.js --allowJs --noEmit --target es2015
index.js:13:36 - error TS2551: Property 'qerySelector' does not exist on type 'Document'. Did you mean 'querySelector'?

13   const imagesContainer = document.qerySelector('#images-container');
                                      ~~~~~~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:11261:5
    11261     querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;
              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    'querySelector' is declared here.

index.js:16:9 - error TS2339: Property 'src' does not exist on type 'HTMLElement'.

16     img.src = photo.src.medium;
           ~~~

Found 2 errors.

Вы можете видеть, что TypeScript в командной строке выделяет те же ошибки кода JavaScript, что и VS Code на скриншоте выше.

Исправление ошибок в нашем коде JavaScript

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

Давайте посмотрим на нашу первую ошибку.

Свойство qerySelectorне существует по типуDocument

index.js:13:36 - error TS2551: Property 'qerySelector' does not exist on type 'Document'. Did you mean 'querySelector'?

13   const imagesContainer = document.qerySelector('#images-container');

  node_modules/typescript/lib/lib.dom.d.ts:11261:5
    11261     querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;
              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    'querySelector' is declared here.

Это может выглядеть довольно ошеломляющим, если вы не привыкли читать ошибки TypeScript, поэтому не паникуйте, если это покажется немного странным! TypeScript обнаружил, что в сети 13мы вызвали метод document.qerySelector. Мы имели в виду, document.querySelectorно ошиблись при наборе. Мы бы узнали об этом, когда попытались запустить наш код в браузере, но TypeScript может сообщить нам об этом раньше.

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

Давайте теперь увеличим масштаб последней части сообщения об ошибке выше:

index.js:13:36 - error TS2551: Property 'qerySelector' does not exist on type 'Document'. Did you mean 'querySelector'?

Конкретно я хочу посмотреть на текст did not exist on type ’Document’. В TypeScript (и вообще во всех типизированных языках) элементы имеют так называемый type.

В TypeScript числа вроде 1или 2.5имеют тип number, строки вроде «hello world»имеют тип string, а экземпляр элемента HTML имеет тип HTMLElement. Это то, что позволяет компилятору TypeScript проверять правильность нашего кода. Как только он узнает тип чего-то, он знает, какие функции вы можете вызвать, чтобы взять это что-то, или какие методы для этого существуют.

Примечание: если вы хотите узнать больше о типах данных, обратитесь к » Введение в типы данных: статические, динамические, сильные и слабые «.

В нашем коде TypeScript видел то, о чем мы говорили document. Это глобальная переменная в браузере, и TypeScript знает об этом и знает, что он имеет тип Document. Этот тип документирует (простите за каламбур!) Все методы, которые мы можем вызывать. Вот почему TypeScript знает, что querySelectorэто метод, а орфографическая ошибка qerySelector- нет.

Читайте также:  10 лучших инструментов, которые должен попробовать каждый веб-разработчик

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

Теперь обратим внимание на нашу следующую ошибку, которая немного менее очевидна.

Свойство src не существует по типу HTMLElement

index.js:16:9 - error TS2339: Property 'src' does not exist on type 'HTMLElement'.

16     img.src = photo.src.medium;

Это одна из тех ошибок, при которых иногда приходится смотреть немного выше ошибки, чтобы найти проблему. Мы знаем, что у элемента изображения HTML есть srcатрибут, так почему его нет в TypeScript?

const img = document.createElement('image');
img.src = photo.src.medium;

Ошибка здесь в первой строке: когда вы создаете новый элемент изображения, вы должны вызвать document.createElement(’img’)(потому что тег HTML есть , а не ). Как только мы это сделаем, ошибка исчезнет, ​​потому что TypeScript знает, что при вызове document.createElement(’img’)вы возвращаете элемент, у которого есть srcсвойство. И все это зависит от типов.

Когда вы вызываете document.createElement(’div’), возвращаемый объект имеет тип HTMLDivElement. Когда вы вызываете document.createElement(’img’), возвращаемый объект имеет тип HTMLImageElement. HTMLImageElementв нем srcобъявлено свойство, поэтому TypeScript знает, что вы можете вызывать img.src. Но HTMLDivElementэтого не происходит, поэтому TypeScript выдаст ошибку.

В случае document.createElement(’image’), поскольку TypeScript не знает ни о каком элементе HTML с тегом image, он вернет объект типа HTMLElement(общий элемент HTML, не относящийся к одному тегу), у которого также отсутствует srcсвойство.

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

Как настроить TypeScript

Немного больно добавлять // @ts-checkв каждый файл, и когда мы запускаем команду в терминале, нужно добавлять эти дополнительные флаги. TypeScript позволяет вместо этого включить его в проекте JavaScript, создав jsconfig.jsonфайл.

Создайте jsconfig.jsonв корневом каталоге нашего проекта и поместите в него:

{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "target": "es2015"
  },
  "include": ["*.js"]
}

Это настраивает компилятор TypeScript (и интеграцию TS вашего редактора) на:

  1. Проверить файлы JavaScript ( checkJsопция).
  2. Предположим, мы строим в среде ES2015 ( targetвариант). Значение по умолчанию ES2015 означает, что мы можем использовать такие вещи, как обещания, без ошибок TypeScript.
  3. Не выводить скомпилированные файлы ( noEmitопция). Когда вы пишете код TypeScript в исходных файлах TypeScript, вам нужен компилятор, чтобы сгенерировать код JavaScript для запуска в браузере. Поскольку мы пишем код JavaScript, работающий в браузере, нам не нужен компилятор для генерации файлов за нас.
  4. Наконец, include: [«*.js«]указывает TypeScript на просмотр любого файла JavaScript в корневом каталоге.

Теперь, когда у нас есть этот файл, вы можете обновить свою инструкцию командной строки следующим образом:

npx tsc -p jsconfig.json

Это запустит компилятор с нашим файлом конфигурации ( -pздесь сокращение от «проект»), поэтому вам больше не нужно передавать все эти флаги при запуске TypeScript.

Работа в строгом режиме

Теперь мы здесь, давайте посмотрим, как мы можем сделать TypeScript еще более тщательным при проверке нашего кода. TypeScript поддерживает так называемый «строгий режим», который инструктирует TypeScript более тщательно проверять наш код и гарантировать, что мы будем иметь дело с любыми потенциальными моментами, когда, например, может находиться объект undefined. Чтобы было понятнее, давайте включим его и посмотрим, какие ошибки мы получим. Добавьте «strict»: trueв «compilerOptions«часть jsconfig.json, а затем повторно запустите TypeScript в командной строке.

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

npx tsc -p jsconfig.json
index.js:3:28 - error TS7006: Parameter 'searchTerm' implicitly has an 'any' type.

3 async function fetchImages(searchTerm, perPage) {
                             ~~~~~~~~~~

index.js:3:40 - error TS7006: Parameter 'perPage' implicitly has an 'any' type.

3 async function fetchImages(searchTerm, perPage) {
                                         ~~~~~~~

index.js:15:5 - error TS2531: Object is possibly 'null'.

15     imagesContainer.append(img);
       ~~~~~~~~~~~~~~~

Found 3 errors.

Начнем сначала с последней ошибки и вернемся к остальным:

index.js:15:5 - error TS2531: Object is possibly 'null'.

15     imagesContainer.append(img);
       ~~~~~~~~~~~~~~~

И давайте посмотрим, как imagesContainerопределяется:

const imagesContainer = document.querySelector('#images-container');

Включение strictрежима сделало TypeScript более строгим в обеспечении существования ожидаемых значений. В этом случае не гарантируется, что document.querySelector(’#images-container’)действительно будет возвращен элемент; что, если его не нашли? document.querySelectorвернется, nullесли элемент не найден, и теперь мы включили строгий режим, TypeScript сообщает нам, что imagesContainerэто действительно может быть null.

Типы союзов

До включения строгого режима, типа imagesContainerбыл Element, но теперь мы стали на строгом режиме типа imagesContainerявляется Element | null. Оператор |(вертикальная черта) создает типы объединения, которые можно читать как «или», так что здесь imagesContainerтип Elementили null. Когда TypeScript говорит нам Object is possibly ’null’, это именно то, что он говорит нам, и он хочет, чтобы мы убедились, что объект действительно существует, прежде чем мы его используем.

Давайте исправим это, выдав ошибку, если мы не найдем элемент контейнера изображений:

const imagesContainer = document.querySelector('#images-container');
if (imagesContainer === null) {
  throw new Error('Could not find images-container element.')
}

for (const photo of data.photos) {
  const img = document.createElement('img');
  img.src = photo.src.medium;
  imagesContainer.append(img);
}

TypeScript теперь счастлив; мы разобрались с этим nullслучаем, выдав ошибку. TypeScript достаточно умен, чтобы понять, что, если наш код не выдаст ошибку в третьей строке в приведенном выше фрагменте, imagesContainerэто не так null, и, следовательно, он должен существовать и иметь тип Element.

Его тип был Element | null, но если бы это было так, nullмы бы выдали ошибку, так что теперь это должно быть Element. Эта функция известна как сужение типов и является очень полезной концепцией, о которой следует знать.

Implicit any

Теперь обратим внимание на оставшиеся две ошибки:

index.js:3:28 - error TS7006: Parameter 'searchTerm' implicitly has an 'any' type.

3 async function fetchImages(searchTerm, perPage) {
                             ~~~~~~~~~~

index.js:3:40 - error TS7006: Parameter 'perPage' implicitly has an 'any' type.

3 async function fetchImages(searchTerm, perPage) {

Одним из следствий включения строгого режима является включение вызываемого правила noImplicitAny. По умолчанию, когда TypeScript не знает тип чего-либо, он по умолчанию присваивает ему специальный тип TypeScript с именем any. any- не лучший тип для вашего кода, потому что с ним не связаны никакие правила с точки зрения того, что будет проверять компилятор. Это позволит случиться чему угодно.

Читайте также:  Создание динамических URL-адресов в Flask

Мне нравится представлять это как компилятор, поднимающий руки вверх и говорящий: «Я не могу вам здесь помочь!» Использование anyотключает любую полезную проверку типов для этой конкретной переменной, поэтому я настоятельно рекомендую избегать ее.

Опишите подпись функции с помощью JSDoc

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

Например, вот как мы можем предоставить информацию о типе нашей fetchImagesфункции:

/**
 * @param {string} searchTerm
 * @param {number} perPage
 *
 * @return void
 */
async function fetchImages(searchTerm, perPage) {
  // function body here
}

Все комментарии JSDoc должны начинаться с /**(обратите внимание на дополнительную информацию *в начале), и внутри них мы используем специальные теги, начиная с @, для обозначения свойств типа. Здесь мы объявляем два параметра ( @param), а затем заключаем их тип в фигурные скобки (как обычные объекты JavaScript).

Здесь мы проясняем, что searchTermэто stringи perPage- это число. Пока мы на этом, мы также используем, @returnчтобы объявить, что возвращает эта функция. В нашем случае он ничего не возвращает, и тип, который мы используем в TypeScript для объявления этого, есть void.

Давайте теперь перезапустим компилятор и посмотрим, что он говорит:

npx tsc -p jsconfig.json
index.js:30:13 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

30 fetchImages(5, 'cats')
               ~

index.js:31:1 - error TS2554: Expected 2 arguments, but got 1.

31 fetchImages('puppies')
   ~~~~~~~~~~~~~~~~~~~~~~

  index.js:9:40
    9 async function fetchImages(searchTerm, perPage) {
                                             ~~~~~~~
    An argument for 'perPage' was not provided.

Found 2 errors.

В этом вся прелесть TypeScript. Предоставляя компилятору дополнительную информацию, теперь он может обнаруживать ошибки в том, как мы вызываем код, которые он не мог раньше. В этом случае, он нашел два вызова, fetchImagesгде у нас есть аргументы в неправильном порядке, и второй, где мы забыли perPageаргумент (ни searchTerm, perPageне являются обязательными параметрами).

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

Объявление типов данных с помощью интерфейса

Несмотря на то, что компилятор не пометил это, у нашего кода все еще есть одна проблема:

const data = await result.json();

Проблема здесь в том, что возвращаемый тип await result.json()- any. Это связано с тем, что, когда вы берете ответ API и конвертируете его в JSON, TypeScript не знает, какие данные там находятся, поэтому по умолчанию используется any. Но поскольку мы знаем, что возвращает API Pexels, мы можем предоставить ему некоторую информацию о типе, используя интерфейсы TypeScript. Они позволяют нам сообщать TypeScript о форме объекта: какие свойства он имеет и какие значения имеют эти свойства.

Давайте объявим интерфейс — опять же, используя синтаксис JSDoc, который представляет данные, возвращаемые API Pexels. Я использовал справочник API Pexels, чтобы выяснить, какие данные возвращаются. В этом случае мы фактически определим два интерфейса: один будет декларировать форму одного photo, возвращаемого API Pexels, а другой — общую форму ответа от API.

Чтобы определить эти интерфейсы с помощью JSDoc, мы используем @typedef, что позволяет нам объявлять более сложные типы. Затем мы используем @propertyдля объявления отдельных свойств в этом интерфейсе. Например, вот тип, который я создаю для человека Photo. Типы всегда должны начинаться с заглавной буквы.

Если вы хотите увидеть полную ссылку на все поддерживаемые функции JSDoc, на сайте TypeScript есть подробный список с примерами.

/**
 * @typedef {Object} Photo
 * @property {{medium: string, large: string, thumbnail: string}} src
 */

Этот тип говорит, что любой объект набраны как Photoбудет иметь одно свойство, src, которое само по себе является объектом с тремя строковыми свойствами: medium, largeи thumbnail. Вы заметите, что API Pexels возвращает больше; вам не нужно объявлять каждое свойство объекта, если вы этого не хотите, а только то, что вам нужно. Здесь наше приложение в настоящее время использует только mediumизображение, но я объявил несколько дополнительных размеров, которые нам могут понадобиться в будущем.

Теперь, когда у нас есть этот тип, мы можем объявить тип PexelsSearchResponse, который будет представлять то, что мы получаем от API:

/**
 * @typedef {Object} PexelsSearchResponse
 * @property {Array<Photo>} photos
 */

Здесь вы можете увидеть ценность объявления ваших собственных типов; мы объявляем, что этот объект имеет одно свойство, photosа затем объявляем, что его значение является массивом, где каждый элемент имеет тип Photo. Это то, что Arrayобозначает синтаксис: это массив, в котором каждый элемент в массиве имеет тип X. [1, 2, 3]будет Array, например.

Как только мы это сделаем, мы можем использовать @typeкомментарий JSDoc, чтобы сообщить TypeScript, что данные, которые мы получаем, result.json()имеют тип PexelsSearchResponse:

/** @type {PexelsSearchResponse} */
const data = await result.json();

@typeэто не то, к чему нужно стремиться все время. Обычно вы хотите, чтобы компилятор разумно определял тип вещей, а не прямо говорил об этом. Но поскольку result.json()возвращается any, мы можем переопределить это с помощью нашего типа.

Test if everything is working

Чтобы доказать, что это работает, я намеренно сделал ошибку mediumпри ссылке на URL-адрес фотографии:

for (const photo of data.photos) {
  const img = document.createElement('img');
  img.src = photo.src.mediun; // typo!
  imagesContainer.append(img);
}

Если мы снова запустим TypeScript, мы увидим проблему, которую TypeScript не заметил бы, если бы мы не проделали ту работу, которую только что проделали, чтобы объявить интерфейс:

index.js:35:25 - error TS2551: Property 'mediun' does not exist on type '{ medium: string; large: string; thumbnail: string; }'. Did you mean 'medium'?

35     img.src = photo.src.mediun;
                           ~~~~~~

  index.js:18:18
    18    * @property {{medium: string, large: string, thumbnail: string}} src
                        ~~~~~~
    'medium' is declared here.

Found 1 error.

Заключение

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

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

Оцените статью
bestprogrammer.ru
Добавить комментарий