Redux-saga. Композиция саг
👉 All
Чтобы запустить несколько саг параллельно, используем эффект
Идея такая же, как в
👉 Race
Есть и аналог
Тут мы ждем, пока выполнится запрос, но не дольше одной секунды. Если запрос успеет выполниться, то его результаты придут в переменную
Удобно сочетать
Не следует использовать эффект
#redux #управлениесостоянием #примерыкода #saga
👉 All
Чтобы запустить несколько саг параллельно, используем эффект
all
:
const results = yield all([call(task1), call(task2), ...]);
yield put(showResults(results));
Идея такая же, как в
Promise.all
- дожидаемся, пока все саги в массиве выполнятся, и продолжаем исполнять код дальше.👉 Race
Есть и аналог
Promise.race
- эффект race
, но с немного другим синтаксисом:
const {posts, timeout} = yield race({
posts: call(fetchApi, '/posts'),
timeout: delay(1000)
})
if (posts)
yield put({type: 'POSTS_RECEIVED', posts})
else
yield put({type: 'TIMEOUT_ERROR'})
Тут мы ждем, пока выполнится запрос, но не дольше одной секунды. Если запрос успеет выполниться, то его результаты придут в переменную
posts
.Удобно сочетать
race
с эффектом cancel
, чтобы отменять "проигравшие" гонку саги.Не следует использовать эффект
fork
внутри race
, так как он неблокирующий и поэтому всегда будет "выполняться" первым.#redux #управлениесостоянием #примерыкода #saga
🔥1
Redux-saga. Корневая сага
Еще пару слов об организации корневой саги с учетом новых знаний про блокирующие/неблокирующие эффекты, а также про attached/detached ветки.
В корневой саге обычно запускается несколько дочерних саг и предполагается, что это происходит одновременно. Поэтому нам нужен неблокирующий способ запуска - есть несколько вариантов.
🔹Параллельный запуск с all
Тут нужно учитывать, что сам эффект all блокирующий и если после него идет какой-то код, то он будет выполнен только после завершения всех саг в массиве.
🔸Неблокирующие вызовы с fork
🔹All + fork
Чтобы решить проблему блокирования корневой саги эффектом all, можно сочетать его с fork, чтобы вызовы всех саг были неблокирующими:
Так все саги запустятся параллельно, но при этом all не будет ждать, пока все они выполнятся.
При этом все созданные ветки остаются привязанными к корневой саге, поэтому если одна из них упадет с ошибкой, корневая сага тоже упадет.
🔸Отделенные ветки со spawn
Не самое популярное решение, но тоже имеет место быть. Можно запустить дочерние саги внутри корневой с помощью spawn. Тогда они будут отделены от родителя и их ошибки не повляют на работу корневой саги.
🔹Перезапуск при падении
И еще один примерчик на сладкое - возможность перезапуска саги, если она не завелась:
Ветки создаются с помощью spawn, чтобы не влиять на родителя и не блокировать его выполнение.
Каждая дочерняя сага запускается с помощью блокирующего эффекта call, поэтому мы можем использовать блок try-catch для обработки ошибок. Если сага удачно запустилась, то на этом все и заканчивается, если же нет, то происходит новый виток бесконечного цикла while(true) и выполняется новая попытка.
В целом все эти примеры актуальны не только для корневой саги, но и для любых других.
#redux #управлениесостоянием #примерыкода #saga
Еще пару слов об организации корневой саги с учетом новых знаний про блокирующие/неблокирующие эффекты, а также про attached/detached ветки.
В корневой саге обычно запускается несколько дочерних саг и предполагается, что это происходит одновременно. Поэтому нам нужен неблокирующий способ запуска - есть несколько вариантов.
🔹Параллельный запуск с all
function* rootSaga() {
yield all([
saga1(),
saga1()
])
}
Тут нужно учитывать, что сам эффект all блокирующий и если после него идет какой-то код, то он будет выполнен только после завершения всех саг в массиве.
🔸Неблокирующие вызовы с fork
yield fork(saga1)
yield fork(saga2)
yield fork(saga3)
🔹All + fork
Чтобы решить проблему блокирования корневой саги эффектом all, можно сочетать его с fork, чтобы вызовы всех саг были неблокирующими:
yield all([ fork(saga1), fork(saga2), fork(saga3) ])
Так все саги запустятся параллельно, но при этом all не будет ждать, пока все они выполнятся.
При этом все созданные ветки остаются привязанными к корневой саге, поэтому если одна из них упадет с ошибкой, корневая сага тоже упадет.
🔸Отделенные ветки со spawn
Не самое популярное решение, но тоже имеет место быть. Можно запустить дочерние саги внутри корневой с помощью spawn. Тогда они будут отделены от родителя и их ошибки не повляют на работу корневой саги.
yield spawn(saga1)
yield spawn(saga2)
yield spawn(saga3)
🔹Перезапуск при падении
И еще один примерчик на сладкое - возможность перезапуска саги, если она не завелась:
const sagas = [
saga1,
saga2,
saga3,
];
yield all(sagas.map(saga =>
spawn(function* () {
while (true) {
try {
yield call(saga)
break
} catch (e) {
console.log(e)
}
}
}))
);
Ветки создаются с помощью spawn, чтобы не влиять на родителя и не блокировать его выполнение.
Каждая дочерняя сага запускается с помощью блокирующего эффекта call, поэтому мы можем использовать блок try-catch для обработки ошибок. Если сага удачно запустилась, то на этом все и заканчивается, если же нет, то происходит новый виток бесконечного цикла while(true) и выполняется новая попытка.
В целом все эти примеры актуальны не только для корневой саги, но и для любых других.
#redux #управлениесостоянием #примерыкода #saga
👍2
Redux-saga. ActionChannel
Эффект
А что делать, если мы не хотим пропускать события, но при этом необходимо обрабатывать их последовательно, а не параллельно?
Для этого в саге есть эффект
Тут важно использовать именно
#redux #управлениесостоянием #примерыкода #saga
Эффект
takeEvery
позволяют ловить и обрабатывать каждый экшен - все обработчики выполняются параллельно.take
+ fork
делает то же самое, а take
+ call
позволяет игнорировать новые события, пока не будет обработано предыдущее.А что делать, если мы не хотим пропускать события, но при этом необходимо обрабатывать их последовательно, а не параллельно?
Для этого в саге есть эффект
actionChannel
- канал экшенов. Нужно создать канал, а потом подписаться на него (а не на сам экшен). Канал будет буферизировать поступающие экшены, пока не обработан предыдущий.
function* watchRequests() {
// создаем канал
const requestChan = yield actionChannel('REQUEST')
while (true) {
// подписываемся на него
const {payload} = yield take(requestChan)
// обрабатываем экшен
yield call(handleRequest, payload)
}
}
Тут важно использовать именно
call
для обработки, так как она должна быть блокирующей.#redux #управлениесостоянием #примерыкода #saga
👍2
Redux-saga. EventChannel
ActionChannel из прошлого поста предназначен для работы с redux-стором и обычными экшенами - он просто особым образом их обрабатывает.
Но можно создать похожий канал для событий любого типа, не связанных со стором (например, на веб-сокеты). В документации приводится пример обратного счетчика, который отправляет событие каждую секунду, пока не дойдет до нуля. То есть мы создаем дополнительный источник экшенов, кроме стора - и так же можем на него подписаться.
Создается такой канал с помощью функции
В
То есть мы находим внешний источник нужных нам событий, подписываемся на событие, а когда оно происходит отправляем его в канал. А на этот канал потом можно будет подписаться в любой саге, как на экшены стора.
Чтобы закрыть канал (если события перестали поступать, нужно использовать константу `END`).
Функция-подписчик должна вернуть метод для сброса эффектов (как в хуке
Использование канала событий:
Канал можно закрыть и извне, вызвав его метод
#redux #управлениесостоянием #примерыкода #saga
ActionChannel из прошлого поста предназначен для работы с redux-стором и обычными экшенами - он просто особым образом их обрабатывает.
Но можно создать похожий канал для событий любого типа, не связанных со стором (например, на веб-сокеты). В документации приводится пример обратного счетчика, который отправляет событие каждую секунду, пока не дойдет до нуля. То есть мы создаем дополнительный источник экшенов, кроме стора - и так же можем на него подписаться.
Создается такой канал с помощью функции
eventChannel
:
import { eventChannel, END } from 'redux-saga'
function createCountdownChannel(seconds) {
const channel = eventChannel(function(emit) {
const interval = setInterval(function() {
seconds--;
if (seconds > 0) emit(seconds);
else emit(END);
}, 1000);
return function() {
clearInterval(interval);
}
})
return channel;
}
В
eventChannel
нужно передать функцию, которая будет подписываться на источник событий. В качестве аргумента она получает эмиттер - функцию, которая будет диспатчить экшены в канал событий.То есть мы находим внешний источник нужных нам событий, подписываемся на событие, а когда оно происходит отправляем его в канал. А на этот канал потом можно будет подписаться в любой саге, как на экшены стора.
Чтобы закрыть канал (если события перестали поступать, нужно использовать константу `END`).
Функция-подписчик должна вернуть метод для сброса эффектов (как в хуке
useEffect
в React).Использование канала событий:
export function* saga() {
const channel = yield call(createCountdownChannel, 10)
try {
while (true) {
let seconds = yield take(channel)
console.log(`countdown: ${seconds}`)
}
} finally {
console.log('countdown terminated')
}
}
Канал можно закрыть и извне, вызвав его метод
close
:
channel.close()
#redux #управлениесостоянием #примерыкода #saga
👍2
Redux-saga. Простые каналы
У нас уже были каналы для экшенов из стора (ActionChannel), каналы для событий из внешних источников (EventChannel).
И в дополнение ко всему этому есть самые простые каналы - Channel. Их отличие в том, что изначально они ни на что не подписаны, ни на стор, ни на внешний источник событий. Это совершенно пустой канал, в котором нет ни одного события.
Одна сага может его создать, другая сага может на него подписаться. Пушить события в него нужно вручную.
Зачем все это нужно?
Документация предлагает нам следующий кейс: с текущими инструментами мы можем либо обрабатывать экшены последовательно, не начиная новый, пока не закончится предыдущий (`take`), или же параллельно все (`takeEvery`). Но не можем, например, обрабатывать параллельно только 3 экшена. И пока не закончится обработка хотя бы одного из нех, не начинать следующий.
Вот для этого нам и нужны каналы.
Что тут происходит?
Мы создаем канал, создаем три слушателя для этого канала. Затем как обычно ловим нужный экшен и просто кладем его в канал.
При попадании экшена в канал, его получит первый свободный слушатель (если свободны несколько, получит все равно только один). Если все слушатели уже заняты, событие останется висеть в канале до тех пор, пока кто-то не освободится.
#redux #управлениесостоянием #примерыкода #saga
У нас уже были каналы для экшенов из стора (ActionChannel), каналы для событий из внешних источников (EventChannel).
И в дополнение ко всему этому есть самые простые каналы - Channel. Их отличие в том, что изначально они ни на что не подписаны, ни на стор, ни на внешний источник событий. Это совершенно пустой канал, в котором нет ни одного события.
Одна сага может его создать, другая сага может на него подписаться. Пушить события в него нужно вручную.
Зачем все это нужно?
Документация предлагает нам следующий кейс: с текущими инструментами мы можем либо обрабатывать экшены последовательно, не начиная новый, пока не закончится предыдущий (`take`), или же параллельно все (`takeEvery`). Но не можем, например, обрабатывать параллельно только 3 экшена. И пока не закончится обработка хотя бы одного из нех, не начинать следующий.
Вот для этого нам и нужны каналы.
import { channel } from 'redux-saga'
function* watcher() {
const chan = yield call(channel); // создаем канал
// подписываемся на этот канал три раза
yield fork(handleRequest, chan);
yield fork(handleRequest, chan);
yield fork(handleRequest, chan);
while (true) {
// ловим экшен REQUEST и кладем в канал
const {payload} = yield take('REQUEST')
yield put(chan, payload)
}
}
function* handler(chan) {
while (true) {
// ловим событие из канала
const payload = yield take(chan)
...
}
}
Что тут происходит?
Мы создаем канал, создаем три слушателя для этого канала. Затем как обычно ловим нужный экшен и просто кладем его в канал.
При попадании экшена в канал, его получит первый свободный слушатель (если свободны несколько, получит все равно только один). Если все слушатели уже заняты, событие останется висеть в канале до тех пор, пока кто-то не освободится.
#redux #управлениесостоянием #примерыкода #saga
Telegram
React Junior
Redux-saga. ActionChannel
Эффект takeEvery позволяют ловить и обрабатывать каждый экшен - все обработчики выполняются параллельно.
take + fork делает то же самое, а take + call позволяет игнорировать новые события, пока не будет обработано предыдущее.
…
Эффект takeEvery позволяют ловить и обрабатывать каждый экшен - все обработчики выполняются параллельно.
take + fork делает то же самое, а take + call позволяет игнорировать новые события, пока не будет обработано предыдущее.
…
👍2
Redux-saga. Multicast-каналы
И наконец в дополнение ко всем уже разобранным каналам у нас есть multicast-каналы. Они работают так же, как и обычные каналы (channel), только уведомление о поступившем событии получают ВСЕ подписчики.
Таким образом, на одно событие можно подписать несколько обработчиков с разной логикой.
#redux #управлениесостоянием #примерыкода #saga
И наконец в дополнение ко всем уже разобранным каналам у нас есть multicast-каналы. Они работают так же, как и обычные каналы (channel), только уведомление о поступившем событии получают ВСЕ подписчики.
Таким образом, на одно событие можно подписать несколько обработчиков с разной логикой.
import { multicastChannel } from 'redux-saga'
function* watchRequests() {
const channel = yield call(multicastChannel)
// подписываем разные обработчики
yield fork(handler1, channel)
yield fork(handler2, channel)
while (true) {
const { payload } = yield take('REQUEST')
yield put(channel, payload)
}
}
#redux #управлениесостоянием #примерыкода #saga
Telegram
React Junior
Redux-saga. Простые каналы
У нас уже были каналы для экшенов из стора (ActionChannel), каналы для событий из внешних источников (EventChannel).
И в дополнение ко всему этому есть самые простые каналы - Channel. Их отличие в том, что изначально они ни на…
У нас уже были каналы для экшенов из стора (ActionChannel), каналы для событий из внешних источников (EventChannel).
И в дополнение ко всему этому есть самые простые каналы - Channel. Их отличие в том, что изначально они ни на…
👍2
Redux-saga. Кастомный I/O
Обычно мы подключаем саги к стору приложения с помощью миддлвара. Эффекты
Но можно запустить сагу отдельно от стора, если это необходимо.
Для этого нам нужно создать кастомный "стор" с полями
-
-
- и
То есть у этого стора должен быть канал, в который будут поступать события - его создаем с помощью функции
Для запуска вызываем функцию
#redux #управлениесостоянием #примерыкода #saga
Обычно мы подключаем саги к стору приложения с помощью миддлвара. Эффекты
take
и put
связываются со стором и слушают его/диспатчат в него новые экшены.Но можно запустить сагу отдельно от стора, если это необходимо.
Для этого нам нужно создать кастомный "стор" с полями
-
channel
, -
dispatch
- и
getState
. То есть у этого стора должен быть канал, в который будут поступать события - его создаем с помощью функции
stdChannel
. Этот канал нужно соединить с внешним I/O.Для запуска вызываем функцию
runSaga
и передаем ей новый стор и собственно сагу.
import { runSaga, stdChannel } from 'redux-saga'
const emitter = new EventEmitter()
const channel = stdChannel()
emitter.on("action", channel.put)
const myIO = {
channel,
dispatch(output) {
emitter.emit("action", output)
},
getState() {
return state
}
}
runSaga(
myIO,
function* saga() { ... },
)
#redux #управлениесостоянием #примерыкода #saga
👍2
Redux-saga. Рецепты
На страничке Recipes https://redux-saga.js.org/docs/recipes в документации redux-saga есть несколько полезных сниппетов:
- throttling
С использованием встроенного эффекта
- debouncing
Сохраняем активный воркер и отменяем его (`cancel`), если пришло новое событие, либо используем встроенный эффект
- повторное выполнение неудачных XHR-запросов
С использованием встроенного эффекта
- функционал Отменить последнее действие
С помощью
- batching нескольких экшенов
С использованием библиотеки redux-batched-actions.
#redux #управлениесостоянием #документация #saga
На страничке Recipes https://redux-saga.js.org/docs/recipes в документации redux-saga есть несколько полезных сниппетов:
- throttling
С использованием встроенного эффекта
throttle
.- debouncing
Сохраняем активный воркер и отменяем его (`cancel`), если пришло новое событие, либо используем встроенный эффект
takeLatest
.- повторное выполнение неудачных XHR-запросов
С использованием встроенного эффекта
retry
или комбинацией базовых эффектов call
, take
, delay
и блока try-catch.- функционал Отменить последнее действие
С помощью
spawn
и race
.- batching нескольких экшенов
С использованием библиотеки redux-batched-actions.
#redux #управлениесостоянием #документация #saga
redux-saga.js.org
Recipes | Redux-Saga
Throttling
👍3
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