Русский
Русский
English
Статистика
Реклама

Перевод Практическое руководство по TypeScript для разработчиков


Представляю вашему вниманию перевод статьи "Working With TypeScript: A Practical Guide for Developers".


Что такое TypeScript?


TypeScript это популярный статический типизатор (static type checker) или типизированное надмножество (typed superset) для JavaScript, инструмент, разработанный Microsoft и добавляющий систему типов к гибкости и динамическим возможностям JavaScript.


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


Установка TypeScript


Для того, чтобы начать работу с TypeScript, нужно либо установить специальный интерфейс командной строки (command line interface, CLI), либо воспользоваться официальной онлайн-песочницей или другим похожим инструментом.


Для выполнения кода мы будем использовать Node.js. Устанавливаем его, если он еще не установлен на вашей машине, инициализируем новый Node.js-проект и устанавливаем транспилятор TypeScript:


# Создаем новую директорию для проектаmkdir typescript-intro# Делаем созданную директорию текущейcd typescript-intro# Инициализируем Node.js-проектnpm init -y# Устанавливаем компилятор TypeScriptnpm i typescript

Это установит tsc (компилятор TypeScript) для текущего проекта. Для того, чтобы проверить установку, в директории проекта создаем файл index.ts следующего содержания:


console.log(1)

Затем используем транспилятор для преобразования кода, содержащегося в этом файле, в JavaScript:


# Преобразуем index.ts в index.jsnpx tsc index.ts

Наконец, выполняем скомпилированный код с помощью команды node:


# Вы должны увидеть `1` в терминалеnode index.js

В данном случае транспилятор не делает ничего, кроме копирования кода из одного файла в другой, но это позволяет убедиться, что все установлено и работает правильно.


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


Определение TypeScript-проекта


Для определения TypeScript-проекта внутри Node.js-проекта, необходимо создать файл tsconfig.json. Присутствие данного файла в директории свидетельствует о том, что мы имеем дело с TypeScript-проектом.


tsconfig.json содержит определенное количество настроек, которые влияют на поведение транспилятора, например, на то, какие файлы следует игнорировать, какой файл является целью компиляции, какие типы импортируются и т.д.


Вы легко можете настроить TypeScript с помощью следующей команды:


# Создаем стандартный tsconfig.jsonnpx tsc --init

Сгенерированный tsconfig.json содержит почти все возможные настройки с кратким описанием каждой из них. К счастью, данный файл содержит хорошие настройки по умолчанию, так что вы можете удалить большую часть закомментированных опций.


Мы еще вернемся к настройкам TypeScript, а сейчас давайте писать код.


Возможности TypeScript


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


Основы типизации


Ключевая идея TypeScript заключается в контроле за динамической природой и гибкостью JavaScript с помощью типов. Давайте рассмотрим эту идею на практике.


В директории проекта создаем файл test.js следующего содержания:


function addOne(age) { return age + 1}const age = 'thirty two'console.log(addOne(age))

Выполняем данный код:


node test.js

  1. Что мы увидим в терминале?
  2. Как вы думаете, правильным ли будет вывод?

В терминале мы увидим thirty two1 без каких-либо предупреждений об очевидной некорректности вывода. Ничего нового: обычное поведение JavaScript.


Но что если мы хотим обеспечить, чтобы функция addOne() принимала только числа? Вы можете добавить в код проверку типа переданного значения с помощью оператора typeof или же вы можете использовать TypeScript, который привнесет в процесс компиляции кода некоторые ограничения.


Заменим содержимое созданного нами ранее index.ts следующим кодом:


function addOne(age: number): number { return age + 1}console.log(addOne(32))console.log(addOne('thirty two'))

Обратите внимание, что мы ограничили принимаемый функцией аргумент и возвращаемое функцией значение типом number.


Преобразуем файл:


npx tsc index.ts

Попытка преобразования проваливается:


index.ts:6:20 - error TS2345: Argument of type 'string' is notassignable to parameter of type 'number'. Аргумент типа 'строка' не может быть присвоен параметру с типом 'число'.

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


string и number это лишь два из основных типов, поддерживаемых TypeScript. TypeScript поддерживает все примитивные значения JavaScript, включая boolean и symbol.


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


  • enum ограниченный набор значений
  • any указывает на то, что переменная/параметр могут быть чем угодно, что, по сути, нивелирует типизацию
  • unknown типобезопасная альтернатива any
  • void указывает на то, что функция ничего не возвращает
  • never указывает на то, что функция выбрасывает исключение или на то, что ее выполнение никогда не заканчивается
  • литеральные типы, конкретизирующие типы number, string или boolean. Это означает, например, что 'Hello World' это string, но string это не 'Hello World' в контексте системы типов. Тоже самое справедливо в отношении false в случае с логическими значениями или для 3 в случае с числами:

// Данная функция принимает не любое число, а только 3 или 4declare function processNumber(s: 3 | 4)declare function processAnyNumber(n: number)const n: number = 10const n2: 3 = 3processNumber(n) // Ошибка: `number` - это не `3 | 4`processAnyNumber(n2) // Работает. 3 - это `number`

Множества


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


Карты (maps)


Карты, как правило, используются для определения связи между ключами и значениями для представления специфичных для приложения данных:


// Создаем ассоциативный типtype User = { id: number username: string name: string}// Создаем объект `user`, соответствующий ассоциативному типуconst user: User = { id: 1, username: 'Superman', name: 'Clark Kent',}

Векторы (vectors)


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


// Создаем ассоциативный типtype User = { id: number username: string name: string}// Создаем несколько объектов `user`, соответствующих ассоциативному типуconst user1: User = { id: 1, username: 'Superman', name: 'Clark Kent',}const user2: User = { id: 2, username: 'WonderWoman', name: 'Diana Prince',}const user3: User = { id: 3, username: 'Spiderman', name: 'Peter Parker',}// Создаем вектор пользователейconst userVector: User[] = [user1, user2, user3]

Кортежи (tuples)


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


// Создаем ассоциативный типtype User = { id: number username: string name: string}// Создаем объект `user`, соответствующий ассоциативному типуconst user1: User = { id: 1, username: 'Superman', name: 'Clark Kent',}// Создаем кортежconst userTuple: [User, number] = [user1, 10]

Объединения (unions)


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


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


Прежде всего, давайте установим node-fetch, чтобы иметь возможность использовать функцию fetch в Node.js:


npm i node-fetch @types/node-fetch

Затем с помощью typeof осуществляем разделение типов:


type User = { id: number username: string name: string email: string}async function fetchFromEmail(email: string) { const res = await fetch('https://jsonplaceholder.typicode.com/users') const parsed: User[] = await res.json() const user = parsed.find((u: User) => u.email === email) if (user) {   return fetchFromId(user.id) } return undefined}function fetchFromId(id: number) { return fetch(`https://jsonplaceholder.typicode.com/users/${id}`)   .then((res) => res.json())   .then((user) => user.address)}function getUserAddress(user: User | string) { if (typeof user === 'string') {   return fetchFromEmail(user) } return fetchFromId(user.id)}getUserAddress('Rey.Padberg@karina.biz').then(console.log).catch(console.error)

Здесь мы в явном виде реализовали предохранитель типов.


К слову, кортежи и объединения можно использовать совместно:


const userTuple: Array<User | number> = [u, 10, 20, u, 30]// Любой элемент может быть либо `User`, либо `number`

Можно определять размер и тип каждого элемента массива:


const userTuple: [User, number] = [u, 10, 20, u, 30]// Ошибка: массив должен состоять из двух элементов с типами `User` и `number`const anotherUserTuple: [User, number] = [u, 10] // Все верно

Предохранители типов (type guards)


Предохранители типов это выражения, выполняющие проверки во время выполнения кода, результат которых может быть использован системой типов для сужения (narrow) области (scope) проверяемого аргумента.


Одним из предохранителей является оператор typeof, который мы использовали в предыдущем примере для сужения области аргумента user.


Существуют и другие предохранители, такие как instanceof, !== и in, полный список можно найти в документации.


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


// Определяем предохранитель для `user`function isUser(u: unknown): u is User { if (u && typeof u === 'object') {   return 'username' in u && 'currentToken' in u } return false}function getUserAddress(user: User | string) { if (isUser(user)) {   return fetchFromEmail(user) } return fetchFromId(user.id)}

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


Весьма распространенным случаем использования пользовательских предохранителей является влидация внешних данных с помощью JSON-схемы, предоставляемой сторонней библиотекой, такой как Ajv. Обычно, это происходит в веб-приложениях, где тело запроса имеет тип unknown (или any в зависимости от используемого фреймворка), и мы хотим проверить его перед использованием:


import Ajv from 'ajv'const ajv = new Ajv()const validate = ajv.compile({ type: 'object', properties: {   username: { type: 'string' },   currentToken: { type: 'string' }, },})function validateUser(data: unknown): data is User { return validate(data)}

В основе данного механизма лежит синхронизация JSON-схемы с типом. Если мы изменим тип, но не изменим схему, то вполне можем получить неожиданное сужение типа.


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


Исключающие объединения (discriminated unions)


Объединения с общим литеральным полем называются исключающими. При работе с такими типами TypeScript предоставляет неявный предохраитель, позволяя избежать его создания в явном виде:


type Member = { type: 'member' currentProject: string}type Admin = { type: 'admin' projects: string[]}type User = Member | Adminfunction getFirstProject(u: User) { if (u.type === 'member') {   return u.currentProject } return u.projects[0]}

В функции getFirstProject() TypeScript сужает область аргумента без помощи предиката. Попытка получить доступ к массиву projects в первой ветке (блоке if) закончится ошибкой типа.


Валидация во время выполнения


Как было отмечено ранее, в случае с пользовательскими предохранителями, корректность возвращаемого результата обеспечивается разработчиком.


При наличии ошибки в предикате, система типов может получить неверную информацию. Рассмотрим пример:


function validateUser(data: unknown): data is User { return true}

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


const invalidUser = undefinedif (validateUser(invalidUser)) { // Предыдущая инструкция всегда возвращает `true` console.log(invalidUser.name) // Ошибка, возникающая во время выполнения}

Существует несколько библиотек, которые позволяют обеспечить автоматическую синхронизацию между валидацией во время выполнения и соответствующим типом. Одним из самых популярных решений является runtypes, однако мы будем использовать io-ts и fp-ts.


Суть данного подхода состоит в том, что мы определяем форму (или фигуру) типа с помощью примитивов, предоставляемых io-ts; эта форма называется декодером (decoder); мы используем ее для проверки данных, которым мы по какой-либо причине не доверяем:


import * as D from 'io-ts/Decoder';import * as E from 'io-ts/Either';import { pipe } from 'fp-ts/function';// Определяем декодер, представляющий `user`const UserDecoder = D.type({   id: D.number,   username: D.string,   name: D.string   email: D.string});// и используем его в отношении потенциально опасных данныхpipe(   UserDecoder.decode(data),   E.fold(       error => console.log(D.draw(error)),       decodedData => {           // типом `decodedData` является `User`           console.log(decodedData.username)       }   ));

Настройка TypeScript


Поведение транспилятора можно настраивать с помощью файла tsconfig.json, находящегося в корне проекта.


Данный файл содержит набор ключей и значений, отвечающих за 3 вещи:


  1. Структура проекта: какие файлы включаются/исключаются из процесса компиляции, зависимости разных TypeScript-проектов, связь между этими проектами через синонимы (aliases).
  2. Поведение типизатора: выполнять ли проверку на наличие null и undefined в кодовой базе, сохранение const enums и т.п.
  3. Процесс транспиляции.

Пресеты TSConfig


TypeScript может преобразовывать код в ES3 и поддерживает несколько форматов модулей (CommonJS, SystemJS и др.).


Точные настройки зависят от среды выполнения кода. Например, если вашей целью является Node.js 10, вы можете транспилировать код в ES2015 и использовать CommonJS в качестве стратегии разрешения модулей.


Если вы используете последнюю версию Node.js, например, 14 или 15, тогда можете указать в качестве цели ESNext или ES2020 и использовать модульную стратегию ESNext.


Наконец, если вашей целью является браузер и вы не используете сборщик модулей, такой как webpack или parcel, то можете использовать UMD.


К счастью, команда TypeScript разработала хороший набор пресетов, которые вы можете просто импортировать в свой tsconfig.json:


{ "extends": "@tsconfig/node12/tsconfig.json", "include": ["src"]}

Среди наиболее важных настроек, можно отметить следующее:


  • declaration: определяет, должен ли TypeScript генерировать файлы определений (.d.ts) во время транспиляции. Данные файлы, как правило, используются при разработке библиотек
  • noEmitOnError: определяет, должен ли TypeScript прерывать процесс компиляции при возникновении ошибок, связанных с неправильными типами. Рекомендуемым значением данной нстройки является true
  • removeComments: true
  • suppressImplicitAnyIndexErrors: true
  • strict: дополнительные проверки. До тех пор, пока у вас не появится веской причины для отключения данной настройки, она должна иметь значение true
  • noEmitHelpers: при необходимости, TypeScript предоставляет утилиты и полифилы для поддержки возможностей, которых не было в ES3 и ES5. Если значение данной настройки является false, утилиты будут помещены в начало кода, в противном случае, они будут опущены (tslib можно устанавливать отдельно)

Заключение


Надеюсь, данная статья позволила вам получить общее предствления о возможностях, предоставляемых TypeScript, а также о том, почему использование TypeScript в дополнение к JavaScript в настоящее время фактически является стандартом веб-разработки.


Система типов TypeScript не является идеальной, но это лучшее, что мы имеет на сегодняшний день.




Облачные серверы от Маклауд отлично подходят для сайтов с JavaScript.


Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!


Источник: habr.com
К списку статей
Опубликовано: 18.05.2021 14:13:42
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании маклауд

Разработка веб-сайтов

Javascript

Typescript

Разработка сайтов

Vds

Vps

Быстрый vds

Надежный vds

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru