Как упростить setTimeout в ReactJS
Была анимация набора текста на чистом javaScript, которая была преобразована в ReactJS. setTimeout
функции не выглядят чистыми и не соответствуют рекомендациям в соответствии со стандартом ReactJS.
Например animationManager()
animationManager = () => {
this.rafRef = requestAnimationFrame(time => {
const typingData = this.props.data;
this.typeEffect(time, typingData[this.index], () => {
this.timeoutRef = setTimeout(() => {
this.rafRef = requestAnimationFrame(time => {
this.deleteEffect(time, () => {
this.timeoutRef = setTimeout(() => {
this.index =
this.index === typingData.length - 1 ? 0 : this.index + 1;
this.animationManager();
}, this.props.pauseBeforeRestarting);
});
});
}, this.props.pauseBeforeDeleting);
});
});
};
Можно ли сделать его более чистым со всеми этими setTimout
?
Полный код https://codesandbox.io/s/qk4591q1kw
2 ответа
Да, вы действительно можете создавать функции, которые действуют как таймер: он возвращает обещание, которое разрешается, когда время истекает, что-то вроде этого:
timer = (duration) => {
return new Promise(resolve => {
window.setTimeout(resolve, duration);
});
}
Точно так же вы можете сделать то же самое для requestAnimationFrame
, Хитрость заключается в том, чтобы использовать оператор распространения ES6, чтобы вы могли передать произвольное количество аргументов в вызываемый обратный вызов:
animationFrame = (callback, ...args) => {
return new Promise(resolve => {
window.requestAnimationFrame(time => {
callback(time, ...args);
});
})
}
Поскольку вы используете ES6, вы можете использовать async
функции ожидания завершения таймера, прежде чем перейти к выполнению следующей строки кода. Если мы сломаем ваш animationManager()
код, это можно увидеть следующим образом:
- Вы хотите начать с
typingEffect
- однажды
typingEffect
завершено, вы хотите, чтобы вызватьdeleteEffect
В этом случае мы можем изменить ваш код следующим образом:
animationManager = () => {
const deleteFunc = (time, typingData) => {
this.deleteEffect(time, async () => {
await this.timer(this.props.pauseBeforeRestarting);
this.index = this.index === typingData.length - 1 ? 0 : this.index + 1;
this.animationManager();
});
};
const typeFunc = (time) => {
const typingData = this.props.data;
this.typeEffect(time, typingData[this.index], async () => {
await this.timer(this.props.pauseBeforeDeleting);
await this.animationFrame(deleteFunc, typingData);
})
};
this.animationFrame(typeFunc);
};
Я предоставил ваш пример для проверки концепции слегка переработанного кода: https://codesandbox.io/s/308kxjzwrq
Обычная практика - использовать для этого Обещания. Вы можете создать вспомогательный Promise, который будет использовать requestAnimationFrame, и сделать ваш поток плоским и "доступным", добавив успешные обратные вызовы onResolve.