React Junior
208 subscribers
37 photos
462 links
Изучение React с нуля
Download Telegram
Typescript. Утилиты. Часть 3 (Parameters, ReturnType)

Первая часть (Readonly, Required, Partial): https://t.me/react_junior/413
Вторая часть (Exclude, Extract, NonNullable): https://t.me/react_junior/429

Утилиты Parameters и ReturnType работают с функциями и могут, соответственно, получать типы входных параметров или возвращаемого значения.

Возьмем функцию:

function add(num1: number, num2: number): number {
return num1 + num2;
}


В утилиту нужно передать тип этой функции, а не ее значение: typeof add

Утилита Parameters вернет кортеж их типов входных аргументов:

[num1: number, num2: number]


Утилита ReturnType вернет тип возвращаемого значения:

number


#typescript
👍3
TypeScript. Условные типы

Статья (англ): https://javascript.plainenglish.io/use-typescript-conditional-types-like-a-pro-7baea0ad05c5

Утилиты Exclude, Extract, NonNullable, Parameters, and ReturnType используют "условные типы" (Conditional Types).

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

Общий синтаксис условных типов выглядит так:

T extends U ? X : Y


Берется некий тип T и присваивается типу U (проверяется, входит ли T в U). Если да, возвращается X, если нет - Y (X и Y могут быть чем угодно, например, тем же исходным типом T).

Простейшая утилита IsString такого рода может выглядеть так:

T extends string ? true : false


Она просто определяет, является ли переданный тип строкой.

Но можно делать и намного более сложные вещи.

Распределенные условные типы

Если передать в утилиту объединение типов, на выходе тоже можно получить объединение, потому что операция выполняется для каждого члена объединения - это называется _распределенные условные типы__ (distributed conditional type).

Но это работает только для "голых" типов, не обернутых в массивы/кортежи/промисы.

#typescript #ссылки #подкапотом
👍4
TypeScript. Пересечения

Статья (англ.): https://javascript.plainenglish.io/using-typescript-intersection-types-like-a-pro-a55da6a6a5f7

Статья подробно рассказывает об операторе & в TypeScript.

Если он работает с примитивными значениями, то понять, каким будет результат просто - этот оператор работает как логическое И:

type T1 = 1 & number; // 1
type T2 = '1' & string; // '1'
type T3 = number & string; // never


Если один из операндов never, то результатом будет never.

Если один из операндов any, то результатом будет any.

type T4 = any & 1; // any
type T5 = any & boolean; // any
type T6 = any & never; // never


Если же операнды являются объектами, то оператор работает с каждым полем этих объектов.

interface Point {
x: number;
y: number;
}
interface Named {
name: string;
}

type NamedPoint = Point & Named;
// { x: number, y: number, name: string }


Если есть одинаковые поля с конфликтующими типами, будет never:

interface A {
n: number
}
interface B {
n: string
}

type C = A & B; // { n: never }


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

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

#typescript #ссылки #подкапотом
👍3
Использование Typescript с React – руководство для новичков

Статья (рус.): https://habr.com/ru/company/otus/blog/456124/

Небольшое руководство для тех, кто только пытается совместить React и TypeScript в одном приложении. Рассказывает, где именно использовать типы в React.

1) Вместо PropTypes для пропсов компонента.


export interface StandardComponentProps {
title?: string
children: React.ReactNode
}

export function StandardComponent({
children,
title = 'Default Title',
}: StandardComponentProps) {

}


Тут же мы видим полезный тип ReactNode.

Если кроме указнных пропсов нужно принимать еще стандартные атрибуты HTML-элементов (`style`, aria-hidden, `className`), нужно просто расширить существующий встроенный интерфейс React.HTMLAttributes.

2) Для аргументов в обработчиках событий.


function onFocus(e: React.FocusEvent) {
console.log('Focused!', e.currentTarget)
}


Еще полезный тип FocusEvent. Помимо него есть еще MouseEvent и, вероятно, ряд других событий.

3) Использование дженериков с компонентами

Компоненты React могут принимать обобщенные типы, которые затем могут использоваться для типизации, например, пропсов.

4) Хуки

Типизации требуют в основном два React-хука: useRef и useReducer, остальные работают из коробки. Для этого тоже используются дженерики.

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

5) typeof и keyof для типизирования компонентов с разными вариантами

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


const styles = {
primary: {
color: 'blue',
},
danger: {
color: 'red',
},
};

type StylesType = typeof styles;

export type ButtonType = keyof StylesType;

interface ButtonProps {
type: ButtonType
};

export function Button({ type = 'primary' }: ButtonProps) {
}


#typescript
👍3👎1
Typescript. Утилиты. Часть 4 (Uppercase, Lowercase, Capitalize, Uncapitalize)

Первая часть (Readonly, Required, Partial): https://t.me/react_junior/413
Вторая часть (Exclude, Extract, NonNullable): https://t.me/react_junior/429
Третья часть (Parameters, ReturnType): https://t.me/react_junior/430

В этот раз 4 очень простые утилиты, которые имеют дело с регистром символов:

- Uppercase
- Lowercase
- Capitalize
- Uncapitalize

#typescript
🔥2
TypeScript. Ключевое слово infer

Весь вечер пытаюсь разобраться с ключевым словом infer в TypeScript 🙂 Не сказать, что концепция самая очевидная, но вроде и не чрезмерно сложная, если я, конечно, все правильно понимаю.

Итак, infer может "выводить" один тип на основании другого. Использовать это ключевое слово можно только в условных конструкциях вместе с extend. Выведенный тип затем можно использовать, например, вернуть из условной конструкции.

Подробнее об условных типах: https://t.me/react_junior/431

Самый понятный для меня (но вероятно не самый популярный) пример использования infer - на приложенном скрине (взято со StackOverflow).

Утилита UnpackArrayType получает обобщенный тип T.
Затем ставится условие:


T extends (infer R)[]


Эта конструкция буквально означает, "можно ли представить T в виде массива элементов какого-нибудь типа R". При этом тип R буквально "выводится" из типа T с помощью infer. Если T равно number[], то R будет равно number.

Если условие получилось выполнить, возвращается свежевыведенный тип R.

Неплохой разбор infer есть в статье Understanding infer in TypeScript (англ.)

Там автор начинает с более простых концепций: типа never, оператора extends, объединений, а затем переходит к работе с функциями и собственно ключевому слову infer.

К слову, именно infer лежит в основе работы утилиты ReturnType.

В конце статьи приводяется некоторые популярные кейсы использования infer, например, выведение возвращаемого типа из промиса.

#ссылки #typescript #подкапотом #infer
👍5
Статья (англ.): https://levelup.gitconnected.com/using-typescript-infer-like-a-pro-f30ab8ab41c7

Еще немного про infer (начало здесь)

В статье приводится очень хорошее пошаговое объяснение того, как работает это ключевое слово.

Infer буквально распаковывает тип и позволяет вывести из него другой тип.

Рассматривается тот же пример с распаковкой массива и получением типа элемента (только подробнее и с анимацией).
Кроме того есть примеры с распаковкой аргументов и возвращаемого значения функций (так работает под капотом утилита ReturnType) и распаковкой ключей объекта.

Важно помнить, что infer может использоваться только в условных типах (`extends`) и после "захвата" доступен только в true-части выражения.

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

В конце задачка, которая сломала мой мозг 🤯

#typescript #infer
👍4
TypeScript. Шаблонные литералы

Статья (англ.): https://javascript.plainenglish.io/how-to-use-typescript-template-literal-types-like-a-pro-2e02a7db0bac

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

type Alignment = 'start' | 'end';
type Side = 'top' | 'right' | 'bottom' | 'left';
type AlignedPlacement = `${Side}-${Alignment}`;


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

В шаблонных строках можно также использовать утилиты, работающие со строками (Uppercase, Lowercase, Capitalize, Uncapitalize).
Также их можно использовать в условных типах в сочетании с ключевым словом infer для создания очень мощных условий.

В статье разобрано использование шаблонных строк для Key Remapping (переименования ключей в сопоставимых типах).

А в самом конце есть прекрасный пример рекурсивной работы с типами.

#typescript
🔥3👍2
Typescript. Утилиты. Часть 5 (Pick, Omit)

Первая часть (Readonly, Required, Partial): https://t.me/react_junior/413
Вторая часть (Exclude, Extract, NonNullable): https://t.me/react_junior/429
Третья часть (Parameters, ReturnType): https://t.me/react_junior/430
Четвертая часть (Uppercase, Lowercase, Capitalize, Uncapitalize): https://t.me/react_junior/435

Утилита Pick получает два параметра: исходный тип и набор ключей. Результат работы - новый тип на основе исходного, в который входят только указанные ключи.

Эта утилита похожа на Mapped Types, только Mapped Types берут полный набор ключей из исходного типа, а для Pick набор ключей заранее указан.

Omit, соответственно, работает наоборот, берет все ключи, кроме указанных. Под капотом использует Pick и Exclude, чтобы получить набор нужных для типа ключей.

#typescript
👍1🔥1
TypeScript. 15 самых популярных утилит

Статья (англ.): https://javascript.plainenglish.io/15-utility-types-that-every-typescript-developer-should-know-6cf121d4047c

С большинством из перечисленных в статье утилит мы уже знакомы.

Partial, Required, Readonly

Функциональность этих утилит разбирали здесь: https://t.me/react_junior/413
А подкапотную реализацию здесь (статья о Mapped Types): https://t.me/react_junior/428
Для перебора ключей используется конструкция P in keyof T.

Exclude, Extract, NonNullable

Функциональность тут: https://t.me/react_junior/429
Реализованы на условных типах: https://t.me/react_junior/431
Члены одного набора типов сопоставляются с членами второго набора с помощью конструкции T extends U ? X : Y

Pick, Omit

Функциональность и реализация тут: https://t.me/react_junior/442
Напоминают Mapped Types.

Parameters, ReturnType

Функциональность тут: https://t.me/react_junior/430
В реализации используется ключевое слово infer https://t.me/react_junior/439, чтобы вытащить нужные типы из полученного типа функции.

Uppercase, Lowercase, Capitalize, Uncapitalize

Функциональность здесь: https://t.me/react_junior/435

Record

С этой утилитой мы еще не встречались.

Она принимает два обобщенных типа: набор ключей Keys и тип значений Type. Из них формируется новый тип, в котором есть все ключи из Keys и каждому из них соответствует значение типа Type.

Условно это выглядит так:


type Keys = 101 | 102 | 103; // набор ключей
type User = { name: string; age: number }; // тип значений

// результат
type Record = {
101: User,
102: User,
103: User
}


#typescript
👍3🔥2
10 вещей, которые нужно знать о классах в TypeScript

Статья (англ.): https://levelup.gitconnected.com/10-things-you-need-to-know-about-typescript-classes-f58c57869266

Достаточно полный справочник по классам в TypeScript.

Первые пункты простые:

1. Статические поля и поля класса
2. Геттеры и сеттеры
3. Наследование классов (и реализация интерфейсов)
4. Абстрактные классы
5. Модификаторы доступа (и их сочетание с ECMAScript Private Fields)
6. Class Expressions
7. Использование дженериков в классах (https://t.me/react_junior/405)

Дальше посложнее: про конструкторы (и абстрактные конструкторы) и про использование в качестве типа самого класса и typeof Class. Это все пока не очень понятно, как использовать.

Но если что тип для функции-конструктора на скрине.

#typescript
👍3🔥2
Next.js + TypeScript

Для описания страниц в Next-приложении нам потребуется тип NextPage (импортируется из пакета next). Это дженерик, который принимает тип пропсов компонента.

#nextjs #серверныйрендеринг #typescript
👍2🔥1
TypeScript в Next.js

Соберем в одном месте типы, которые использует Next:

Данные для серверного рендеринга - GetStaticProps, GetStaticPaths, GetServerSideProps

import { GetStaticProps, GetStaticPaths, GetServerSideProps } from 'next';
export const getStaticProps: GetStaticProps = async function (context) {
// ...
}
export const getStaticPaths: GetStaticPaths = async function () {
// ...
}
export const getServerSideProps: GetServerSideProps = async function (context) {
// ...
}


API-роуты - NextApiRequest, NextApiResponse

import type { NextApiRequest, NextApiResponse } from 'next'

export default function handler(req: NextApiRequest, res: NextApiResponse) {
res.status(200).json({ name: 'John Doe' })
}


Тип NextApiResponse может также принимать обобщенный тип данных ответа.

Компонент App - AppProps

import type { AppProps } from 'next/app'

export default function MyApp({ Component, pageProps }: AppProps) {
// ...
}


#nextjs #документация #typescript
👍3🔥1
React: самые используемые типы

Статья (англ.): https://jser.dev/2023-05-31-react-types-in-typescript/

Автор статьи сделал подборку типов, используемых в React, чтобы нам не пришлось.

1. ReactElement

Главный "строительный" метод React - React.createElement(). Он принимает конфиг элемента, который нужно создать - его и описывает интерфейс ReactElement.


interface ReactElement {
type: T,
props: P,
key: Key | null,
}


ReactElement или JSX.Element (то же самое, просто алиас) - это то, что должно быть возвращено из JSXElementConstructor (из функционального или классового компонента).

2. ReactNode

Это "надтип" для ReactElement, как и в DOM - есть ноды (включают комментарии и текстовые узлы), а есть их подмножество - элементы.

В тип ReactNode входят ReactElement, строки, числа, булевы значения, фрагменты, порталы и даже null и undefined

3. FunctionComponent

Интерфейс, описывающий функциональный компонент

4. RefObject и MutableRefObject

Типы, описывающие рефы, созданные хуком useRef.
У useRef есть несколько перезагрузок и одна из них приводит к тому, что поле ref.current становится readonly и его нельзя изменить 😳

5. ComponentProps

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

6. Dispatch и SetStateAction

Эти типы описывают самый популярный хук useState.
Тип Dispatch - это просто функция, которая принимает аргумент заявленного типа и ничего не возвращает.
SetStateAction - это либо новое состояние, либо функция, которая принимает старое состояние и возвращает новое.

7. События

С типизацией событий в React всегда сложно. Есть два подхода:
- типизировать само событие (SyntheticEvent), которое приходит в обработчик в виде аргумента, например, MouseEvent
- или типизировать сам обработчик - MouseEventHandler

Оба типа - дженерики, которым можно передать тип элемента, на котором происходит событие.

#typescript #ссылки
👍3
Вариантность и совместимость типов в TypeScript

Попробуем разобраться, что такое ковариантность и контравариантность и на что они влияют в TypeScript.

Подтип и супертип

Сначала определим основные понятия - подтип и супертип.

Подтип расширяет супертип.

⁃ Dog - это подтип, Animal - это супертип.
⁃ String - это подтип, Object - это супертип.
⁃ number - это подтип, any - это супертип.

Мы пишем, что подтип меньше супертипа (SubType <: SuperType), однако по факту, в подтипе больше информации, чем в супертипе, потому что подтип буквально расширяет супертип. Подтип «избыточен» по сравнению с супертипом.

Любое значение с типом SubType всегда является одновременно значением типа SuperType, так как имеет все необходимые для SuperType поля.

Структурная типизация

Важно понимать, что в TypeScript подтип и супертип необязательно связаны отношениями наследования (class Dog extends Animal). TypeScript реализует структурную типизацию и ориентируется только на структуру типов.


type Animal = {
name: string
}

type Dog = {
name: string
breed: string
}


Типы Animal и Dog никак не связаны между собой, но Dog все равно является подтипом для Animal.

Ковариантность как основная стратегия

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

Есть конкретные правила для определения совместимости. В большинстве случаев верно утверждение:

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


let a: any = 42 // number <: any
let b: Animal = new Dog() // Dog <: Animal


Довольно просто понять, почему так сделано - значение с типом SubType также является значением типа SuperType, поэтому тут нет противоречий. Если программа готова справиться с Animal (супертип), значит, она справится и с Dog (подтип), ведь он предоставляет те же самые данные.

Такая стратегия называется Ковариантностью. Она разрешает «избыточность» типа и запрещает «недостаточность».

Контравариантность для параметров функций

Стратегия ковариантности действует везде, кроме одного места - определения совместимости функций.


type Foo1 = (param: number) => void
type Foo2 = (param1: number, param2: string) => void


Казалось бы, Foo2 является подтипом для Foo1 (Foo2 <: Foo1), а значит функцию типа Foo2 должно быть можно положить в переменную с типом Foo1:


let foo1: F1 = (param1: number, param2: string) => {} // Error


Но мы получаем ошибку. При этом наоборот все работает:


let foo2: F2 = (param1: number) => {} // Ok


Здесь тоже несложно понять, почему так. Программа ожидает функцию с одним аргументом и передает ей только один аргумент. Если на этом месте окажется функция, ожидающая больше аргументов, она их просто не получит.

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

Такая стратегия называется «контравариантность». Она разрешает «недостаточность» типа и запрещает «избыточность».

#typescript
👍3🌭1
Как устроена система типов в TypeScript

Статья (рус.): https://ru.hexlet.io/blog/posts/sistema-tipov-v-typescript

В статье разбирается стратегия ковариантности типов на основе принципе подстановки Барбары Лисков: поведение подтипа не должно противоречить поведению супертипа.

Объясняется, что подтип более строгий, чем супертип, так как ему соответствует меньше значений (собственно поэтому SubType < SuperType).

Немного говорится об иерархии типов в TypeScript, особенностях типов any, unknown, never и void.

Приводится простой способ проверить соответствие:


type A = string extends unknown ? true : false // true
type B = unknown extends string ? true : false // false


Статья также рассказывает о двух видах приведения типов:

⁃ восходящем (безопасное приведение подтипа к супертипу, происходит неявно)
⁃ нисходящее (приведение супертипа к подтипу, должно быть явным)

Обычно восходящее приведение происходит неявно, автоматически, но есть две ситуации, в которых оно запрещено - при работе с литеральными объектами (которые создаются на месте использования).

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

#typescript
👍3
TypeScript: Защитники типов (Type Guards)

Когда мы работаем с объединениями (union) в TypeScript, часто возникает необходимость совершить разные действия в зависимости от того, какой конкретно тип пришел.


function move(obj: Dog | Fish | Bird) {
if (obj.type === ‘dog’) obj.run()
else if (obj.type === ‘fish’) obj.swim()
else obj.fly()
}


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


function move(obj: Dog | Fish | Bird) {
if (obj.type === ‘dog’) {
// Здесь TS уже уверен, что работает именно с Dog
obj.run()
}
// …
}


Такие проверочные конструкции, которые понятны TS, называются «защитниками типа» - Type Guards. Они позволяют «сузить тип», выбрать нужный из объединения.

1. На основе типа данных

TypeScript понимает операторы instanceof и typeof.


function foo(param: number | string) {
if (typeof param === ‘string’) {
param.toUpperCase()
} else {
param.toFixed(2)
}

function move(obj: Dog | Fish | Bird) {
if (obj instanceof Dog) obj.run()
else if (obj instanceof Fish) obj.swim()
else obj.fly()
}


Эти операторы отлично распознаются компилятором внутри условий if-else, а также внутри тернарного оператора. Но не распознаются внутри конструкции switch.

2. С использование Tagged Union

Tagged Union (размеченное объединение) или Discriminated Union (дискриминантное объединение) - это объединение типов, у которых есть специальное общее поле со значением специфичным для конкретного типа. По этому полю эти типы можно различить.


class Bird {
type: ‘bird’ = ‘bird’
}
class Fish {
type: ‘fish’ = ‘fish’
}


Не имеет значения, как называется дискриминант, главное, чтобы он однозначно определял тип - TypeScript это понимает:


function move(obj: Dog | Fish | Bird) {
if (obj.type === ‘dog’) obj.run()
else if (obj.type === ‘fish’) obj.swim()
else obj.fly()
}


Такая проверка будет работать в любом условии: и в if-else, и в тернарном операторе, и даже в switch.

3. По наличию публичного поля

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


class Author {
public book: string = ‘Odyssey’
}
class Artist {
public painting: string = ‘Mona Lisa’
}

function getMasterpiece(creator: Author | Artist) {
let masterpiece;
if (‘book’ in creator) masterpiece = creator.book;
else masterpiece = creator.painting;
}


4. Функция-предикат

И наконец последняя (самая мощная) конструкция, которая может сузить объединение типов и которую может распознать TypeScript - это функция, которая возвращает предикат. Предикат - это однозначное утверждение, что значение, переданное этой функции, принадлежит к конкретному типу.

Такая функция оформляется особым образом - с помощью ключевого слова is:


function isDog(obj: Dog | Fish | Bird): obj is Dog {
return obj.type === ‘dog’
}


Функция принимает параметр obj и после проведения проверки утверждает, принадлежит ли obj к типу Dog.

Проверка внутри функции может быть абсолютно любая - TypeScript это не волнует. Нужно только, чтобы функция вернула логическое значение - true или false. Таким образом, ответственность за определение типа полностью перекладывается на разработчика.

Параметров тоже может быть сколько угодно, главное, чтобы один из них фигурировал в сигнатуре предиката.


function move(obj: Dog | Fish) {
if (isDog(obj)) {
obj.run()
} else {
obj.swim()
}
}


#typescript
👍3🔥2
Чем полезен тип unknown?

Статья (англ.): https://michaeluloth.com/programming-types-unknown-why-useful/

Автор статьи напоминает нам, что мы не всегда можем быть уверены в типе данных, которые приходят из какого-то внешнего источника (пользовательский ввод или api). И советует использовать для таких данных тип unknown до тех пор, пока они не пройдут явную валидацию.


// не доверяем пришедшим данным
const getUserInput = (): unknown => {/*...*/}

const safe = () => {
const data = getUserInput()

if (typeof data === 'string') { // явно валидируем
data.toUpperCase() // используем метод строки
} else {
// обрабатываем некорректный тип
}
}


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

Пример не самый идеальный и в целом можно решать проблему неожиданных типов другими путями (например, использовать try-catch), но эта статья делает две хорошие вещи:

- раскрывает сущность типа unknown
- напоминает, что нельзя доверять чужим данным

#ссылки #typescript
👍6