TypeScript и все что тебе нужно в разработке

от автора

АвторМаслов Андрей, Front-end разработчик.

О статье

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

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

Навигатор

Intersection Types и Union Types

Intersection Types

В TS вы можете пересекать типы. Вы можете получить тип C способом пересечения типов А и В. Смотрите пример ниже:

type A = {   id: number   firstName: string   lastName: string  }  type B = {   id: number   height: number    weight: number }  type C = A & B   //Итог пересечения типов A и B type C = {   id: number   firstName: string   lastName: string    height: number    weight: number }

Union Types

Аналогично пересечению, вы можете выполнить и объединение типов, т.е создать аннотации несколько типов в текущей переменной. Смотрите пример ниже:

type A = number  type B = string  type C = A | B  //Итог объединения A и B type C = number или string  const parseAmount = (val: C) => {   if (typeof val === 'number') {     return val    }    if (typeof val === 'string') {     return val.resplace(',', '.')   } }

Generic Types

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

type FetchResponse<T> = {   data: T   errorMessage: string   errorCode: number }  type AuthDataRs = {   accessToken: string    refreshToken: string  }  const login = async (lg: string, ps: string): FetchResponse<AuthDataRs> => {   const response = await fetch(...)    return response }  //FetchResponse<AuthDataRs> - вот такая запись позволит вам  //переиспользовать FetchResponse для различных запросов.

При необходимости можно расширять свой тип несколькими дженериками:

type FetchResponse<T, P> = {   data: T   error: P }

Так же вы можете назначать тип по умолчанию дженерику:

type FetchResponse<T, P = string> = {   data: T   error: P }

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

Utility Types

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

Awaited

Awaited<T>
Утилита предназначена для ожидания в асинхронных операциях, например:

type A = Awaited<Promise<number>>; //type A -> number

Partial

Partial<T>
Утилита предназначена для создания нового типа, где каждое свойство станет опциональным. Напомню, для того чтобы сделать свойство объекта опциональным, необходимо использовать знак «?»:

type A = {   id: number    name?: string //Опциональное свойство (необязательное) }

Как работает Partial ?

type A = {   id: number    name: string }  type B = Partial<A>  //Output type B = {   id?: number //Опциональное свойство (необязательное)   name?: number //Опциональное свойство (необязательное) }

Required

Required<T>
Утилита работает в точности наоборот как Partial. Свойства текущего типа делает строго обязательными.

type A = {   id?: number    name?: string }  type B = Required<A>  //Output type B = {   id: number //Обязательное свойство   name: number //Обязательное свойство }

Readonly

Readonly<T>
Утилиты преобразует все свойства типа, делает их недоступными для переназначения с использованием нового значения.

type A = {   id: number   name: string }  type B = Readonly<A>  const firstObj: A = { id: 0, name: 'first'} const secondObj: B = { id: 1, name: 'second'}  firstObj.name = 'first_1' // it's correct secondObj.name = 'second_2' //Cannot assign to 'name' because it is a read-only property.

Если у вас есть необходимость сделать поле readonly только для определенного свойства объекта, то необходимо написать ключевое слово перед именем св-ва:

type A = {   readonly id: number    name: string }

Record

Record<T, U>
Утилита предназначена для создания типа объекта, Record<Keys, Types>, где Keys — имена свойств объекта, а Types — типы значений свойств.

enum CarNames {   AUDI = 'audi',   BMW = 'bmw' }  type CarInfo = {   color: string    price: number  }  type Cars = Record<CarNames, CarInfo>  //Output type Cars = {   audi: CarInfo;   bmw: CarInfo; }

Pick

Pick<T, ‘key1’ | ‘key2’>
Утилита предназначена для создания нового типа из выбранных свойств объекта.

type A = {   id: number    name: string  }  type B = Pick<A, 'name'>  //Output 1 type B = {   name: string  }  type B = Pick<A, 'id' | 'name'>  //Output 2 type B = {   id: number    name: string }

Omit

Omit<T, ‘key1’ | ‘key2’>
Утилита предназначена для создания типа из оставшихся (не исключенных) свойств объекта.

type A = {   id: number    name: string  }  type B = Omit<A, 'id'>  //Output 1 type B = {   name: string  }  type B = Omit<A, 'id' | 'name'>  //Output 2 type B2 = {}

Exclude

Exclude<T, U>
Утилита создает union тип, исключая свойства, которые уже присутствуют в двух разных типах. Он исключает из T все поля, которые можно назначить U.

type A = {   id: number   name: string   length: number }  type B = {   id: number   color: string   depth: string }  type C = Exclude<keyof A, keyof B>  //Output  type C = "name" | "length"

Extract

Extract<T, U>
Создает union тип, извлекая из T все члены объединения, которые можно назначить U.

type A = {   id: number   name: string   length: number }  type B = {   id: number   name: string   color: string   depth: string }  type C = Extract<keyof A, keyof B>  //Output  type C = "id" | "name"

ReturnType

ReturnType<T>
Создает тип, состоящий из типа, возвращаемого функцией T.

type A = () => string   type B = ReturnType<A>  //Output type B = string

Это одни из основных Utility Types, в материалах к статье я оставлю ссылку на документацию, где при желании вы сможете разобрать остальные утилиты для продвинутой работы с TS.

Conditional Types

В TypeScript есть возможность создавать типы в зависимости от передаваемого дженерика.

type ObjProps = {   id: number    name: string  }  type ExtendsObj<T> = T extends ObjProps ? ObjProps : T  const obj1: ObjProps = {   id: 0,    name: 'zero' }  const obj2 = {   id: 1 }  type A = ExtendsObj<typeof obj1> // type A = ObjProps type B = ExtendsObj<typeof obj2> // type B = { id: number }

Mapped Types

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

type MapToNumber<T> = {   [P in keyof T]: number }  const obj = {id: 0, depth: '1005'}  type A = MapToNumber<typeof obj>  //Output type A = {   id: number    depth: number  }

Type Guards

Если тип не определен или неизвестен, то на помощь разработчику приходит «защита типов».

typeof

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

const fn = (val: number | string) => {   if (typeof val === 'number') {     return ...   }    throw new Error(`Тип ${typeof val} не может быть обработан`) }

in

Еще один из способов защитить тип, использовать in, этот оператор проверяет присутствие свойства в объекте.

const obj = {   id: 1,   name: 'first' }  const bool1 = 'name' in obj  //true const bool2 = 'foo' in obj  //false

instanceof

Оператор экземпляра проверяет, появляется ли свойство прототипа конструктора где-нибудь в цепочке прототипов объекта

function C() {} function D() {}  const o = new C();  o instanceof C //true o instanceof D //false

is

Этот оператор указывает TypeScript какой тип присвоить переменной, если функция возвращает true. В примере ниже оператор is сужает тип у переменной foo (string | number) до string. Это определенная пользователем защита типа. Благодаря защите компилятор приводит тип до определенного внутри блока if.

interface FirstName {     firstName: string }  interface FullName extends FirstName {     lastName: string }  const isFirstName = (obj: any): obj is FirstName => {     return obj && typeof obj.firstName === "string" }  const isFullName = (obj: any): obj is FullName => {     return isFirstName(obj) && typeof (obj as any).lastName === "string"; }  const testFn = (objInfo: FirstName | FullName | number) => {     if (isFullName(objInfo)) {         console.log('Тип FullName')     } else if (isFirstName(objInfo)) {         console.log('Тип FirstName')     } else {       console.log('Тип не принадлежит FullName или FirstName')     } }  testFn({ firstName: 'Andrey' }) //Тип FirstName testFn({ firstName: 'Andrey', lastName: 'Maslov' }) //Тип FullName testFn(1) //Тип не принадлежит FullName или FirstName

Заключение

Как видите, TypeScript — это мощный инструмент для разработки, который позволяет улучшить качество вашего кода, сделать его более надежным и легко поддерживаемым. В этом туториале мы рассмотрели приемы работы с TypeScript, над такими продвинутыми темами, например, как дженерики и type guards.

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

Если у вас возникнут вопросы или потребуется дополнительная помощь, обращайтесь к официальной документации TypeScript или дополнительным материалам.

Материалы для изучения

Основы
Utility Types
Шпаргалка по TS в картинках


ссылка на оригинал статьи https://habr.com/ru/articles/762902/