# Snippets

## Links

* <https://www.typescriptlang.org/docs/handbook/utility-types.html>

## Basic types

{% code overflow="wrap" %}

```typescript
// Define object
type Game=  Record<string, unknown>
let some: Game = {zz: 11}, some2: Game = {zz:11, xx:22}

// Ternaries
type StringFromType<T> = T extends string ? 'string' : never

type lorem = StringFromType<'lorem ipsum'> // 'string'
type ten = StringFromType<10> // never

type NullableString = string | null | undefined

type NonNullable<T> = T extends null | undefined ? never : T // Built-in type, FYI

type CondUnionType = NonNullable<NullableString> // evalutes to `string`
```

{% endcode %}

## Constant object for searching keys in another object with dynamic strings

```typescript
const FIELDS = {
  cardnum: 'cardnum',
  cardmon: 'cardmon',
  cardcvv: 'cardcvv',
} as const;

const messages = {
  cardnumPlaceholder: { . . . },
  cardmonPlaceholder: { . . . },
  cardcvvPlaceholder: { . . . },
}

var message1 = messages[`${FIELDS.cardnum}Placeholder`]
var message2 = messages[`${FIELDS.cardmon}Placeholder`]
```

## Как получить тип вложенного типа?

{% code overflow="wrap" lineNumbers="true" %}

```typescript
// Это пример хорошей организации типов

// Есть общий интрерфейс Person
interface Person {
  name: string;
  lastName: string;
  id: string;
}

// Енам лицензий пилота
enum LicenseType {
  STUDENT = "Student",
  SPORT = "Sport",
  COMMERCIAL = "Commercial",
  FLIGHT_INSTRUCTOR = "FLIGHT Instructor"
}

// Сам пилот
interface Pilot extends Person {
  licenseNumber: number;

  // У него есть свойство licenseType с типом LicenseType
  // Собирать тип из маленьких типов - хорошая практика
  licenseType: LicenseType;
}

// Теперь аргумент функции мы также типизируем с LicenseType
const isCommercialLicense = (license: LicenseType) =>
  license === LicenseType.COMMERCIAL;

const commercialPilot: Pilot = {
  name: "Ivan",
  lastName: "Petrov",
  id: "a",
  licenseNumber: 2022,
  licenseType: LicenseType.COMMERCIAL
};

// Все красиво типизируется
console.log(isCommercialLicense(commercialPilot.licenseType));

// Теперь плохой пример
//
interface JetEngine {
  model: {
    name: string;
    serialNumber: number;
    type: "turbo" | "turbofan" | "ramjet";
  };

  // У нас теперь есть такой union-тип.
  // По-хорошему такое нужно выносить в енам
  state: "disabled" | "power-up" | "ready";
}

// Мы могли бы попытаться сделать так, но это было бы неправильно:
// const checkStateReadiness = (value: string) => value === "ready";
// const checkStateReadiness = (value: JetEngine.state) => value === "ready";

// Зато можно вытащить вложенный тип через скобочную запись
const checkStateReadiness = (value: JetEngine["state"]) => value === "ready";

// Теперь функция хорошо типизируется
console.log(checkStateReadiness("Bullshit"));
```

{% endcode %}

## Generics?

{% code overflow="wrap" lineNumbers="true" %}

```typescript
// Функция каркас
function identity<Type>(argument: Type): Type {
  return argument;
}

// Вызываем функцию с конкретным типом
const number = identity<number>(123);

interface MobileHouse {
  model: string;
}

// А можем вызвать так
const mobileHouse = identity<MobileHouse>({ model: "Hymer" });

console.log(number, mobileHouse);

// Общий тип c дженериком Type
type APIResponse<Type> = {
  items?: Type[]; // Данные могут быть в виде списка
  item: Type; // Или одним элементом, если идет запрос по id
};

// Конкретный тип User
type User = {
  id: string;
};

// fetchAPI - это тоже каркас.
// Принимает дженерик тип и возвращает промис с этим типом
const fetchAPI = <Type>(url: string): Promise<APIResponse<Type>> => {
  return fetch(url).then((response) => response.json());
};

export const getUser = (id: string): Promise<User> => {
  // В этой функции мы используем fetchAPI с конкретным типом User
  return fetchAPI<User>(`/v1/user/${id}`).then(({ item }) => item);
};
```

{% endcode %}

### Trim

{% code overflow="wrap" lineNumbers="true" %}

```typescript
type Chars = ' ' | '\n' | '\t'
type Trim<S extends string> = S extends `${Chars}${infer SS}` ? Trim<SS> : S extends `${infer SS}${Chars}` ? Trim<SS> : S
type str = Trim<'   zzz  '>
```

{% endcode %}

## Как сужать тип?

{% code overflow="wrap" lineNumbers="true" %}

```typescript
type Employee = {
  id: string;
  name: string;
  age: number;
  sex: string;
  dateOfBirth: Date;
  // ... много чего еще
};

// Обычно мы делаем так:
// const getUserLabel = ({name, age}: Employee): string => {
//   return `${age} ${name}`
// }

// Приходится использовать полный объект, когда как нужно всего пара полей
// console.log(getUserLabel({
//   id: 'subscribe',
//   name: 'Inna',
//   age: 22,
//   sex: 'female',
//   dateOfBirth: new Date()
// }))

// as - костыль! Избегай его!
// console.log(getUserLabel({
//   name: 'Inna',
//   age: 22,
// } as Employee))

// Pick - утилита для сужения типов
// Передаем ей базовый тип Employee
// И юнион тип из тех значений, которые нам нужны в функции
const getUserLabel = ({
  name,
  age
}: Pick<Employee, "name" | "age">): string => {
  return `${age} ${name}`;
};

// Теперь можем использовать объект из двух нужных полей
console.log(
  getUserLabel({
    name: "Inna",
    age: 22
  })
);

// Общий тип
type BaseType = {
  a: number;
  // общие свойства
};

// Получаем частный тип из общего с помощью сужения типов
export type CurrentType = Pick<BaseType, "a">;
```

{% endcode %}

## Как keyof нам может помочь?

{% code overflow="wrap" lineNumbers="true" %}

```typescript
// Есть время истечения срока подписки
type ExpiryDateTime = {
  days: number;
  hours: number;
  minutes: number;
  seconds: number;
};

// Соответсвующий объект
let expiryDateTime: ExpiryDateTime = {
  days: 0,
  hours: 0,
  minutes: 0,
  seconds: 0
};

// Функция для изменения объекта по ключу
const change = (
  key: keyof ExpiryDateTime, // Вот тут keyof создает нам юнион тип из свойств ExpiryDateTime
  value: number,
  expiryDateTime: ExpiryDateTime
): ExpiryDateTime => {
  return { ...expiryDateTime, [key]: value };
};

expiryDateTime = change("seconds", 10, expiryDateTime);

console.log(expiryDateTime);
```

{% endcode %}

## Как типизировать переменную возвращаемым значением функции?

{% code overflow="wrap" lineNumbers="true" %}

```typescript
let foo = "bar";
// typeof дает нам тип переменной
let name: typeof foo; // string

// Функция возвращает юнион тип из разных значений
const getHisOrHer = (value: number): "his" | "her" | "its" => {
  if (value === 0) {
    return "its";
  }

  return value % 2 === 0 ? "his" : "her";
};

// Утилита ReturnType помогает нам взять тип, который функция возвращает
const pronoun: ReturnType<typeof getHisOrHer> = getHisOrHer(3);

console.log(pronoun);
console.log(name);
```

{% endcode %}

## Как маппить тип?

{% code overflow="wrap" lineNumbers="true" %}

```typescript
type RGB = {
  r: number;
  g: number;
  b: number;
};

type Color = {
  color1: RGB;
  color2: RGB;
};

// Создавать клон - плохо
// type ColorCode = {
//   color1: number;
//   color2: number;
// };

// Такие самописные утилиты помогают нам мапить другие типы
type toNumberSwitch<Type> = {
  [Property in keyof Type]: number; // Оператор in итерируется по значениям union-типа
};

export type toBooleanSwitch<Type> = {
  [Property in keyof Type]: boolean;
};

export type toStringSwitch<Type> = {
  [Property in keyof Type]: string;
};

const color: toNumberSwitch<Color> = {
  color1: 1,
  color2: 255
};

console.log(color);
```

{% endcode %}

## Type form Object values

{% code overflow="wrap" lineNumbers="true" %}

```typescript
 const OPERATORS = {
  AND: { value: 'AND' },
  OR: { value: 'OR' },
  AND_NOT: { value: 'AND NOT' },
  OR_NOT: { value: 'OR NOT' },
  NOT: { value: 'NOT', unary: true },
} as const

type Keys = keyof Readonly<typeof OPERATORS> // "AND" | "OR" | "NOT" | "AND_NOT" | "OR_NOT"
type OperatorsType= typeof OPERATORS[Keys]['value']; // "AND" | "OR" | "AND NOT" | "OR NOT" | "NOT"
```

{% endcode %}

## Standard utils implementation

### Implement Pick\<Type, Keys>

{% code overflow="wrap" lineNumbers="true" %}

```typescript
type CustomPick<T, K extends keyof T> = {
    [Key in K]: T[Key]
}
```

{% endcode %}

### Implement Omit\<Type, Keys>

{% code overflow="wrap" lineNumbers="true" %}

```typescript
type CustomOmit<T,K extends keyof T> = {
   [Key in keyof T as Key extends K ? never : Key] : T[Key]
}
```

{% endcode %}

### Implement Extract\<Type, Type>

{% code overflow="wrap" lineNumbers="true" %}

```typescript
type Extract<T, U> = T extends U ? T : never
type Exclude<T, U> = T extends U ? never : T
```

{% endcode %}

### Implement ReturnType\<Type>

{% code overflow="wrap" lineNumbers="true" %}

```typescript
type ReturnTypeCustom<T> = T extends (...args: any[]) => infer R ? R : any;
type b = ReturnTypeCustom<() => string | number> // string | number
```

{% endcode %}

### Implement FnParameters\<Type>

{% code overflow="wrap" lineNumbers="true" %}

```typescript
type FnParameters<T> = T extends (...args: infer R)=>any ? R : never
type params = FnParameters<(a1: number, b2: string)=>void>
```

{% endcode %}

### Implement FirstArgOfFunc\<Type>

{% code overflow="wrap" lineNumbers="true" %}

```typescript
type FirstArgOfFunc<T> = T extends (
  first: infer FirstArgument,
  ...args: any[]
) => any ? FirstArgument : never

type t = FirstArgOfFunc<(name: string, age: number) => void> // string
```

{% endcode %}

### Implement Array type

{% code overflow="wrap" lineNumbers="true" %}

```typescript
type ArrayType<T> = T extends (infer Item)[] ? Item : T
type t = ArrayType<[string, number]> // string | number
```

{% endcode %}
