useFrameExtended
Описание
useFrameExtended — хук, который организует расширенный кадровый цикл на базе requestAnimationFrame и предоставляет методы управления: start, stop, restart. Колбэк вызывается на каждом кадре и получает тайминги:
frame— номер кадра, начиная с1послеstart()/restart();deltaTime— миллисекунды с предыдущего кадра;timeSinceLastStart— миллисекунды с момента последнегоstart()/restart();timeSinceStart— миллисекунды с самого первого запуска хука (сбрасываетсяrestart()).
Сигнатура
ts
function useFrameExtended(
callback: UseFrameExtendedCallback,
): UseFrameExtendedReturn;Параметры
callback— функция, вызываемая на каждом кадре с объектом таймингов.
Возвращает:
UseFrameExtendedReturn— объект управления циклом:start(): void— запуск цикла (если вызван до маунта, запуск отложится до маунта);stop(): void— остановка текущего цикла;restart(): void— остановка, сброс счётчиков и новый запуск.
Примеры
1) Автозапуск в layout‑фазе
tsx
import { useLayoutEffect } from 'react';
import { useFrameExtended } from '@webeach/react-hooks';
export function Logger() {
const { start } = useFrameExtended(({ frame, deltaTime, timeSinceStart }) => {
if (frame % 60 === 0) {
console.log(
'frame',
frame,
'Δ',
deltaTime.toFixed(2),
'ms',
'total',
timeSinceStart.toFixed(1),
'ms',
);
}
});
useLayoutEffect(() => {
start();
}, []);
return null;
}2) Кнопки «Старт/Стоп»
tsx
import { useFrameExtended } from '@webeach/react-hooks';
export function Controls() {
const loop = useFrameExtended(({ deltaTime }) => {
// ваша логика кадра
});
return (
<div>
<button onClick={() => loop.start()}>Start</button>
<button onClick={() => loop.stop()}>Stop</button>
</div>
);
}3) Перезапуск с обнулением счётчиков
tsx
import { useFrameExtended } from '@webeach/react-hooks';
export function Restartable() {
const { start, stop, restart } = useFrameExtended(
({ frame, timeSinceLastStart }) => {
// frame снова начнётся с 1 после restart
},
);
return (
<div>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
<button onClick={restart}>Restart</button>
</div>
);
}Поведение
Запуск/остановка
start()запускает цикл. Если вызван до маунта, запуск будет отложен и выполнится сразу после маунта.
Повторный запуск
start()- При повторном вызове
start()активный цикл сначала останавливается, затем запускается заново. Счётчикframeне сбрасывается (для сброса используйтеrestart()).
- При повторном вызове
restart()- Сбрасывает
frame(снова с1) и «глобальный» таймер;timeSinceStartиtimeSinceLastStartначинают отсчёт заново.
- Сбрасывает
Тайминги кадра
deltaTimeотражает реальную длительность предыдущего кадра; используйте его для кадрово‑независимых анимаций.
Актуальность колбэка
- Используется актуальная версия
callback; обновления пропсов/состояния подхватываются без ручной переподписки.
- Используется актуальная версия
Layout‑семантика
- Подписка выполняется в layout‑фазе: удобно для измерений DOM и синхронной подготовки к пейнту.
Стабильность методов
start,stop,restart— стабильны между рендерами; их безопасно указывать в зависимостях эффектов/колбэков.
Когда использовать
- Анимации и последовательные вычисления, требующие пауз/перезапусков.
- Интеграции с Canvas/WebGL/SVG, когда нужен контроль над жизненным циклом кадров.
- Сценарии, где требуется различать «время с запуска» и «время с последнего старта».
Когда не использовать
- Для редких/разовых действий (
setTimeout, обычные эффекты). - Если не нужен контроль цикла — подойдёт простой
useFrame. - Для тяжёлых вычислений в каждом кадре — распределяйте работу или снижайте частоту.
Частые ошибки
Ожидание, что
start()обнуляет счётчикиstart()не сбрасываетframeи общий таймер. Используйтеrestart().
Вызов
start()в теле рендера- Побочные действия инициируйте в эффекте (
useEffect/useLayoutEffect), а не во время рендера.
- Побочные действия инициируйте в эффекте (
Перегрузка кадра тяжёлой логикой
- Длительный код внутри колбэка создаёт рывки. Выносите тяжёлые операции или используйте воркеры.
Забытая остановка при управлении вручную
- Хук сам очищается при размонтировании, но если в логике компонент/страница переключаются, корректно вызывайте
stop().
- Хук сам очищается при размонтировании, но если в логике компонент/страница переключаются, корректно вызывайте
Типизация
Экспортируемые типы
UseFrameExtendedReturn- Объект управления циклом:
{ start(): void; stop(): void; restart(): void }.
- Объект управления циклом:
UseFrameExtendedCallback(info: UseFrameExtendedCallbackOptions) => void— вызывается на каждом кадре.
UseFrameExtendedCallbackOptionsframe: number— номер кадра, начиная с1послеstart()/restart();deltaTime: number— миллисекунды с предыдущего кадра;timeSinceLastStart: number— миллисекунды с последнегоstart()/restart();timeSinceStart: number— миллисекунды с первого запуска (сбрасываетсяrestart()).