延迟后反应日志最终状态值

React log final state value after delay

我有一个 MERN 应用程序。在反应方面,我有一个状态。此状态每秒可能会或可能不会更改多次。当状态更新时,我想将状态发送到我的后端服务器 API,这样我就可以将值保存在我的 mongodb 中。这种状态可能每秒改变数百次,这是我希望允许的。但是,我只想最多每 5 秒将此值发送到我的服务器一次。这是为了避免垃圾邮件和阻塞我的 mongodb Atlas 请求。

目前,我尝试过 setInterval、setTimeout,甚至用 while(time

这些都提出了问题:

setInterval 很好,因为我可以用 lastSentValue 检查 currentValue,如果它们不相等 (!==),那么我会将 currentValue 发送到我的服务器。不幸的是,当我设置间隔时,它 returns 调用 setInterval 时存在的初始值。

如果你知道我如何让用户发送垃圾邮件布尔按钮,同时最多每 5 秒从前端 (React) 向后端 (Node) 发送一次更新,并且它发送当前和向上迄今为止的价值然后请分享您的想法,我会尽快测试它们。

我的状态值存储为::

const [aValue, anUpdate] = useState(false);

在我的 React 应用程序中返回的 onClick 方法改变了状态。

function Clicked(){
    anUpdate(!aValue);
}

我设置的间隔测试如下所示::

//This is so that the button may be pressed multiple times but the value is only sent once.
const [sent, sentUpdate] = useState(false);

//inside of the clicked method

if(!sent){
    sentUpdate(true);
    setInterval(()=>{
        console.log(aValue);
    },1000);
}

我的 setTimeout 非常相似,除了我再添加一个 sentUpdate 并在记录 aValue 后将其重置为 false,这样我可以再次记录超时。

//setInterval and setTimeout expected results in psudocode
aValue=true
first click->set aValue to !aValue (now aValue=false), start timeout/interval, stop setting timeouts/interval until completed
second click->set aValue to !aValue (now aValue=true), do not set timeout/interval as it is still currently waiting.
Completed timeout/interval
Log->false

//expected true as that is the current value of aValue. If logged outside of this timeout then I would receive a true value logged

在完全相反的方向上,我偶然发现的另一个流行的 Whosebug 答案是定义一个函数,该函数使用 while 循环占用计算机时间以伪造 setTimeout/setInterval。

看起来像这样::

function wait(ms){
    let start = new Date().getTime();
    let end = start;
    while(end < start + ms) {
        end = new Date().getTime();
    }
}

不幸的是,当在上述 if 语句中使用时(为了避免垃圾邮件),我的结果是::

aValue=true
first click->set aValue to !aValue (now aValue=false), start wait(5000), turn off if statement so we don't call many while loops
second click->nothing happens yet - waiting for first click to end.
first click timeout->logged "false"->if statement turned back on
second click that was waiting in que is called now->set aValue to !aValue (now aValue=true), start wait(5000), turn off if statement so we don't call many while loops
second click timeout->logged "true"->if statement turned back on

所以 while 循环方法也不是一个选项,因为它仍然会发送每个按钮按下。当他们垃圾点击时,它只会让客户陷入困境。

我看到的另一种方法是使用 Promise 包装我的 setTimeout 和 setInterval,但这绝不会改变 setTimeout/setInterval 的原始输出。 它看起来像这样::

const promise = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(true);
    },5000);
});
promise.then(console.log(aValue));
//I also tried resolve(aValue)->promise.then(val=>console.log(val));

对于你试图用循环和回调中的开始间隔做的事情,只会在到达迭代的特定状态值(即初始状态值)上关闭。

解决方案是使用 useEffect 挂钩来处理或“侦听”对依赖项值的更改。每次 state 更新都会触发组件重新渲染并调用 useEffect 挂钩,并且由于依赖项已更新,因此会调用挂钩的回调。

useEffect(() => {
  sendStateToBackend(state);
}, [state]);

如果您想限制 sendStateToBackend 实际调用的频率,那么您需要限制调用。这是一个使用 lodash 的 throttle 高阶函数的示例。

import { throttle } from 'lodash';

const sendStateToBackend = throttle((value) => {
  // logic to send to backend.
}, 5000);

const MyComponent = () => {

  ...

  useEffect(() => {
    sendStateToBackend(state);
  }, [state]);

  ...

更新

如果你想等直到点击按钮开始向后端发送更新,那么你可以使用React ref跟踪最初单击按钮的时间,以触发向后端发送数据。

const clickedRef = React.useRef(false);
const [aValue, anUpdate] = React.useState(false);

React.useEffect(() => {
  if (clickedRef.current) {
    sendToBackend(aValue);
  }
}, [aValue]);

const clicked = () => {
  clickedRef.current = true
  anUpdate(t => !t);
};

...

另见 Lodash per method packages,因为 package/bundle 大小似乎是个问题。

所以我昨晚脑洞大开,我通过创建一系列超时来解决这个问题,这些超时取消了之前的按钮点击超时,并使用上次超时的剩余值设置了一个新的超时。

const [buttonValue, buttonUpdate] = useState(props.buttonValue);
const [lastButtonValue, lastButtonUpdate] = useState(props.buttonValue);
const [newDateNeededValue, newDateNeededUpdate] = useState(true);
const [dateValue, dateUpdate] = useState();
const [timeoutValue, timeoutUpdate] = useState();

function ButtonClicked(){
    let oldDate = new Date().getTime();
    
    if(newDateNeededValue){
      newDateNeededUpdate(false);
      dateUpdate(oldDate);
    }else{
      oldDate = dateValue;
    }
    
    //clear old timeout
    clearTimeout(timeoutValue);
    
    //check if value has changed -- value has not changed, do not set timeout
    if(lastButtonValue === !buttonValue){
      console.log("same value do not set new timout");
      buttonUpdate(!buttonValue);
      return;
    }
    
    //set timeout
    timeoutUpdate(setTimeout(()=>{
      console.log("timed out");
      lastButtonUpdate(!buttonValue);
      newDateNeededUpdate(true);
      //This is where I send to my previous file and send to my server with axios
      props.onButtonUpdate({newVal:!buttonValue});
      clearTimeout(timeoutValue);
    }, (5000-(new Date().getTime() - oldDate))));
}