#javascript #reactjs #react-hooks
#javascript #reactjs #реагирующие крючки
Вопрос:
краткое изложение
Я пытаюсь создать кнопку, которая имеет как обычный щелчок, так и отдельное действие, которое происходит, когда пользователь нажимает и удерживает ее, аналогично кнопке «Назад» в Chrome.
То, как я это делаю, включает в себя setTimeout()
обратный вызов, который проверяет наличие чего-либо в состоянии. По какой-то причине обратный вызов использует состояние с момента, когда setTimeout()
был вызван, а не в то время, когда вызывается обратный вызов (1 секунда спустя).
Вы можете просмотреть его в codesandbox
как я пытаюсь этого добиться
Чтобы получить эту функцию, я вызываю setTimeOut()
OnMouseDown. Я также установил значение isHolding, которое находится в состоянии, в true.
В Mouseup я устанавливаю значение isHolding равным false, а также запускаю clickHandler()
, что является опорой, если функция удержания не успела быть вызвана.
Обратный вызов в setTimeOut()
проверит, имеет ли значение isHolding значение true, и если это так, он будет запущен clickHoldHandler()
, что является опорой.
проблема
Удержание находится в состоянии (я использую перехваты), но когда setTimeout()
запускается обратный вызов, я возвращаю не текущее состояние, а то, какое состояние было при setTimetout()
первом вызове.
мой код
Вот как я это делаю:
const Button = ({ clickHandler, clickHoldHandler, children }) => {
const [isHolding, setIsHolding] = useState(false);
const [holdStartTime, setHoldStartTime] = useState(undefined);
const holdTime = 1000;
const clickHoldAction = e => {
console.log(`is holding: ${isHolding}`);
if (isHolding) {
clickHoldHandler(e);
}
};
const onMouseDown = e => {
setIsHolding(true);
setHoldStartTime(new Date().getTime());
setTimeout(() => {
clickHoldAction(e);
}, holdTime);
};
const onMouseUp = e => {
setIsHolding(false);
const totalHoldTime = new Date().getTime() - holdStartTime;
if (totalHoldTime < holdTime || !clickHoldHandler) {
clickHandler(e);
}
};
const cancelHold = () => {
setIsHolding(false);
};
return (
<button
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
onMouseLeave={cancelHold}
>
{children}
</button>
);
};
Комментарии:
1. Всякий раз, когда компонент перезапускается, все функции внутри воссоздаются.
setTimeout
содержит ссылку на «старую» функцию, которая содержит ссылку на «старое» состояние.2. Привет, Зак, я бы предложил взглянуть на эту статью, в которой объясняется разница между функциональными компонентами и компонентами класса и то, как они фиксируют состояние. overreacted.io /…
Ответ №1:
Вы должны обернуть эту задачу обратного вызова в редуктор и запустить тайм-аут в качестве эффекта. Да, это, безусловно, усложняет ситуацию (но это «лучшая практика»):
const Button = ({ clickHandler, clickHoldHandler, children }) => {
const holdTime = 1000;
const [holding, pointer] = useReducer((state, action) => {
if(action === "down")
return { holding: true, time: Date.now() };
if(action === "up") {
if(!state.holding)
return { holding: false };
if(state.time holdTime > Date.now()) {
clickHandler();
} else {
clickHoldHandler();
}
return { holding: false };
}
if(action === "leave")
return { holding: false };
}, { holding: false, time: 0 });
useEffect(() => {
if(holding.holding) {
const timer = setTimeout(() => pointer("up"), holdTime - Date.now() holding.time);
return () => clearTimeout(timer);
}
}, [holding]);
return (
<button
onMouseDown={() => pointer("down")}
onMouseUp={() => pointer("up")}
onMouseLeave={() => pointer("leave")}
>
{children}
</button>
);
};
рабочая изолированная среда:https://codesandbox.io/s/7yn9xmx15j
В качестве запасного варианта, если редуктор становится слишком сложным, вы можете запомнить объект настроек (не лучшая практика):
const state = useMemo({
isHolding: false,
holdStartTime: undefined,
}, []);
// somewhere
state.isHolding = true;
Комментарии:
1. @zach извиняется за беспорядок, мне определенно нужно поспать сейчас… и спасибо за редактирование 🙂
2. нет проблем, вы проделали 99% пути. Я просто поменял местами две функции. 🙂