[Перевод] Новые возможности TypeScript, повышающие удобство разработки

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

Одна из наиболее заметных сильных сторон TypeScript — это поддержка некоторых из новейших возможностей, описанных в спецификации ECMAScript. Когда разработчик обновляется до новой версии TypeScript, это означает, что в его распоряжении оказываются и новые возможности JavaScript. Причём, использование этих возможностей не означает потенциальных проблем с совместимостью. TypeScript, помимо внедрения новейших возможностей JavaScript, заметен ещё и тем, что создатели языка постоянно представляют сообществу TS-программистов что-то новое, призванное повысить удобство работы. Сюда входят, например, вспомогательные инструменты для рефакторинга кода, средства для переименования сущностей и для поиска мест, где они используются в программах.

ikdcio3bkdkuvgg4h-lplk9lvpm.jpeg

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

Иммутабельные объекты и массивы


Для того чтобы во время компиляции сделать иммутабельными массивы, используемые в виде обычных переменных и параметров функций, в TypeScript можно воспользоваться вспомогательными типами Readonly и ReadonlyArray. Однако их использование может вызвать ощущение неоднородности в аннотировании типов, особенно при объявлении массивов с использованием символов [] после указания типа. В TypeScript 3.4 появился новый способ маркировки параметров, которые являются массивами, предназначенными только для чтения. Тут же появился и новый способ объявления переменных, которые должны быть иммутабельными.

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


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

function foo(s: ReadonlyArray) { /* ... */ }
 
function foo(s: readonly string[]) { /* ... */ }


В обоих случаях любая попытка модификации массива (например — с использованием его метода push) приведёт к появлению ошибки. Это новшество позволяет устранить необходимость в использовании вспомогательного типа-дженерика, что означает появление кода, который легче читать. Объектные типы тоже можно маркировать как сущности, предназначенные только для чтения, но они всё ещё нуждаются в использовании вспомогательного типа Readonly.

Повышение удобства работы с иммутабельными переменными с использованием конструкции as const


Тип переменной, объявленной с использованием ключевого слова const, не может быть изменён. Эта концепция существует в JavaScript. Она принята и в TypeScript ради организации более строгой работы с типами. Но при работе с объектными типами данных, с такими, как объекты или массивы, оказывается, что такие структуры не являются по-настоящему иммутабельными. Использование ключевого слова const означает, что конкретный экземпляр объекта или массива при работе с константой будет оставаться неизменным, однако содержимое этого объекта или массива может быть легко изменено. Например, без нарушения правил работы с const-сущностями, в массив можно добавлять новые значения с помощью метода push, можно менять значения свойств объектов.

Используя Readonly и ReadonlyArray можно указать TypeScript на то, что система должна рассматривать объектные сущности так, как будто они являются по-настоящему иммутабельными. Это значит, что каждый раз, когда в коде будет делаться попытка изменения такой сущности, будет выдаваться сообщение об ошибке.

interface Person { 
  name: string; 
}
 
const person = { 
  name: 'Will' 
} as Readonly;
person.name = 'Diana'; // ошибка!


В TypeScript 3.4, кроме прочих новшеств, появилось и понятие const assertion (константное утверждение), предусматривающее использование конструкции as const. Это — упрощённый метод объявления констант, содержащих иммутабельные объекты и массивы. Такие объявления строятся с помощью добавления as const в конец объявления константы. У этого метода есть и дополнительное преимущество, которое заключается в том, что при его использовании не нужно явным образом указывать тип в утверждении as const.

const person = { 
        name: 'Will' 
} as const;
 
person.name = 'Diana'; // ошибка!
 
// С массивами тоже можно использовать as const
const array = [1, 2, 3] as const; 
array.push(4); // ошибка!


Вспомогательный тип Omit


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

Вспомогательный тип Partial позволяет пометить все свойства объекта как необязательные. До выхода TypeScript 3.5, как оказалось, я постоянно применял в своих проектах один интересный механизм. Это — то же самое, чего сейчас позволяет достичь использование вспомогательного типа Omit. Данный тип, как следует из его имени, позволяет исключать что-то из других типов. Omit принимает тип и объединение ключей, после чего возвращает новый тип, из которого исключены свойства, описанные ключами. Прошли те дни, когда мне приходилось пользоваться Pick и Exclude для самостоятельной реализации функционала Omit.

// Теперь это есть в TypeScript 3.5
type Omit = Pick>;
 
interface A { 
    propA?: string; 
    propB?: string; 
    propC?: string; 
}
 
type B = Omit; 
const b: B = { propA: 'hi' }; // ошибка;


Новые возможности JavaScript, поддерживаемые TypeScript


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

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

▍Проверка на null и undefined


JavaScript-разработчики знакомы с концепцией истинности и ложности значений. При проверке на истинность можно выделить 6 значений, которые всегда являются ложными: 0, null, undefined, «», NaN, и, конечно, false. Чаще всего разработчику просто нужно узнать, является ли значение истинным или ложным, но в некоторых случаях нужно лишь узнать о том, является ли исследуемое значение настоящим значением null или undefined. Например, в том случае, если в коде нужно различать 0 и undefined:

// использование || не поможет в том случае, если index равно 0
const getValueOrOne = (x?: number) => index || 1; 
getValueOrOne(0); // 1 <-- проблематично


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

// этот код работает, но устроен он сложнее
const getValueOrOne = (x?: number) => index !== null && index !== undefined ? : 1; 
getValueOrOne(0); // 0


Теперь код работает, но он требует использования более сложных проверок. Новый оператор для проверки значения на null и undefined (он выглядит как два вопросительных знака — ??) упрощает подобные проверки, возвращая значение, находящееся в его левой части, в том случае, если оно не равно null и undefined. В противном случае он возвращает то, что находится в его правой части.

// Работает!
const getValueOrOne = (x?: number) => index ?? 1; 
getValueOrOne(0); // 0
getValueOrOne(2); // 2
getValueOrOne(); // 1


▍Опциональные последовательности


Ещё одна новая возможность JavaScript, доступная в TypeScript 3.7 — это оператор для организации опциональных последовательностей (?.). Я впервые встретился с таким оператором в языке программирования Groovy. C тех пор мне хотелось, чтобы он появился и в JavaScript. Этот оператор позволяет организовать обращение к вложенным свойствам объектов без необходимости постоянной проверки их существования. Если при обращении к свойству этот оператор встретится со значением undefined — он просто вернёт это значение, не выбрасывая ошибку TypeError.

// без оператора опциональной последовательности
const value = foo && foo.bar && foo.bar.baz;
 
// с оператором опциональной последовательности
const value = foo?.bar?.baz;


Оператор опциональной последовательности, скомбинированный с оператором проверки значений на null и undefined, даёт разработчику ещё больше возможностей, позволяя, например, записывать в переменную либо значение некоего вложенного свойства объекта, либо, если такого свойства не существует, некое стандартное значение. Вот как это выглядит:

const value = foo?.bar?.baz ?? 'default value';


▍Приватные поля классов


В TypeScript, с момента появления этого языка, есть собственная концепция приватных полей классов, объявляемых с модификатором доступа private. Эта концепция появилась в TypeScript ещё до того, как в стандарте ECMAScript были описаны классы. Но в TypeScript эта концепция относится к механизмам, работающим во время компиляции кода. Компилятор выдаст ошибку в том случае, если к приватному полю класса обращаются не из собственных методов класса. Теперь в JavaScript появилась возможность объявления приватных свойств и методов класса. Но эта возможность и семантически, и синтаксически отличается от того, что до сих пор существовало в TypeScript.

Приватные поля в JavaScript объявляют не с использованием модификатора доступа private. Вместо этого их объявляют, ставя в начале их имён символ #.

class Fan { 
    #on = false; 
    private name = 'fan';
 
    turnOn() { 
        this.#on = true; 
    }
   isTurnedOn() { 
        return this.#on; 
    }
}
 
const fan = new Fan(); 
fan.isTurnedOn(); // false  
fan.turnOn(); 
fan.isTurnedOn(); // true
 
fan.on; // не существует
fan.#on; // недоступно
fan.name; // выдаёт ошибку компиляции, но доступно в JS


Сейчас JavaScript поддерживает приватные поля, предложение по приватным методам находится на третьей стадии согласования. В настоящее время модификатор private и знак # в имени поля нельзя использовать совместно. Оба подхода могут пригодиться в ходе разработки, и то, какой именно выбрать, зависит от программиста. Вот подкаст, в котором обсуждают новый синтаксис объявления приватных полей.

▍Использование ключевого слова await на верхнем уровне кода


Механизмы асинхронного программирования серьёзно расширяют возможности JavaScript и TypeScript. Сначала в этой сфере появились промисы, потом — конструкция async/await, которая позволяет писать более чистый асинхронный код.

Один из случаев, когда используют промисы, а не async/await — это вызов асинхронного метода за пределами асинхронной функции. Например — на верхнем уровне кода модуля или приложения. В качестве обходного пути в такой ситуации можно предложить создание асинхронного немедленно вызываемого функционального выражения (IIFE, Immediately Invoked Function Expression) и выполнение асинхронного кода внутри такого выражения.

(async () => { 
    const response = await fetch('https://api.github.com/users/sitepen'); 
    const data = await response.json(); 
    console.log(`Check out the blog at ${data.blog}`); 
})();


Теперь TypeScript поддерживает возможность JavaScript по использованию ключевого слова await на верхнем уровне кода. Это значит, что await можно использовать за пределами функций, объявленных с ключевым словом async. Это очень хорошо в плане написания компактного и понятного кода. Правда, выражения await на верхнем уровне кода критикуют за то, что они могут вызвать замедление загрузки модулей и создать ситуацию, в которой некий модуль может замедлить загрузку всего приложения, так как системе приходится ждать завершения асинхронной операции, а потом уже выполнять весь код модуля.

const response = await fetch('https://api.github.com/users/sitepen'); 
const data = await response.json();
 
export default { ...data };


Улучшенная среда для экспериментов с TypeScript


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

Итоги


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

Уважаемые читатели! Какие новые возможности TypeScript кажутся вам наиболее интересными?

1ba550d25e8846ce8805de564da6aa63.png

© Habrahabr.ru