在 setTimeout 中调用的函数不使用当前的 React 状态
Function called in setTimeout not using current React state
快速总结
我正在尝试创建一个按钮,它既有常规点击又有用户点击并按住它时发生的单独操作,类似于 Chrome 中的后退按钮。
我这样做的方式涉及一个 setTimeout()
和一个检查状态的回调。出于某种原因,回调正在使用调用 setTimeout()
时的状态,而不是调用回调时的状态(1 秒后)。
您可以在codesandbox
上查看
我是如何努力实现的
为了获得此功能,我正在调用 setTimeOut()
onMouseDown。我还将处于状态的 isHolding 设置为 true。
onMouseUp 我设置isHolding 为false 并且运行 clickHandler()
,这是一个道具,如果hold 函数还没有来得及被调用。
setTimeOut()
中的回调会检查isHolding是否为真,如果是则运行clickHoldHandler()
,这是一个prop.
问题
isHolding 处于状态(我正在使用挂钩),但是当 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>
);
};
您应该将该回调任务包装到 reducer 中并触发超时作为效果。是的,这肯定会使事情变得更加复杂(但它是 "best practice"):
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
如果 reducer 变得太复杂,作为后备方案,您可以记忆一个设置对象(不是最佳实践):
const state = useMemo({
isHolding: false,
holdStartTime: undefined,
}, []);
// somewhere
state.isHolding = true;
快速总结
我正在尝试创建一个按钮,它既有常规点击又有用户点击并按住它时发生的单独操作,类似于 Chrome 中的后退按钮。
我这样做的方式涉及一个 setTimeout()
和一个检查状态的回调。出于某种原因,回调正在使用调用 setTimeout()
时的状态,而不是调用回调时的状态(1 秒后)。
您可以在codesandbox
上查看我是如何努力实现的
为了获得此功能,我正在调用 setTimeOut()
onMouseDown。我还将处于状态的 isHolding 设置为 true。
onMouseUp 我设置isHolding 为false 并且运行 clickHandler()
,这是一个道具,如果hold 函数还没有来得及被调用。
setTimeOut()
中的回调会检查isHolding是否为真,如果是则运行clickHoldHandler()
,这是一个prop.
问题
isHolding 处于状态(我正在使用挂钩),但是当 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>
);
};
您应该将该回调任务包装到 reducer 中并触发超时作为效果。是的,这肯定会使事情变得更加复杂(但它是 "best practice"):
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
如果 reducer 变得太复杂,作为后备方案,您可以记忆一个设置对象(不是最佳实践):
const state = useMemo({
isHolding: false,
holdStartTime: undefined,
}, []);
// somewhere
state.isHolding = true;