TypeScript, часть 3: работа с пользовательскими типами

Эта статья является частью серии статей о TypeScript:

TypeScript, часть 1: введение

TypeScript, часть 2: добавление TypeScript в ваше приложение

TypeScript, часть 3. Работа с пользовательскими типами

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

Пользователь приложения — с его first_name, last_name, email, phone — можно рассматривать как отдельный тип. Сообщение в блоге, состоящее из title, author и body, является другим типом. И как было бы здорово, если бы мы могли воспользоваться всеми преимуществами TypeScript и применить их к нашим самым уникальным потребностям? Вы, наверное, понимаете, к чему я клоню — вы можете!

Введите пользовательские типы. Пользовательские типы — это именно то, на что они похожи. Можно создать тип status, который представляет собой просто строку, но предотвращает случайную передачу другой строки (например, name) вместо статуса и отображение статуса John вашему пользователю по имени John. Вы можете создать interfaces, которые обеспечивают большую гибкость и являются стандартной передовой практикой для создания типов для реквизита React. И одна из моих самых любимых частей TypeScript — многие сторонние библиотеки теперь предоставляют информацию о типе, которая, если вы когда-либо работали с зависимостью, требующей какого-то массивного, глубоко вложенного набора реквизитов, вы поймете, почему это прекрасная вещь. .

Пользовательские типы

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

Однако они не обязательно должны быть объектами. Вы действительно можете превратить что угодно в пользовательский тип. Если вы создаете приложение для написания блогов, вы можете упорядочить сообщения по жанрам. Но вы не хотите, чтобы ваши пользователи могли выбирать только любой жанр — это вызывает беспорядок, когда в качестве существующих вариантов есть как JavaScript, так и JS. Итак, вы создаете пользовательский тип:

type Genre = 'JavaScript' | 'Golang' | 'Programming' | 'Business'

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

Почему мы используем пользовательские типы

Помните наше обсуждение ранее о том, почему мы используем типы в первую очередь. Это делает нашу жизнь проще. Мы устанавливаем для себя меры безопасности, чтобы всегда знать, точно что мы должны передать в функцию, что именно возвращается из функции, какие методы мы можем вызывать для переменной, является ли переменная может быть null или undefined и т. д.

Создание собственных пользовательских типов позволяет нам делать все это на более сложном уровне. Мы можем более точно определить, какие значения должны быть разрешены (что также служит своего рода документацией для будущих разработчиков), и позволяет нам применять меры безопасности для типов, которые нам особенно нужны. Если действительный пользователь в нашем приложении должен иметь поля first_name, last_name, email и phone, то мы должны всегда проверять их вместе,поскольку действительный пользователь — это не одно, авсе допустимые поля . Это также позволяет нам указать, что User — это то, что мы хотим передать в функцию или вернуть из нее, а не просто любой старый объект.

Когда использовать пользовательские типы

Это будет в некоторой степени зависеть от мнения разработчиков и лучших практик команды. Лично мне нравится создавать собственные типы в нескольких сценариях:

  1. Основной объект приложения (т. е. User, BlogPost, Genre), который будет передаваться по всему приложению. Вы довольно быстро почувствуете это, просто придерживаясь принципа DRY.
  2. Узкое подмножество существующего типа (например, приведенный выше пример Genre, в котором разрешено небольшое подмножество типа string).
  3. React props (мы более подробно рассмотрим это позже).

Но, как правило, стоит задать себе вопрос: «Сделает ли создание и поддержка этого типа разработку и поддержку этого приложения проще и надежнее, или это станет бременем?»

Создание пользовательского типа

Создать собственный тип довольно просто, и вы уже видели его синтаксис. Ключевое слово type действует как let или const и присваивает имя типа левой части оператора:

// Note: the ? after the address field means the field is optional // An object that has all valid fields besides this is still a User
type User = {
  first_name: string;
  last_name: string;
  email: string;
  phone: string;
  address?: string;
  age: number;
  verified: boolean;
}
type Title = string;

Тип Проверка пользовательского типа

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

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

let name = 'Zuma';
typeof name === 'string'; // this will evaluate to true
typeof name === 'number'; // this will evaluate to false

Синтаксис typeof работает для примитивных типов. Аналогичным образом можно проверить массивы:

let pets = ['Zuma', 'Roxy'];
console.log(pets instanceof Array); // this will evaluate to true

С пользовательскими типами мы также должны создавать собственные методы для проверки типов. Мы делаем это с помощью предикатов типов.

Во-первых, давайте вернемся назад и поговорим о примере type User выше. Вы можете спросить себя, почему я не могу просто проверить typeof u.first_name === ‘string’ && u.last_name === ‘string’ && u.email === ‘string’ и так далее и тому подобное? Ответ: можно, но когда вы закончите все эти проверки типов, TypeScript по-прежнему не будет знать, что переменнаяuимеет тип User. У вас больше не будет информации для дальнейшей работы. Когда вы пытаетесь передать переменную u в функцию, TypeScript все равно выдает ошибку, потому что не знает, какой это тип!

Чтобы решить эту проблему, мы используем специальную функцию, называемую предикатом типа. Тип возвращаемого значения предиката типа — var is type. В нашем примере это будет u is User. Наша функция будет выглядеть примерно так:

// We pass type unknown so that we can check a variable of any type
function isUser(u: unknown): u is User {
  // Assert that the unknown value is of type User, so that we can 
  // check the expected fields
  
  const user = u as User;
  if (!user.first_name || typeof user.first_name !== 'string') {
    return false;
  }
  if (!user.last_name || typeof user.last_name !== 'string') {
    return false;
  }
  if (!user.email || typeof user.email !== 'string') {
    return false;
  }
  if (!user.phone || typeof user.phone !== 'string') {
    return false;
  }
  // We use && instead of || here because it is an optional field
  if (user.address && typeof user.address !== 'string') {
    return false;
  }
  if (!user.age || typeof user.age !== 'number') {
    return false;
  }
  
  return true;
}

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

Далее мы рассмотрим интерфейсы и использование типов при работе со сторонними библиотеками.