withThrottleComputed (для синхронных вычислений)
Применяется на входе в реактивный граф для изоляции сырого спама данных.
- Когда использовать: Когда входной сигнал генерирует сотни событий в секунду (координаты мыши, пиксели скролла), и вам нужно «сжать» этот поток до дозированных тиков (например, раз в 300 мс) перед тем, как пустить его дальше в другие сервисы, computed-свойства или тяжелые UI-компоненты.
- Как это работает: Он перехватывает чтение сырого сигнала, подписывается на него, но обновляет результирующий стейт строго по таймеру, отсекая промежуточный дребезг.
typescript
import { AbstractService, withThrottleComputed } from '@pravosleva/reactive-engine'
export class Throttle2DLogic extends AbstractService {
// ...
// Пример: Троттлинг мышиного трекинга перед расчетом тяжелой гео-модели
public rawCoords = this.engine.signal({ x: 0, y: 0 }, 'signal:raw-coords')
// Зажимаем спам координат на этапе вычислений
public throttledCoords = withThrottleComputed(
this.engine,
() => this.rawCoords.value,
{ limit: 300 },
'computed:throttled-coords'
)
}Пример 212
Ниже приведен готовый пример использования сервиса:
ts
import { AbstractService, withThrottleComputed } from '@pravosleva/reactive-engine'
export class Throttle2DLogic extends AbstractService {
// Сигнал, куда записываются сырые координаты X и Y при движении мыши
public coordsSignal = this.createSignal<{ x: number; y: number }>({ x: 0, y: 0 }, 'example-212:signal:coords')
// 1. Сырой сигнал мыши (спамит на каждый пиксель)
public rawCoords = this.engine.signal({ x: 0, y: 0 }, 'example-212:signal:raw-coords')
/**
* 2.1 🚫 Альтернативный вариант (он тоже будет работать)
* ☝️ НО НЕ для этого случая, когда оригинальный синал генерирует сотни изменений в секунду (спам),
* т.к. чтение сырого сигнала требует неоправдано больше ресурсов для этого случая.
* Реактивный ресурс, обёрнутый в декоратор withThrottle.
* Он считывает координаты и выполняет "тяжёлый" фейковый расчёт зон.
* Троттлинг гарантирует частоту выполнения не чаще 1 раза в 300 мс.
*/
// public analyticsResource = this.engine.resource(
// withThrottle(
// async (coords, _abortSignal) => {
// // Имитируем небольшую задержку расчёта (например, обращение к гео-модели)
// await new Promise((resolve) => setTimeout(resolve, 100))
// const sector = coords.x < 200 ? 'Левый сектор' : 'Правый сектор'
// return `Аналитика GPU: [${sector}] для точки X: ${coords.x}, Y: ${coords.y}`
// },
// { limit: 300 } // Лимит троттлинга 300 мс
// ),
// this.coordsSignal,
// 'example-212:resource:analytics'
// )
/**
* 2.2 ✅ Ресурс слушает уже ЗАТРOТТЛЕННЫЙ сигнал.
* Он перейдет в статус loading ровно 1 раз в 300мс! Счетчик изменений будет равен 1!
*/
// Вычисляемый затроттленный сигнал (тикает строго 1 раз в 300мс)
public throttledCoords = withThrottleComputed(
this.engine,
() => this.coordsSignal.value,
{ limit: 300 },
'[IS_OPTIMIZED=1]:example-212:computed:throttled-coords'
)
public analyticsResource = this.engine.resource(
async (coords) => {
await new Promise((resolve) => setTimeout(resolve, 100))
const sector = coords.x < 200 ? 'Левый сектор' : 'Правый сектор'
return `Аналитика GPU: [${sector}] для точки X: ${coords.x}, Y: ${coords.y}`
},
this.throttledCoords,
'example-212:resource:analytics'
)
/**
* Экшен обновления координат из UI
*/
public updateCoords(x: number, y: number) {
this.coordsSignal.value = { x, y }
}
}tsx
import { MouseEvent } from 'react'
import { ReactiveEngine, useReactiveValue } from '@pravosleva/reactive-engine/react'
import { Throttle2DLogic } from './service.Throttle2DLogic'
import baseClasses from '~/ui.common.module.scss'
import clsx from 'clsx'
const engine = new ReactiveEngine({
logger: {
isEnabled: true, // Включаем логгер
traceTime: true, // Добавляем вывод таймингов по желанию
filter: /^example-212:*/ // Можно фильтровать только нужные логи
}
})
export const Throttle2DExample = () => {
const logic = engine.inject(Throttle2DLogic)
// Подписываемся на сырой сигнал координат и обработанный ресурс аналитики
// -- Будьте внимательны: Оцените разницу на что подписаться!
// ⛔ const coords = engine.use(logic.coordsSignal)
const coords = engine.use(logic.throttledCoords)
// --
const { loading, data: analyticsResult } = useReactiveValue(logic.analyticsResource)
// Перехват движения мыши внутри зоны
const handleMouseMove = (e: MouseEvent<HTMLDivElement>) => {
const rect = e.currentTarget.getBoundingClientRect()
const x = Math.round(e.clientX - rect.left)
const y = Math.round(e.clientY - rect.top)
// Спамим изменения в сигнал на каждый пиксель движения
logic.updateCoords(x, y)
}
return (
<div
className={clsx(baseClasses.unit, baseClasses.stack2)}
style={{ fontFamily: 'system-ui', width: '600px' }}
>
<div className={baseClasses.absoluteUnitLabel}>Throttle Computed Mouse Tracking Demo</div>
<div className={baseClasses.stack1}>
{/* Индикаторы текущего состояния */}
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 'small' }}>
<span>Сырые координаты из сигнала:</span>
<span style={{ fontFamily: 'monospace', color: '#00b4d8' }}>X: {coords.x}, Y: {coords.y}</span>
</div>
{/* Интерактивная зона для вождения мышкой */}
<div
onMouseMove={handleMouseMove}
style={{ width: '100%', height: '180px', background: '#15151a', borderRadius: '8px', cursor: 'crosshair', display: 'flex', alignItems: 'center', justifyContent: 'center', userSelect: 'none' }}
>
<span style={{ color: '#aaa', fontSize: '13px' }}>Двигайте курсор внутри этой зоны</span>
</div>
</div>
{/* Терминал вывода затроттленной аналитики */}
<div className={baseClasses.stack1}>
<div style={{ fontSize: 'small', display: 'flex', justifyContent: 'space-between', gap: '8px' }}>
<span>Результат обработки (не чаще 1 раза в 300мс):</span>
{loading && <span style={{ color: '#e6af2e' }}>⏳ Расчёт...</span>}
</div>
<div style={{ background: '#111', borderRadius: '8px', padding: '12px', minHeight: '44px', display: 'flex', alignItems: 'center', fontSize: '13px', fontFamily: 'monospace', color: '#4caf50' }}>
{analyticsResult || <span style={{ color: '#aaa' }}>Запустите движение мыши...</span>}
</div>
</div>
</div>
)
}