Введение в TypeScript

Опубликовано: 2020-10-29

Первые языки, на которых я когда-либо программировал, — это C и Java. В этих языках каждый раз, когда вы определяете переменную, вы должны указать ее тип. Если вы попытаетесь присвоить ему значение другого типа, компилятор будет жаловаться:

 // main.c int main() { int x = "two"; return 0; } > cc main.c -o main // warning: initialization makes integer from pointer without a cast

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

Гифка Губки Боба, ломающего компьютер

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

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

 const user = getUser( userId ); greet( user.name ); // Uncaught TypeError: user is undefined

Если вам надоели ошибки типа undefined в вашем коде, пришло время добавить компилятор в ваш рабочий процесс. Давайте посмотрим, что такое TypeScript и как он позволяет нам улучшить качество нашего программного обеспечения на несколько порядков.

Что такое TypeScript

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

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

Как работает TypeScript

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

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

 let user = "David"; let age = 34; let worksAtNelio = true; console.log( user, age, worksAtNelio );

также является кодом TypeScript. Вы можете скопировать и вставить его в TypeScript Playground, и вы увидите, что он компилируется. Так что же в нем такого особенного? Его типы, конечно. В JavaScript вы можете сделать следующее:

 let user = "David"; let age = 34; let worksAtNelio = true; user = { name: "Ruth" }; console.log( user, age, worksAtNelio );

но это вызовет ошибку в TypeScript. Просто попробуйте на игровой площадке, и вы увидите следующую ошибку:

 Type '{ name: string; }' is not assignable to type 'string'.

Так что это все о?

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

Gif с человеком, у которого есть отличная отличная идея

В нашем примере, когда мы определяли переменную user , мы присваивали ей текстовую строку "David" , поэтому TypeScript знает, что user является (и всегда должен быть) строкой . Проблема в том, что чуть позже мы пытаемся изменить тип нашей user переменной, присвоив ей объект, имеющий единственное свойство ( name ), значением которого является строка "Ruth" . Понятно, что этот объект не является строкой , поэтому TypeScript жалуется и сообщает нам, что присваивание выполнить невозможно.

Есть два возможных способа исправить это:

 // Option 1 let user = "David"; user = "Ruth"; // OK. // Option 2 let user = { name: "David" }; user = { name: "Ruth" };

Давайте будем явными при определении типов наших переменных

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

 let user: string = "David"; let age: number = 34; let worksAtNelio: boolean = true; console.log( user, age, worksAtNelio );

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

С одной стороны, явное указание типов может помочь лучше документировать наш код (что станет еще яснее в следующем разделе). С другой стороны, это позволяет нам решить ограничения системы вывода типов. Да, вы правильно прочитали: бывают случаи, когда вывод не очень хорош, и лучшее, что может сделать TypeScript, это сказать нам: «Ну, я не знаю, какой именно должна быть эта переменная; Я думаю, это может быть что угодно!»

Рассмотрим следующий пример:

 const getName = ( person ) => person.name; let david = { name: "David", age: 34 }; console.log( getName( david ) ); // Parameter 'person' implicitly has an 'any' type.

В этом случае мы определяем функцию getName , которая получает параметр и возвращает значение. Обратите внимание, как много вещей мы предполагаем в такой простой функции: мы ожидаем объект ( person ) по крайней мере с одним свойством, называемым name . Но на самом деле у нас нет никакой гарантии, что тот, кто вызовет эту функцию, будет использовать ее правильно. Но даже если они это сделают, мы все еще не знаем, каков результирующий тип этой функции. Конечно, вы можете предположить, что имя будет строкой , но вы знаете это только потому, что вы человек и понимаете значение этого слова. Но посмотрите на следующие примеры:

 getName( "Hola" ); // Error getName( {} ); // Returns undefined getName( { name: "David" } ); // Returns a string getName( { name: true } ); // Returns a boolean

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

Забавная гифка с очень грустным ребенком

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

Определите свои собственные типы

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

 let david = { name: "David", age: 34 };

но мы не обращали особого внимания на его тип, выведенный TypeScript, не так ли? Все, что мы видели, это пример недопустимого присваивания из-за несоответствия типизации:

 let user = "David"; user = { name: "Ruth" }; // Type '{ name: string; }' is not assignable to type 'string'.

что каким-то образом намекнуло, что тип объекта был « {name:string;} », что можно прочитать как «это объект со свойством, называемым name of type string ». Если так определяются типы объектов, то следующее должно быть допустимым TypeScript:

 let david: { name: string, age: number } = { name: "David", age: 34 };

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

В общем, пользовательский тип — это, по сути, обычный тип TypeScript с настраиваемым именем:

 type Person = { name: string; age: number; }; let david: Person = { name: "David", age: 34 };

Круто, да? Теперь, когда у нас есть тип Person , мы можем легко аннотировать нашу функцию getName и четко указать тип нашего входного параметра:

 const getName = ( person: Person ) => person.name;

И это простое обновление дает TypeScript много информации! Например, теперь он может сделать вывод, что тип результата этой функции является строкой , потому что он точно знает, что атрибут name объекта Person также является строкой :

 let david: Person = { name: "David", age: 34 }; let davidName = getName( david ); davidName = 2; // Type 'number' is not assignable to type 'string'.

Но, как всегда, вы можете явно аннотировать тип результата, если хотите:

 const getName = ( person: Person ): string => person.name;

Еще немного о типах TypeScript…

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

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

 type Person = { name: string; age: number; }; let david: Person = { name: "David", age: 34 }; // OK let ruth: Person = { name: "Ruth" }; // Property 'age' is missing in type '{ name: string; }' but required in type 'Person' let toni: Person = { name: "Toni", age: 35, gender: "M" }; // Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'. Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.

Как видите, переменная Person принимает только те объекты, которые полностью соответствуют типу Person . Если какие-то атрибуты отсутствуют (у ruth нет age ) или атрибутов больше, чем включено в тип (у toni есть gender ), TypeScript выдаст сообщение об ошибке и вызовет ошибку несоответствия типов.

Но, если мы говорим о вызове функций, все по-другому! Типы аргументов в функции не являются точным требованием, но указывают минимальный интерфейс, которому должны соответствовать параметры. Я знаю, я знаю, это слишком абстрактно. Давайте взглянем на следующий пример, который, я думаю, прояснит ситуацию:

 type NamedObject = { name: string; }; const getName = ( obj: NamedObject ): string => obj.name; const ruth = { name: "Ruth" } getName( ruth ); // OK const david = { name: "David", age: 34 }; getName( david ); // OK const toni = { firstName: "Toni" }; getName( toni ); // Argument of type '{ firstName: string; }' is not assignable to parameter of type 'NamedObject'. Property 'name' is missing in type '{ firstName: string; }' but required in type 'NamedObject'.

Как видите, мы переопределили функцию getName как нечто более общее: теперь она принимает объект NamedObject , то есть объект, который должен иметь атрибут name типа string . Используя это определение, мы видим, что ruth и david идеально соответствуют этим требованиям (оба имеют атрибут name ), а toni — нет, так как у него нет ожидаемого атрибута name .

Заключение

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

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

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

Избранное изображение King's Church International на Unsplash.