Создание сайтов в Артемовске, ДНР. Пошаговое руководство по 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 строках кода легко обнаружить ошибки, но что, если вы работаете с кодовой базой, состоящей из тысяч строк? Тогда вы легко обнаружите потенциальные проблемы?

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

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

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

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

// @ts-check

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

TypeScript показывает ошибки в VS Code

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

Ошибки, отображаемые в консоли 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 (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 (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— нет.

Мы увидим больше этих типов по мере изучения следующих руководств по 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 вашего редактора) на:

Проверить файлы JavaScript (checkJsопция).

Предположим, мы строим в среде ES2015 (targetвариант). По умолчанию ES2015 означает, что мы можем использовать такие вещи, как обещания, без ошибок TypeScript.

Не выводить скомпилированные файлы (noEmitопция). Когда вы пишете код TypeScript в исходных файлах TypeScript, вам нужен компилятор для создания кода JavaScript для запуска в браузере. Поскольку мы пишем код JavaScript, который выполняется в браузере, нам не нужен компилятор для создания каких-либо файлов.

Наконец, 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. Оператор | (pipe) создает типы объединения, которые вы можете прочитать как «или», так что здесь imagesContainerтип Elementor 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. Эта функциональность известна как сужение типа и является очень полезной концепцией, о которой следует знать.

Неявный любой

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

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не лучший тип для использования в вашем коде, потому что с ним не связано никаких правил в плане того, что будет проверять компилятор. Это позволит всему случиться.

Мне нравится представлять, как компилятор вскидывает руки вверх и говорит: «Здесь я ничем не могу вам помочь!» Использование 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. Я использовал справочник Pexels API, чтобы выяснить, какие данные возвращаются. В этом случае мы фактически определим два интерфейса: один будет объявлять форму одного photoсообщения, которое возвращает API Pexels, а другой будет объявлять общую форму ответа от API.

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

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

/**

* @typedef {Object} Photo

* @property {{medium: string, large: string, thumbnail: string}} src

*/

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

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

/**

* @typedef {Object} PexelsSearchResponse

* @property {Array} 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, мы можем переопределить это с помощью нашего типа.

Проверьте, все ли работает

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

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, позволяя пользователям искать фотографии или видео и помечать их как избранные, а также сохранять эти избранные в локальном хранилище.

Делитесь нашими материалами с друзьями!

 

 

Заказать разработку сайта