Forwarded from TypeScript Challenge
Type Challenges #11. Tuple to object
Ссылка на задачу
Условие
Нужно превратить массив в объект, ключами в котором являются элементы исходного массива.
Решение
Прежде всего, уточняем, что ключами объекта могут быть только строки/числа/символы. Вместо перечисления можно использовать встроенный тип
Для получения набора элементов массива используем конструкцию
#easy
Ссылка на задачу
Условие
Нужно превратить массив в объект, ключами в котором являются элементы исходного массива.
Решение
type TupleToObject<T extends ReadonlyArray<string | number | symbol>> = {
[P in T[number]]: P
}
Прежде всего, уточняем, что ключами объекта могут быть только строки/числа/символы. Вместо перечисления можно использовать встроенный тип
PropertyKey
:
type TupleToObject<T extends readonly PropertyKey[]> = {
[P in T[number]]: P
}
Для получения набора элементов массива используем конструкцию
T[number]
.#easy
GitHub
type-challenges/questions/00011-easy-tuple-to-object/README.md at main · type-challenges/type-challenges
Collection of TypeScript type challenges with online judge - type-challenges/type-challenges
👍6
Forwarded from TypeScript Challenge
Type Challenges #189. Awaited
Ссылка на задачу
Условие
Предположим, что у нас есть тип, завернутый в другой тип, например,
Решение
Чтобы решить задачку, нужно понять, какая именно обертка может быть. Посмотрим на тестовые кейсы:
Тут обычные промисы, а также кастомная структура с полем
Для описания обертки нам подойдет встроенная утилита
Но не забываем, что внутри обертки может быть еще одна обертка, поэтому нужно добавить немножко рекурсии:
#easy
Ссылка на задачу
Условие
Предположим, что у нас есть тип, завернутый в другой тип, например,
Promise<string>
. Нужно написать утилиту MyAwaited
для получения внутреннего типа.Решение
Чтобы решить задачку, нужно понять, какая именно обертка может быть. Посмотрим на тестовые кейсы:
type X = Promise<string>
type Y = Promise<{ field: number }>
type Z = Promise<Promise<string | number>>
type Z1 = Promise<Promise<Promise<string | boolean>>>
type T = { then: (onfulfilled: (arg: number) => any) => any }
type cases = [
Expect<Equal<MyAwaited<X>, string>>,
Expect<Equal<MyAwaited<Y>, { field: number }>>,
Expect<Equal<MyAwaited<Z>, string | number>>,
Expect<Equal<MyAwaited<Z1>, string | boolean>>,
Expect<Equal<MyAwaited<T>, number>>,
]
Тут обычные промисы, а также кастомная структура с полем
then
. Кроме того, внутри промиса может быть другой промис и так далее.Для описания обертки нам подойдет встроенная утилита
PromiseLike<T>
, осталось только вывести вложенный тип с помощью infer
:
type MyAwaited<T> = T extends PromiseLike<infer A> ? A : never;
Но не забываем, что внутри обертки может быть еще одна обертка, поэтому нужно добавить немножко рекурсии:
type MyAwaited<T> = T extends PromiseLike<infer A> ? (A extends PromiseLike<infer B> ? MyAwaited<A> : A) : never;
#easy
GitHub
type-challenges/questions/00189-easy-awaited/README.md at main · type-challenges/type-challenges
Collection of TypeScript type challenges with online judge - type-challenges/type-challenges
👍2
Redux ToolKit: краткий конспект
Собираюсь повторить RTK Query (а затем разобраться с React Query). Поэтому краткая выжимка по Redux Toolkit:
1. Мыслим слайсами
Функция createSlice создает сразу и action creators, и редьюсер, и даже набор селекторов, если мы работаем с EntityAdapter.
2. Асинхронщина
Вся асинхронщина через createAsyncThunk + extraReducers.
3. Селекторы
Селекторы напрямую в RTK не входят, для них используем reselect.
4. Связь с компонентами
В компонентах используем классические хуки
5. Создание хранилища
Стор создается с помощью configureStore.
***
В целом это все, в основном удобный синтаксический сахар, ничего сверхъестественного поверх базового Redux нет.
#управлениесостоянием #redux
Собираюсь повторить RTK Query (а затем разобраться с React Query). Поэтому краткая выжимка по Redux Toolkit:
1. Мыслим слайсами
Функция createSlice создает сразу и action creators, и редьюсер, и даже набор селекторов, если мы работаем с EntityAdapter.
2. Асинхронщина
Вся асинхронщина через createAsyncThunk + extraReducers.
3. Селекторы
Селекторы напрямую в RTK не входят, для них используем reselect.
4. Связь с компонентами
В компонентах используем классические хуки
useSelector
и useDispatch
.5. Создание хранилища
Стор создается с помощью configureStore.
***
В целом это все, в основном удобный синтаксический сахар, ничего сверхъестественного поверх базового Redux нет.
#управлениесостоянием #redux
Telegram
React Junior
Redux Toolkit. Слайсы и экшены
Переходим к отдельным редьюсерам, или слайсам (срезам). Будем рассматривать их вместе с экшенами, так как они тесно связаны, ведь редьюсеры обрабатывают экшены.
В обычном redux это довольно многословная часть кода: здесь…
Переходим к отдельным редьюсерам, или слайсам (срезам). Будем рассматривать их вместе с экшенами, так как они тесно связаны, ведь редьюсеры обрабатывают экшены.
В обычном redux это довольно многословная часть кода: здесь…
👍5
Forwarded from TypeScript Challenge
Type Challenges. Уровень easy. Резюме
Итак, мы прорешали 13 задачек уровня easy.
4 - Pick
7 - Readonly
11 - Tuple to Object
14 - First of Array
18 - Length of Tuple
43 - Exclude
189 - Awaited
268 - If
533 - Concat
898 - Includes
3057 - Push
3060 - Unshift
3312 - Parameters
Проведем небольшую ретроспективу и отметим основные моменты решений.
🟢 1. extends
Самое популярное ключевое слово в решениях.
Используется для уточнения типов в дженериках (9 задач):
Используется для сравнения в условных типах (2 задачи):
Используется в комбинации с ключевым словом
🟢 2. Копирование ключей объекта (3 задачи)
Используется для создания Mapped Types, когда новый тип повторяет ключи исходного
🟢 3. keyof
Ключевое слово для получения набора из всех ключей объекта (2 задачи)
🟢 4. Превращение массива типов в перечисление типов(1 задача)
Используем конструкцию
🟢 5. Использование readonly структур для работы с кортежами (3 задачи)
🟢 6. Деструктуризация массивов (5 задач)
#easy
Итак, мы прорешали 13 задачек уровня easy.
4 - Pick
7 - Readonly
11 - Tuple to Object
14 - First of Array
18 - Length of Tuple
43 - Exclude
189 - Awaited
268 - If
533 - Concat
898 - Includes
3057 - Push
3060 - Unshift
3312 - Parameters
Проведем небольшую ретроспективу и отметим основные моменты решений.
🟢 1. extends
Самое популярное ключевое слово в решениях.
Используется для уточнения типов в дженериках (9 задач):
type MyPick<T, K extends keyof T>
Используется для сравнения в условных типах (2 задачи):
type If<C extends boolean, T, F> = C extends true ? T : F
Используется в комбинации с ключевым словом
infer
для выведения одних типов из других (4 задачи):
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never
🟢 2. Копирование ключей объекта (3 задачи)
Используется для создания Mapped Types, когда новый тип повторяет ключи исходного
type MyReadonly<T> = {
readonly [K in keyof T]: T[K]
}
🟢 3. keyof
Ключевое слово для получения набора из всех ключей объекта (2 задачи)
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
🟢 4. Превращение массива типов в перечисление типов(1 задача)
Используем конструкцию
T[number]
.
type TupleToObject<T extends ReadonlyArray<string | number | symbol>> = {
[P in T[number]]: P
}
🟢 5. Использование readonly структур для работы с кортежами (3 задачи)
type Length<T extends readonly any[]> = T['length']
🟢 6. Деструктуризация массивов (5 задач)
type Push<T extends any[], U> = [...T, U]
#easy
Telegram
TypeScript Challenge
Type Challenges #4. Pick
Ссылка на задачу
Ссылка на песочницу с тестами
Нужно реализовать своими силами встроенную утилиту Pick<T, K>.
Утилита принимает тип T и создает на его основе новый тип, состоящий из свойств, которые указаны в наборе K.
Решение:…
Ссылка на задачу
Ссылка на песочницу с тестами
Нужно реализовать своими силами встроенную утилиту Pick<T, K>.
Утилита принимает тип T и создает на его основе новый тип, состоящий из свойств, которые указаны в наборе K.
Решение:…
👍4
RTK Query
Освежаю знания об RTK Query
RTK Query - это часть Redux Toolkit, которая предназначена для удобной работы с API. Она собирает все, что относится к API в одном месте и дает нам много приятного синтаксического сахара.
Заводим для API отдельный слайс, где будут лежать все данные, полученные с сервера. Но не обычный слайс, который
Подключить этот api-слайс к стору посложнее, чем обычный, тут нужно и редьюсер добавить, и специальный миддлвар.
В api-слайсе определяем функцию, которая будет осуществлять запросы -
На этом техническая подготовка заканчивается и начинается собственно логика обмена данными - поле
Примечание: можно выполнить запрос и «вручную» без использования хука.
Отправка данных на сервер работает практически так же, как и вытягивание, только эндпоинт нужно немного по-другому оформить (через `builder.mutation`). Хук тоже немного другой.
Самое приятное, что запросы к серверу кешируются, однако кеш иногда приходится сбрасывать. Это можно сделать напрямую руками, с помощью метода
Конечно, api-слайс можно расчленять (injectEndpoints).
RTK Query предлагает продвинутые техники работы с API, например, оптимистичное обновление, когда данные меняются сразу, а запрос выполняется уже в фоне. Еще есть возможность подписаться на источник событий (сокет-канал) , чтобы получать данные из него.
Предыдущее резюме по RTK Query, с большим количество технических подробностей.
#управлениесостоянием #rtkquery #обменданными
Освежаю знания об RTK Query
RTK Query - это часть Redux Toolkit, которая предназначена для удобной работы с API. Она собирает все, что относится к API в одном месте и дает нам много приятного синтаксического сахара.
Заводим для API отдельный слайс, где будут лежать все данные, полученные с сервера. Но не обычный слайс, который
createSlice
, а прокачанный слайс, который createApi
.Подключить этот api-слайс к стору посложнее, чем обычный, тут нужно и редьюсер добавить, и специальный миддлвар.
В api-слайсе определяем функцию, которая будет осуществлять запросы -
baseQuery
.На этом техническая подготовка заканчивается и начинается собственно логика обмена данными - поле
endpoints
. Тут мы получаем builder
и с его помощью (`builder.query`) создаем массив наших эндпоинтов (есть много разных настроек) . Каждый эндпоинт на выходе из слайса создает себе личный хук , который можно дернуть в любом месте приложения. А в этом хуке уже предусмотрена куча удобных поле - data
, isLoading
, error
и т.п. И дополнительно можно закинуть ряд настроек, вроде селектора . То есть вытаскивать данные из API со всеми удобствами мы уже можем. Можно даже сочетать все это дело с EnitityAdapter. Примечание: можно выполнить запрос и «вручную» без использования хука.
Отправка данных на сервер работает практически так же, как и вытягивание, только эндпоинт нужно немного по-другому оформить (через `builder.mutation`). Хук тоже немного другой.
Самое приятное, что запросы к серверу кешируются, однако кеш иногда приходится сбрасывать. Это можно сделать напрямую руками, с помощью метода
refetch
, который есть в хуках. Но лучше настроить зависимости с помощью системы тегов, чтобы RTK Query сбрасывала то, что нужно, самостоятельно. Конечно, api-слайс можно расчленять (injectEndpoints).
RTK Query предлагает продвинутые техники работы с API, например, оптимистичное обновление, когда данные меняются сразу, а запрос выполняется уже в фоне. Еще есть возможность подписаться на источник событий (сокет-канал) , чтобы получать данные из него.
Предыдущее резюме по RTK Query, с большим количество технических подробностей.
#управлениесостоянием #rtkquery #обменданными
Telegram
React Junior
Redux Essentials 16. RTK Query. Создание слайса
RTK Query - это еще один уровень абстракции над абстракциями Redux, позволяющий инкапсулировать работу с API: методы получения/обновления/удаления данных, состояние выполнения запроса, синхронизация состояния…
RTK Query - это еще один уровень абстракции над абстракциями Redux, позволяющий инкапсулировать работу с API: методы получения/обновления/удаления данных, состояние выполнения запроса, синхронизация состояния…
🔥3👍2
Forwarded from TypeScript Challenge
Type Challenges #12. Chainable Options
Ссылка на задачу
Ссылка на песочницу с тестами
Условие
У нас есть некий объект/класс с двумя методами:
Задача: типизировать класс
Важный момент: нельзя два раза передать в
Решение
Тут нам нужно решить две задачи:
- сохранять переданные ключи и значения, чтобы набрать нужный тип и вернуть его в
- запретить передавать один ключ дважды - для этого будем проверять, если ли у нас уже такой ключ и возвращать
#medium
Ссылка на задачу
Ссылка на песочницу с тестами
Условие
У нас есть некий объект/класс с двумя методами:
option(key, value)
и get()
. В первый можно передать имя поля (строка) и любое значение для него. Таким образом мы "набираем" себе объект с нужными полями. Причем вызовы метода option
можно объединять в цепочку (чейнить). Метод get
возвращает этот "собранный" объект.
declare const config: Chainable
const result = config
.option('foo', 123)
.option('name', 'type-challenges')
.option('bar', { value: 'Hello World' })
.get()
interface Result {
foo: number
name: string
bar: { value: string }
}
Задача: типизировать класс
Chainable
.Важный момент: нельзя два раза передать в
option
одно и то же свойство.Решение
Тут нам нужно решить две задачи:
- сохранять переданные ключи и значения, чтобы набрать нужный тип и вернуть его в
get
- для этого сделаем Chainable
дженериком и будем просто дописывать новые данные - запретить передавать один ключ дважды - для этого будем проверять, если ли у нас уже такой ключ и возвращать
never
если нет
type Chainable<T = {}> = {
option: <K extends string, V>(
key: K extends keyof T ? never : K,
value: V
) => Chainable<Omit<T, K> & Record<K, V>>;
get: () => T;
}
#medium
GitHub
type-challenges/questions/00012-medium-chainable-options/README.md at main · type-challenges/type-challenges
Collection of TypeScript type challenges with online judge - type-challenges/type-challenges
👍2
React Query за 10 минут
Видео (рус): https://www.youtube.com/watch?v=4-SOv7eTfoQ
Действительно хорошее введение в React Query. За 11 минут пишем простое React-приложение, которое демонстрирует основные возможности и концепции этой библиотеки. Это будет табличка с данными, к которой затем еще прикрутится пагинация и форма создания нового элемента.
Видео объясняет, как:
- получить данные через
- как получить состояние запроса (isLoading, error)
- как работает кеширование и как оно помогает улучшить пользовательский опыт
- зачем React Query делает фоновые запросы
- как обновить данные на сервере через
- как принудительно сбросить кеш запроса (инвалидация кеша)
React Query умеет все то же самое, что и RTK Query, но работает по-другому: в форме хуков, без общего стора. С RTK Query мы выносили все, что связано с api в отдельный слайс, в отдельный файл и работали с этими данными отдельно. React Query же работает как хук, внутри компонента и предоставляет все необходимое на месте.
По первому впечатлению: React Query попроще, полегче, поприятнее (обожаю хуки), чем RTK Query, но возможно не потянет более сложные задачи, код станет запутанным. RTK Query тесно работает со стором и это дает больше возможностей.
#управлениесостоянием
Видео (рус): https://www.youtube.com/watch?v=4-SOv7eTfoQ
Действительно хорошее введение в React Query. За 11 минут пишем простое React-приложение, которое демонстрирует основные возможности и концепции этой библиотеки. Это будет табличка с данными, к которой затем еще прикрутится пагинация и форма создания нового элемента.
Видео объясняет, как:
- получить данные через
useQuery(key, fetchFn)
, в том числе как сделать запрос с параметрами (например, с номером текущей страницы)- как получить состояние запроса (isLoading, error)
- как работает кеширование и как оно помогает улучшить пользовательский опыт
- зачем React Query делает фоновые запросы
- как обновить данные на сервере через
useMutation(mutationFn, config)
- как принудительно сбросить кеш запроса (инвалидация кеша)
React Query умеет все то же самое, что и RTK Query, но работает по-другому: в форме хуков, без общего стора. С RTK Query мы выносили все, что связано с api в отдельный слайс, в отдельный файл и работали с этими данными отдельно. React Query же работает как хук, внутри компонента и предоставляет все необходимое на месте.
По первому впечатлению: React Query попроще, полегче, поприятнее (обожаю хуки), чем RTK Query, но возможно не потянет более сложные задачи, код станет запутанным. RTK Query тесно работает со стором и это дает больше возможностей.
#управлениесостоянием
YouTube
React Query за 10 минут! Быстрый курс
В этом ролике я расскажу о том, что такое React Query, как делать запрос на бэкенд, пагинацию, отправку POST запроса (мутации) и расскажу про плюсы и минусы этой библиотеки. Всё это за 11 минут!
⏰ Таймкоды:
00:00:00 Начало
00:00:46 Пишем код с помощью Axios…
⏰ Таймкоды:
00:00:00 Начало
00:00:46 Пишем код с помощью Axios…
👍4
Что вам больше нравится?
Anonymous Poll
15%
React Query
13%
RTK Query
21%
Не могу сравнить, работал только с одним инструментом
29%
Не могу оценить, не работал ни с одним инструментом
12%
Оба хороши, каждый для своей задачи
4%
Оба хороши и взаимозаменяемы
4%
Ни то, ни то не нравится
2%
Что-то другое :)
👍2
Forwarded from Cat in Web
Решаем задачки
Если интересно порешать задачки, то заходите 🤓:
- TypeScript Challenge
- Leetcode Challenge
#challenges #реклама
Если интересно порешать задачки, то заходите 🤓:
- TypeScript Challenge
- Leetcode Challenge
#challenges #реклама
Telegram
Leetcode Challenge
Пытаюсь решать задачки с leetcode
🔥2
React Query vs RTK Query: всестороннее сравнение
Статья (англ.): https://www.frontendmag.com/insights/react-query-vs-rtk-query/
В статье сравниваются два популярных решения для взаимодействия React-приложений с удаленными источниками данных.
Сходство
🔹Оба инструмента используют лучшие performance-практики работы с удаленными данными:
- кеширование запросов
- предотвращение дублирующихся запросов
- повторные запросы в случае ошибок
🔹Оба инструмента предлагают решения для самых распространненных задач получения данных:
- пагинация - обновление списка данных
- оптимистические обновления
Отличие
Отличие заключается в архитектуре инструментов.
🔸React Query - это независимая библиотека. Ее функциональность реализована в виде хуков, то есть привязана к жизненному циклу React-компонентов.
🔸RTK Query - это инструмент, созданный поверх Redux Toolkit. Он работает в парадигме Redux: через редьюсеры и экшены, взаимодействует с глобальным стейтом приложения.
Кривая обучения
Автор считает, что и React Query, и RTK Query интуитивно понятны. Это довольно спорное утверждение, так как чтобы понять и то, и другое, требуется немного отформатировать свое понимание работы с данными.
Что касается RTK Query, тут вообще требуется сначала разобраться с Redux Toolkit - а это дело непростое.
Когда что использовать (привет, кэп)
RTK Query лучше использовать, если:
- В приложении уже используется Redux и Redux Toolkit. Тогда RTK Query доступна из коробки и уже интегрирована с прочими инструментами.
- Требуется сложная логика управления данными. RTK Query - это более мощный инструмент и на нем можно построить более сложную систему.
React Query лучше использовать, если:
- В приложении не используется глобальное состояние или запросы данных с ним не взаимодействуют. React Query не предоставляет никаких способов создания глобального стейта или взаимодействия с ним, все только внутри компонента.
- Требуется простая логика обработки данных.
- Не хочется долго разбираться.
- Не хочется тянуть зависимости в виде Redux Toolkit.
#управлениесостоянием #redux
Статья (англ.): https://www.frontendmag.com/insights/react-query-vs-rtk-query/
В статье сравниваются два популярных решения для взаимодействия React-приложений с удаленными источниками данных.
Сходство
🔹Оба инструмента используют лучшие performance-практики работы с удаленными данными:
- кеширование запросов
- предотвращение дублирующихся запросов
- повторные запросы в случае ошибок
🔹Оба инструмента предлагают решения для самых распространненных задач получения данных:
- пагинация - обновление списка данных
- оптимистические обновления
Отличие
Отличие заключается в архитектуре инструментов.
🔸React Query - это независимая библиотека. Ее функциональность реализована в виде хуков, то есть привязана к жизненному циклу React-компонентов.
🔸RTK Query - это инструмент, созданный поверх Redux Toolkit. Он работает в парадигме Redux: через редьюсеры и экшены, взаимодействует с глобальным стейтом приложения.
Кривая обучения
Автор считает, что и React Query, и RTK Query интуитивно понятны. Это довольно спорное утверждение, так как чтобы понять и то, и другое, требуется немного отформатировать свое понимание работы с данными.
Что касается RTK Query, тут вообще требуется сначала разобраться с Redux Toolkit - а это дело непростое.
Когда что использовать (привет, кэп)
RTK Query лучше использовать, если:
- В приложении уже используется Redux и Redux Toolkit. Тогда RTK Query доступна из коробки и уже интегрирована с прочими инструментами.
- Требуется сложная логика управления данными. RTK Query - это более мощный инструмент и на нем можно построить более сложную систему.
React Query лучше использовать, если:
- В приложении не используется глобальное состояние или запросы данных с ним не взаимодействуют. React Query не предоставляет никаких способов создания глобального стейта или взаимодействия с ним, все только внутри компонента.
- Требуется простая логика обработки данных.
- Не хочется долго разбираться.
- Не хочется тянуть зависимости в виде Redux Toolkit.
#управлениесостоянием #redux
Frontend Mag
React Query vs RTK Query: A Comprehensive Comparison - Frontend Mag
We compare and contrast React Query vs RTK Query to help developers understand the similarities and differences between these two popular data management libraries for React applications.
👍1
Forwarded from Cat in Web
Переменная объявлена без указания типа:
const a = 42
Какой тип выведет для нее TypeScript?
const a = 42
Какой тип выведет для нее TypeScript?
Anonymous Quiz
72%
number
10%
42
13%
any
4%
unknown
1%
undefined
👍1👏1
Forwarded from TypeScript Challenge
Type Challenge #1042. IsNever
Ссылка на задачу
Ссылка на песочницу с тестами
Условие
Реализовать утилиту
Решение
Очень важная задачка, так как она затрагивает тему "распределенных" условных типов (описание в документации). Когда у нас есть дженерик с условным типом (extends) и он получает в качестве параметра union, он себя ведет "распределенно", то есть применяется к каждому элементу объединения.
И тут мы иногда имеем странные вещи:
Штука в том, что распределенный условный тип
Есть лайфхак, чтобы отменить распределенное поведение: обернуть обе части условного типа в квадратные скобки:
Зная это, решение составить очень просто:
#medium
Ссылка на задачу
Ссылка на песочницу с тестами
Условие
Реализовать утилиту
IsNever<T>
, которая возвращает true, если в качестве аргумента получает never. В остальных случаях возвращает false.Решение
Очень важная задачка, так как она затрагивает тему "распределенных" условных типов (описание в документации). Когда у нас есть дженерик с условным типом (extends) и он получает в качестве параметра union, он себя ведет "распределенно", то есть применяется к каждому элементу объединения.
И тут мы иногда имеем странные вещи:
// тут все нормально (без дженериков)
type A = never extends never ? 'yes' : 'no'
let a: A // a: 'yes'
// а тут что-то пошло не так
type B<T> = T extends never ? 'yes' : 'no'
let b: B<never> // b: never
Штука в том, что распределенный условный тип
B
ожидает union, а получает never, который интерпретирует как пустой union - и вообще не выполняется.Есть лайфхак, чтобы отменить распределенное поведение: обернуть обе части условного типа в квадратные скобки:
type B<T> = [T] extends [never] ? 'yes' : 'no'
let b: B<never> // b: 'yes'
Зная это, решение составить очень просто:
type IsNever<T> = [T] extends [never] ? true : false
#medium
GitHub
type-challenges/questions/01042-medium-isnever/README.md at main · type-challenges/type-challenges
Collection of TypeScript type challenges with online judge - type-challenges/type-challenges
👍2
Вариантность и совместимость типов в TypeScript
Попробуем разобраться, что такое ковариантность и контравариантность и на что они влияют в TypeScript.
Подтип и супертип
Сначала определим основные понятия - подтип и супертип.
Подтип расширяет супертип.
⁃ Dog - это подтип, Animal - это супертип.
⁃ String - это подтип, Object - это супертип.
⁃ number - это подтип, any - это супертип.
Мы пишем, что подтип меньше супертипа (SubType <: SuperType), однако по факту, в подтипе больше информации, чем в супертипе, потому что подтип буквально расширяет супертип. Подтип «избыточен» по сравнению с супертипом.
Любое значение с типом SubType всегда является одновременно значением типа SuperType, так как имеет все необходимые для SuperType поля.
Структурная типизация
Важно понимать, что в TypeScript подтип и супертип необязательно связаны отношениями наследования (class Dog extends Animal). TypeScript реализует структурную типизацию и ориентируется только на структуру типов.
Типы Animal и Dog никак не связаны между собой, но Dog все равно является подтипом для Animal.
Ковариантность как основная стратегия
Когда мы пытаемся присвоить значение какой-то переменной в TypeScript, компилятор смотрит, а соответствует ли тип этого значения объявленному типу этой переменной, совместимы ли они, можно ли положить это значение в эту переменную.
Есть конкретные правила для определения совместимости. В большинстве случаев верно утверждение:
⁃ Вы можете безопасно использовать подтип там, где ожидается супертип.
Довольно просто понять, почему так сделано - значение с типом SubType также является значением типа SuperType, поэтому тут нет противоречий. Если программа готова справиться с Animal (супертип), значит, она справится и с Dog (подтип), ведь он предоставляет те же самые данные.
Такая стратегия называется Ковариантностью. Она разрешает «избыточность» типа и запрещает «недостаточность».
Контравариантность для параметров функций
Стратегия ковариантности действует везде, кроме одного места - определения совместимости функций.
Казалось бы, Foo2 является подтипом для Foo1 (Foo2 <: Foo1), а значит функцию типа Foo2 должно быть можно положить в переменную с типом Foo1:
Но мы получаем ошибку. При этом наоборот все работает:
Здесь тоже несложно понять, почему так. Программа ожидает функцию с одним аргументом и передает ей только один аргумент. Если на этом месте окажется функция, ожидающая больше аргументов, она их просто не получит.
В обратную сторону тоже все хорошо. Даже если программа ожидает функцию с двумя аргументами и передает два аргумента, а в сигнатуре функции указан лишь один, ничего не сломается. Функция получит свой аргумент, а второй - ненужный - просто проигнорирует.
Такая стратегия называется «контравариантность». Она разрешает «недостаточность» типа и запрещает «избыточность».
#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.
Приводится простой способ проверить соответствие:
Статья также рассказывает о двух видах приведения типов:
⁃ восходящем (безопасное приведение подтипа к супертипу, происходит неявно)
⁃ нисходящее (приведение супертипа к подтипу, должно быть явным)
Обычно восходящее приведение происходит неявно, автоматически, но есть две ситуации, в которых оно запрещено - при работе с литеральными объектами (которые создаются на месте использования).
В общем, хорошая статья, подводящая под довольно сложную тему хороший теоретический базис.
#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
Хекслет
Как устроена система типов в TypeScript
Перевели большую статью бывшего разработчика Amazon Web Services Хэ Чжэнхао и узнали, что такое иерархия типов в TypeScript и как они соотносятся между собой.
👍3
TypeScript: Защитники типов (Type Guards)
Когда мы работаем с объединениями (union) в TypeScript, часто возникает необходимость совершить разные действия в зависимости от того, какой конкретно тип пришел.
Эта логика обычно реализуется с помощью разнообразных условий и проверок. Эти условия пишутся на JavaScript, но TypeScript умеет понимать некоторые из этих конструкций. То есть, если правильно написать проверку, то внутри блока if компилятор TypeScript будет понимать, что работает уже с уточненным типом.
Такие проверочные конструкции, которые понятны TS, называются «защитниками типа» - Type Guards. Они позволяют «сузить тип», выбрать нужный из объединения.
1. На основе типа данных
TypeScript понимает операторы
Эти операторы отлично распознаются компилятором внутри условий if-else, а также внутри тернарного оператора. Но не распознаются внутри конструкции switch.
2. С использование Tagged Union
Tagged Union (размеченное объединение) или Discriminated Union (дискриминантное объединение) - это объединение типов, у которых есть специальное общее поле со значением специфичным для конкретного типа. По этому полю эти типы можно различить.
Не имеет значения, как называется дискриминант, главное, чтобы он однозначно определял тип - TypeScript это понимает:
Такая проверка будет работать в любом условии: и в if-else, и в тернарном операторе, и даже в switch.
3. По наличию публичного поля
Почти то же самое, что и предыдущий пункт, но тут мы проверяем не значение поля, а его наличие - с помощью оператора
4. Функция-предикат
И наконец последняя (самая мощная) конструкция, которая может сузить объединение типов и которую может распознать TypeScript - это функция, которая возвращает предикат. Предикат - это однозначное утверждение, что значение, переданное этой функции, принадлежит к конкретному типу.
Такая функция оформляется особым образом - с помощью ключевого слова
Функция принимает параметр
Проверка внутри функции может быть абсолютно любая - TypeScript это не волнует. Нужно только, чтобы функция вернула логическое значение - true или false. Таким образом, ответственность за определение типа полностью перекладывается на разработчика.
Параметров тоже может быть сколько угодно, главное, чтобы один из них фигурировал в сигнатуре предиката.
#typescript
Когда мы работаем с объединениями (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
React useTransition: решает все проблемы с производительностью или нет?
Статья (англ.): https://www.developerway.com/posts/use-transition
В React 18 появился "конкурентный рендеринг", цель которого решить проблемы с производительностью веб-приложений.
‼️Суть проблемы
Обновление стейта в React - штука синхронная. Как только стейт начал обновляться, поток выполнения кода блокируется. Пока не будут произведены все необходимые вычисления, пока не перерендерятся все изменившиеся компоненты, поток будет занят. Если в это время пользователь захочет повзаимодействовать со страницей, у него ничего не получится.
Например, мы собираемся вывести на страницу большой список из сотни пунктов. Каждый пункт - это немаленький компонент, который требует времени на отрисовку. Мы закидываем этот список в стейт - и обновление понеслось. Пока не отрендерятся все 100 пунктов, страница будет "висеть", пользователь не сможет никуда кликнуть, ничего прокрутить.
🔆 Переходные изменения для решения проблемы
React 18 вводит понятие "переходных изменений" - transitions. Переходное изменение в данном случае - это НЕ критичное изменение стейта. Например, вот этот самый список: отрисовать его не так критично, как сохранить интерактивность. Он и так большой, ничего страшного, если придется подождать чуть больше до его появления на странице.
Мы создаем transition и уже внутри него вносим изменения в стейт (закидываем список). React понимает, что это изменения и все изменения, вызванные им, не суперважные. Он начинает их обрабатывать, но если в это время пользователь начнет двигать мышкой, то React все бросит и переключится на пользователя.
Таким образом, "переходы" обрабатываются как бы "в фоне", хотя это не очень правильный термин, ведь JavaScript по-прежнему остается однопоточным языком. Правильнее будет сказать, что обработка переходного изменения просто прерывается, если появляются более срочные дела, а потом React снова к ней возвращается.
Выглядит это все вот так:
‼️Проблема переходных изменений
Проблема скрывается вот тут:
При запуске нового перехода флаг
В статье эта проблема очень хорошо разобрана на примере.
Берем простую страницу с тремя табами, у одного из которых очень тяжелый контент. Смену таба делаем переходным изменением, чтобы пока контент рендерится, пользователь не страдал. Это прекрасно решает проблему открытия тяжелого таба, но создает новую - при открытии легкого таба после тяжелого.
- мы хотим открыть тяжелый таб, запускаем переход
- пользователь ждет (показываем его лоадер), но в это время он может взаимодействовать со страницей (например, нажать другой таб)
- когда переход обработан, отображается тяжелый таб
- теперь пользователь кликает на легкий таб, казалось бы он должен открыться сразу же, но нет, интерфейс виснет
Виснет он именно потому, что меняется флаг
Решение у проблемы есть - это мемоизация. Но в целом следует хорошенько думать, прежде чем использовать конкурентный рендеринг. Это очень тонкий механизм, который требует ясного понимания подкапотной работы React.
#ссылки #concurrent #важно
Статья (англ.): https://www.developerway.com/posts/use-transition
В React 18 появился "конкурентный рендеринг", цель которого решить проблемы с производительностью веб-приложений.
‼️Суть проблемы
Обновление стейта в React - штука синхронная. Как только стейт начал обновляться, поток выполнения кода блокируется. Пока не будут произведены все необходимые вычисления, пока не перерендерятся все изменившиеся компоненты, поток будет занят. Если в это время пользователь захочет повзаимодействовать со страницей, у него ничего не получится.
Например, мы собираемся вывести на страницу большой список из сотни пунктов. Каждый пункт - это немаленький компонент, который требует времени на отрисовку. Мы закидываем этот список в стейт - и обновление понеслось. Пока не отрендерятся все 100 пунктов, страница будет "висеть", пользователь не сможет никуда кликнуть, ничего прокрутить.
🔆 Переходные изменения для решения проблемы
React 18 вводит понятие "переходных изменений" - transitions. Переходное изменение в данном случае - это НЕ критичное изменение стейта. Например, вот этот самый список: отрисовать его не так критично, как сохранить интерактивность. Он и так большой, ничего страшного, если придется подождать чуть больше до его появления на странице.
Мы создаем transition и уже внутри него вносим изменения в стейт (закидываем список). React понимает, что это изменения и все изменения, вызванные им, не суперважные. Он начинает их обрабатывать, но если в это время пользователь начнет двигать мышкой, то React все бросит и переключится на пользователя.
Таким образом, "переходы" обрабатываются как бы "в фоне", хотя это не очень правильный термин, ведь JavaScript по-прежнему остается однопоточным языком. Правильнее будет сказать, что обработка переходного изменения просто прерывается, если появляются более срочные дела, а потом React снова к ней возвращается.
Выглядит это все вот так:
const [list, setList] = useState([])
const [isPending, startTransition] = useTransition()
const showList = () => {
startTransition(() => {
setList(hugeList)
})
}
‼️Проблема переходных изменений
Проблема скрывается вот тут:
const [isPending, startTransition] = useTransition()
При запуске нового перехода флаг
isPending
изменяется, что вызывает дополнительный перерендер компонента. Если компонент сам по себе тяжелый, то этот перерендер сам заблокирует интерактивность страницы.В статье эта проблема очень хорошо разобрана на примере.
Берем простую страницу с тремя табами, у одного из которых очень тяжелый контент. Смену таба делаем переходным изменением, чтобы пока контент рендерится, пользователь не страдал. Это прекрасно решает проблему открытия тяжелого таба, но создает новую - при открытии легкого таба после тяжелого.
- мы хотим открыть тяжелый таб, запускаем переход
- пользователь ждет (показываем его лоадер), но в это время он может взаимодействовать со страницей (например, нажать другой таб)
- когда переход обработан, отображается тяжелый таб
- теперь пользователь кликает на легкий таб, казалось бы он должен открыться сразу же, но нет, интерфейс виснет
Виснет он именно потому, что меняется флаг
isPending
и текущий компонент перерендеривается, а текущий у нас сейчас - тяжелый таб. То есть прежде чем перейти на легкий таб, мы перерендериваем тяжелый.Решение у проблемы есть - это мемоизация. Но в целом следует хорошенько думать, прежде чем использовать конкурентный рендеринг. Это очень тонкий механизм, который требует ясного понимания подкапотной работы React.
#ссылки #concurrent #важно
Developerway
React useTransition: performance game changer or...?
Looking into what React Concurrent Rendering is, what hooks like useTransition and useDeferredValue do, what are the benefits and downsides of using them.
👍6🔥4
Принципы SOLID в React: так ли все с ними гладко? (Часть 1)
Статья (рус.):https://nuancesprog.ru/p/19540/
Автор разбирает тонкости и подводные камни SOLID применительно к React-приложениям.
Основная идея в том, что принципы SOLID в нашем сознании тесно связаны с ООП. Однако в React они также применимы, более того, иногда они применяются без нашего ведома. Поэтому нам стоит разобраться в этом и писать код более осмысленно.
Принцип единой ответственности
Должна быть только одна причина для существования компонента. Но у нас же есть "контейнерные" компоненты, которые могут управлять сразу несколькими дочерними подкомпонентами.
Автор предлагает рассматривать принцип единой ответственности с такой точки зрения:
- компоненты-менеджеры (контейнеры) отвечают только за композицию дочерних компонентов, сами при этом не имеют собственного JSX.
- а компоненты-работники (презентационные) ничего не знают про бизнес логику и только отрисовывают свою часть страницы.
Принцип открытости/закрытости
Компонент должен быть открыт для расширений и закрыт для модификаций. Тут важно понимать, что модификации имеют в виду только с точки зрения потребителя компонента, так как для самого компонента и то, и то - одно и то же. Это основа принципа обратной совместимости.
В React у нас есть несколько способов расширять компонент, не модифицируя его. Посмотрим на примере компонента кнопки, у которого есть проп icon - иконка. Иконка размещается перед текстом кнопки. Поступила задача - теперь должна быть возможность располагать иконку после текста. Есть несколько способов решения:
- добавить проп iconPlacement
- добавить новые свойства iconStart и iconEnd (продолжая поддерживать icon)
- или вообще создать новый компонент IconButton, наследующий от Button
Что выбрать, во многом зависит от ситуации.
Принцип подстановки Лисков
Довольно сложный для понимания принцип: вы должны быть в состоянии заменить любой супертип на его подтип, например, все Button на IconButton. Однако чаще всего это невозможно, так как IconButton требует дополнительных пропсов.
Однако мы и не должны это делать. Важно понимать, для чего мы создали IconButton. Он не должен заменять Button, в целом это совершенно независимый компонент, который никак не полагается на наследование от Button. Наоборот, он использует композицию.
Однако принцип Лисков вполне работает в React, в тех ситуациях, когда такая замена изначально предусматривается. Например, компонент Button фактически наследует от HTMLButtonElement - он должен и может везде его заменить.
#ссылки #паттерны
Статья (рус.):https://nuancesprog.ru/p/19540/
Автор разбирает тонкости и подводные камни SOLID применительно к React-приложениям.
Основная идея в том, что принципы SOLID в нашем сознании тесно связаны с ООП. Однако в React они также применимы, более того, иногда они применяются без нашего ведома. Поэтому нам стоит разобраться в этом и писать код более осмысленно.
Принцип единой ответственности
Должна быть только одна причина для существования компонента. Но у нас же есть "контейнерные" компоненты, которые могут управлять сразу несколькими дочерними подкомпонентами.
Автор предлагает рассматривать принцип единой ответственности с такой точки зрения:
- компоненты-менеджеры (контейнеры) отвечают только за композицию дочерних компонентов, сами при этом не имеют собственного JSX.
- а компоненты-работники (презентационные) ничего не знают про бизнес логику и только отрисовывают свою часть страницы.
Принцип открытости/закрытости
Компонент должен быть открыт для расширений и закрыт для модификаций. Тут важно понимать, что модификации имеют в виду только с точки зрения потребителя компонента, так как для самого компонента и то, и то - одно и то же. Это основа принципа обратной совместимости.
В React у нас есть несколько способов расширять компонент, не модифицируя его. Посмотрим на примере компонента кнопки, у которого есть проп icon - иконка. Иконка размещается перед текстом кнопки. Поступила задача - теперь должна быть возможность располагать иконку после текста. Есть несколько способов решения:
- добавить проп iconPlacement
- добавить новые свойства iconStart и iconEnd (продолжая поддерживать icon)
- или вообще создать новый компонент IconButton, наследующий от Button
Что выбрать, во многом зависит от ситуации.
Принцип подстановки Лисков
Довольно сложный для понимания принцип: вы должны быть в состоянии заменить любой супертип на его подтип, например, все Button на IconButton. Однако чаще всего это невозможно, так как IconButton требует дополнительных пропсов.
Однако мы и не должны это делать. Важно понимать, для чего мы создали IconButton. Он не должен заменять Button, в целом это совершенно независимый компонент, который никак не полагается на наследование от Button. Наоборот, он использует композицию.
Однако принцип Лисков вполне работает в React, в тех ситуациях, когда такая замена изначально предусматривается. Например, компонент Button фактически наследует от HTMLButtonElement - он должен и может везде его заменить.
#ссылки #паттерны
NOP::Nuances of programming
Принципы SOLID в React: так ли все с ними гладко?
Принципы SOLID принято описывать только в положительном свете. Поэтому многие нюансы часто упускаются из виду. Сегодня поговорим о том, применимы ли вообще принципы SOLID в React-приложении и какими ключевыми особенностями обладает каждый из них.
👍4❤2
Принципы SOLID в React: так ли все с ними гладко? (Часть 2)
Статья (рус.):https://nuancesprog.ru/p/19540/
Принцип разделения интерфейсов
Компонент не должен зависеть от свойств, которые он не использует. Прекрасный принцип, с которым сложно поспорить.
В качестве примера приводится компонент UserAvatar, который в качестве пропа получает объект типа User. И хотя аватар исползует только пару свойств юзера, он все равно вынужден знать об этом типе. Правильнее будет передать в компонент только те пропсы, которые ему действительно нужны - изменить его интерфейс.
Принцип инверсии зависимостей
Реализация должна зависеть от абстракций и не должна зависеть от других реализаций.
React в принципе построен на этом принципе, мы используем его, даже не замечая.
У нас есть два вида зависимостей:
- прямая зависимость - это импорты. Мы прямо импортируем что-то из соответствующих модулей, и если в этих модулях что-то изменится, нам будет больно.
- обратная зависимость - это то, что мы получаем в виде параметров. От них мы тоже зависим, но хотя бы можем их контролировать - определить абстракцию - интерфейс.
В итоге наш компонент UserAvatar в исходном виде нарушает сразу три приципа SOLID:
- единой ответственности (компонент в курсе бизнес-логики, знает, как устроен user)
- разделения интерфейсов
- инверсии зависимостей (импортирует вместо того, чтобы принимать, зависит от реализации, а не от абстракции)
Автор отдельно отмечает, что принцип инверсии зависимостей это не только про обработчики событий, которые мы передаем дочерним компонентам. На самом деле композиция в React - это и есть инверсия зависимостей.
Проп children - это тоже инверсия зависимостей. Благодаря ей компонент-работник, который ничего не должен знать о бизнес-логике, может содержать в себе компоненты-менеджеры, не нарушая ни одного принципа, так как ничего об этом не знает. Более того, компонент даже может определить интерфейс для этого пропа (например, принимать только строку) - то есть зависит от абстракции.
В React инверсия зависимостей - это перенос ответственности на родителя. Важно соблюдать разумные границы в реализации этого принципа, ведь ответственности может стать очень много. Иногда можно использовать и прямые зависимости.
#ссылки #паттерны
Статья (рус.):https://nuancesprog.ru/p/19540/
Принцип разделения интерфейсов
Компонент не должен зависеть от свойств, которые он не использует. Прекрасный принцип, с которым сложно поспорить.
В качестве примера приводится компонент UserAvatar, который в качестве пропа получает объект типа User. И хотя аватар исползует только пару свойств юзера, он все равно вынужден знать об этом типе. Правильнее будет передать в компонент только те пропсы, которые ему действительно нужны - изменить его интерфейс.
Принцип инверсии зависимостей
Реализация должна зависеть от абстракций и не должна зависеть от других реализаций.
React в принципе построен на этом принципе, мы используем его, даже не замечая.
У нас есть два вида зависимостей:
- прямая зависимость - это импорты. Мы прямо импортируем что-то из соответствующих модулей, и если в этих модулях что-то изменится, нам будет больно.
- обратная зависимость - это то, что мы получаем в виде параметров. От них мы тоже зависим, но хотя бы можем их контролировать - определить абстракцию - интерфейс.
В итоге наш компонент UserAvatar в исходном виде нарушает сразу три приципа SOLID:
- единой ответственности (компонент в курсе бизнес-логики, знает, как устроен user)
- разделения интерфейсов
- инверсии зависимостей (импортирует вместо того, чтобы принимать, зависит от реализации, а не от абстракции)
Автор отдельно отмечает, что принцип инверсии зависимостей это не только про обработчики событий, которые мы передаем дочерним компонентам. На самом деле композиция в React - это и есть инверсия зависимостей.
Проп children - это тоже инверсия зависимостей. Благодаря ей компонент-работник, который ничего не должен знать о бизнес-логике, может содержать в себе компоненты-менеджеры, не нарушая ни одного принципа, так как ничего об этом не знает. Более того, компонент даже может определить интерфейс для этого пропа (например, принимать только строку) - то есть зависит от абстракции.
В React инверсия зависимостей - это перенос ответственности на родителя. Важно соблюдать разумные границы в реализации этого принципа, ведь ответственности может стать очень много. Иногда можно использовать и прямые зависимости.
#ссылки #паттерны
NOP::Nuances of programming
Принципы SOLID в React: так ли все с ними гладко?
Принципы SOLID принято описывать только в положительном свете. Поэтому многие нюансы часто упускаются из виду. Сегодня поговорим о том, применимы ли вообще принципы SOLID в React-приложении и какими ключевыми особенностями обладает каждый из них.
👍6
Зачем писать юнит-тесты на фронтенд?
Статья (рус.): https://habr.com/ru/companies/nordclan/articles/755302/
Статья для тех, кто (я 🙃) очень плохо понимает, как вообще тестировать фронтенд. Методички не будет, зато будет практический пример, очень понятный.
Самый важный посыл - нужно отделять логику от UI - и помещать ее, например, в хуки. Тогда мы сможем отдельно протестировать хук. Кроме того, мы сможем подкинуть компоненту поддельную логику и проверить, как он с ней взаимодействует.
В качестве примера у нас тут игра судоку. Автор сначала пишет код без тестов и засовывает всю логику прямо в компонент, пишет тесты на то, что в ячейку можно ввести значение, но быстро сталкивается с тем, что никак нельзя проверить состояние игры - сохранилось ли введенное значение.
Пункт 1 - сначала пишем тесты, потом реализацию.
Тогда автор выносит всю логику работы с игровым полем в хук и тестирует отдельно хук. Когда речь идет о логике, кажется, вполне понятно, что нужно тестировать. Проверяем начальное состояние поля. Пытаемся изменить значение в конкретной ячейке и проверяем измененное состояние.
Пункт 2 - отделяем логику от UI.
А дальше финт ушами - мы передаем наш хук в компонент в качестве пропа! То есть всю логику целиком передаем в компонент, чтобы он ее использовал, не задумываясь о том, что там под капотом.
(Тут кстати не забываем про принцип инверсии зависимостей. Компонент строго определяет интерфейс пропа, который он хочет видеть, и полностью полагается на него, а не на конкретную реализацию.)
Теперь при тестировании компонента мы, например, можем подкинуть ему фальшивый хук, в котором замоканы все нужные методы.
Что же тестировать в самом компоненте?
Например то, что он вызывает нужный метод в нужный момент. Мы имитируем ввод данных в ячейку и проверяем, был ли вызван метод, изменяющий состояние.
Пункт 3 - когда логика протестирована, мы тестируем взаимодействие UI с логикой.
#ссылки #тестирование
Статья (рус.): https://habr.com/ru/companies/nordclan/articles/755302/
Статья для тех, кто (я 🙃) очень плохо понимает, как вообще тестировать фронтенд. Методички не будет, зато будет практический пример, очень понятный.
Самый важный посыл - нужно отделять логику от UI - и помещать ее, например, в хуки. Тогда мы сможем отдельно протестировать хук. Кроме того, мы сможем подкинуть компоненту поддельную логику и проверить, как он с ней взаимодействует.
В качестве примера у нас тут игра судоку. Автор сначала пишет код без тестов и засовывает всю логику прямо в компонент, пишет тесты на то, что в ячейку можно ввести значение, но быстро сталкивается с тем, что никак нельзя проверить состояние игры - сохранилось ли введенное значение.
Пункт 1 - сначала пишем тесты, потом реализацию.
Тогда автор выносит всю логику работы с игровым полем в хук и тестирует отдельно хук. Когда речь идет о логике, кажется, вполне понятно, что нужно тестировать. Проверяем начальное состояние поля. Пытаемся изменить значение в конкретной ячейке и проверяем измененное состояние.
Пункт 2 - отделяем логику от UI.
А дальше финт ушами - мы передаем наш хук в компонент в качестве пропа! То есть всю логику целиком передаем в компонент, чтобы он ее использовал, не задумываясь о том, что там под капотом.
(Тут кстати не забываем про принцип инверсии зависимостей. Компонент строго определяет интерфейс пропа, который он хочет видеть, и полностью полагается на него, а не на конкретную реализацию.)
Теперь при тестировании компонента мы, например, можем подкинуть ему фальшивый хук, в котором замоканы все нужные методы.
Что же тестировать в самом компоненте?
Например то, что он вызывает нужный метод в нужный момент. Мы имитируем ввод данных в ячейку и проверяем, был ли вызван метод, изменяющий состояние.
Пункт 3 - когда логика протестирована, мы тестируем взаимодействие UI с логикой.
#ссылки #тестирование
Хабр
Зачем писать юнит-тесты на фронтенд?
Привет, хабр! Меня зовут Александр, я работаю фронтенд-разработчиком в компании Nord Clan. Сегодня речь пойдет про тесты… Про юнит-тесты. Думаю, что почти все слышали про юнит-тесты, пробовали их...
👍2🔥1
Testing Library. Вступление
Незапланированно и стремительно перехожу к рассмотрению Testing Library и ее субпакетов.
Вот тут сайтик с документацией и примерами: https://testing-library.com/
Что это вообще за штука такая?
По сути это набор утилит для тестирования DOM, то есть нашего с вами фронтенда. Они работают с любым DOM-подобным ресурсом и помогают находить элементы и эмулировать события, как это мог бы делать пользователь.
Философия Testing Library:
«Чем больше тесты похожи на реальное использование вашего продукта, тем больше уверенности они дают».
Эта фраза преследует вас по всей доке. Она означает, что мы максимально отходим от реализации и работаем только с тем, что есть на странице - что видит пользователь. Тесты ничего не знают о вашем коде, а значит, они не сломаются, если код изменится.
Testing Library может работать с реальным DOM из браузера, а также с его эмуляциями (JSDOM, Jest).
У них есть пакеты с базовой функциональностью, а также отдельные модули для популярных фреймворков, которые добавляют специфические утилиты.
Итак, Testing Library это большой набор хелперов для работы с DOM в стиле реального пользователя. Ее удобно использовать в тестах, но это не тест-раннер и не полноценный фреймворк для тестирования - просто хелпер, не привязанный к другим технологиям (только к DOM).
Утверждается, что мы можем использовать Testing Library для любых видов тестов, от юнитов до e2e (есть даже интеграция с cypress).
Итак, основной принцип: мы работаем с DOM, а не с инстансами компонентов.
#тестирование #testinglibrary #документация
Незапланированно и стремительно перехожу к рассмотрению Testing Library и ее субпакетов.
Вот тут сайтик с документацией и примерами: https://testing-library.com/
Что это вообще за штука такая?
По сути это набор утилит для тестирования DOM, то есть нашего с вами фронтенда. Они работают с любым DOM-подобным ресурсом и помогают находить элементы и эмулировать события, как это мог бы делать пользователь.
Философия Testing Library:
«Чем больше тесты похожи на реальное использование вашего продукта, тем больше уверенности они дают».
Эта фраза преследует вас по всей доке. Она означает, что мы максимально отходим от реализации и работаем только с тем, что есть на странице - что видит пользователь. Тесты ничего не знают о вашем коде, а значит, они не сломаются, если код изменится.
Testing Library может работать с реальным DOM из браузера, а также с его эмуляциями (JSDOM, Jest).
У них есть пакеты с базовой функциональностью, а также отдельные модули для популярных фреймворков, которые добавляют специфические утилиты.
Итак, Testing Library это большой набор хелперов для работы с DOM в стиле реального пользователя. Ее удобно использовать в тестах, но это не тест-раннер и не полноценный фреймворк для тестирования - просто хелпер, не привязанный к другим технологиям (только к DOM).
Утверждается, что мы можем использовать Testing Library для любых видов тестов, от юнитов до e2e (есть даже интеграция с cypress).
Итак, основной принцип: мы работаем с DOM, а не с инстансами компонентов.
#тестирование #testinglibrary #документация
Testing-Library
Testing Library | Testing Library
Simple and complete testing utilities that encourage good testing practices
👍3