解决setInterval+usestate实现倒计时出现闭包问题

今天破天荒得想去写一个倒计时,久久不碰代码得我还是遵循老思想,先把逻辑写一遍,然后按照逻辑一步一步得实现代码,好了,思想对,代码实现起来就错了。

错误代码:

import React, { useState, useEffect } from 'react';

function Time(props) {
  const [ time, setTime ] = useState(10);
  const [ disable, setDisable ] = useState(false);
  let count;

  useEffect(() => {
    return () => {
      if (time <= 0) {
        clearInterval(count);
      }
    }
  }, [count, time])

   const handleClick = () => {
     setDisable(false);

     count = setInterval(() => {
       if (time === 0) {
         clearInterval(count);
      } else {
         setTime(time - 1)
       }
     }, 1000);
   }

  return (
    <div>
      <div style={{ background: 'red' }} onClick={handleClick}>我是按钮</div>
      <div>剩余{time}s</div>
    </div>
  )
}

export default Time;

大家看起来怪怪得吧?果不其然,踩坑了,前几天刚刚温习过usestate的内容,这次完美入坑。

正解:

import React, { useState, useEffect } from 'react';

function Time(props) {
  const [ time, setTime ] = useState(10);
  const [ disable, setDisable ] = useState(false);
  let count;

  useEffect(() => {
    return () => {
      if (time <= 0) {
        clearInterval(count);
      }
    }
  }, [count, time])

  const handleClick = () => {
    setDisable(false);

    count = setInterval(() => {
      setTime((v) => {
        if (v === 0) {
          clearInterval(count);
          return 0
        } else {
         return v -1;
        }
      })
    }, 1000);
  }

 return (
    <div>
      <div style={{ background: 'red' }} onClick={handleClick}>我是按钮</div>
      <div>剩余{time}s</div>
    </div>
  )
}

export default Time;

这里要讲一个知识点:


usestate的赋值方法大家想必都了如指掌了,例如:const [a, setA] = usestate(),使用时setA(123);这里还有一种写法,参数的函数式写法,setA((nA) => nA + 1),这里针对异步调用的时候可以取到最新的值,有点类似this.setstate的第二个参数的用法。

state在生命周期中或者是react合成事件是异步任务;在原生事件(setTimeout,Promise.resolve().then)里执行时是同步任务(这里目前还存在一点点歧义:据说这个说法是18版本之前的,18版本之后修复了这个问题,现在都是异步任务。这个大家要去验证一下哈)。

在react中state数据的改变都遵循一个原则,state指向的内容是不可变的。state改变时并不是当前内容本身改变了,而是state指向了新的内容对象。原内容对象则会在没有被任何变量引用的情况下会自动被释放。所以上述出现的问题是因为当state数据改变的时候它本身指向了新的内容对象,但是因为闭包的原因settimeout中还保持着对上一个内容对象的引用,所以settimeout中打印的值不是state中最新的值,而是之前的旧值。

解决方法:

1.解决方法可以利用useRef这个hook,在每次time值进行改变时将最新的值保存起来,在需要使用的时候直接使用useRef保存的值。

2.使用setstate的函数赋值,取到最新的值。具体原理,请参考官网源码实现。

扩展:

react框架本身为我们在promise等操作中为我们保存了上次旧值的引用,那么我们在某些场景下就可以直接使用这种特性来为我们解决开发问题比如: 在开发时当某个str值改变时,根据str生成新的url地址然后发请求,由于接口性能、网络原因等导致我在连续发n次请求后,接口响应的先后顺序不可控,而我们只需要取最后一次响应的值进行操作,所以就可以使用这个特性去判断当前state中str值是不是最新值就可以了。

有朋友看过我写的代码,指点了一下,如下:

正解那块性能可能存在问题:

1.能不用state的不用state的:动脚趾头想想,每一次的state都会触发页面重新render。原理自己去官网代码哈。

2.实现的方式要在useEffect里去判断count,这里就涉及到useEffect内部比较逻辑。

心得:

越不起眼的知识点越容易踩坑~

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值