setTimeout中调用的函数未使用当前的React状态

时间:2019-04-23 19:10:13

标签: javascript reactjs react-hooks

快速摘要

我正在尝试创建一个既具有常规点击又具有单独的操作的按钮,该按钮在用户单击并按住时会发生,类似于Chrome中的后退按钮。

我执行此操作的方式涉及一个setTimeout()和一个回调,该回调检查状态是否正常。由于某种原因,回调从调用setTimeout()开始就使用状态,而不是在调用回调时(1秒后)使用状态。

您可以在codesandbox

上查看它

我如何做到这一点

为了获得此功能,我在onMouseDown上致电setTimeOut()。我还将状态中的isHolding设置为true。

onMouseUp我将isHolding设置为false,并且如果还没有时间调用hold函数,则运行clickHandler(),这是一个道具。

setTimeOut()中的回调将检查isHolding是否为true,如果为true,它将运行clickHoldHandler(),这是一个道具。

问题

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>
  );
};

1 个答案:

答案 0 :(得分:2)

您应该将该回调任务包装到reducer中,并作为效果触发超时。是的,这肯定会使事情变得更加复杂(但这是“最佳实践”):

  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;