在前端开发中,经常会用到轮询(setInterval),比如后端异步数据处理,需要定时查询最新状态。但是,在使用ReactHook进行轮询操作时,你可能会发现setInterval并不是那么好控制。今天就来说说如何解决项目开发中调用setInterval的问题,以及如何更优雅的使用setInterval。问题的介绍从一个简单的例子开始。为了描述的方便,本文的案例使用一个计数定时器来进行演示。从“反应”中导入反应,{useEffect,useState};导出默认函数IntervalExp(){const[count,setCount]=useState(0);useEffect(()=>{consttimer=setInterval(()=>{setCount(count+1);},1000);return()=>clearInterval(timer);},[]);return(
);}先用useState定义一个count变量,然后在useEffect中定义一个定时器,名字为timer,进行count+1操作在计时器中,并在卸载组件时清除计时器。理想情况下,count将执行+1操作并保持递增。但这种情况并非如此。计数变为1后,将不再有任何变化。原因很简单。由于在useEffect中并没有将依赖计数对象添加到依赖对象数组中,所以每次获取的都是旧的计数对象,即0。方法一:添加依赖数组importReact,{useEffect,useState}from"react";导出默认函数IntervalExp(){const[count,setCount]=useState(0);useEffect(()=>{consttimer=setInterval(()=>{setCount(count+1);},1000);console.log("Updated",timer);return()=>clearInterval(timer);},[数数]);return(
);}将count对象添加到依赖数组后,可以发现定时器现在可以正常工作了。但是注意这里有个坑。返回的时候,也就是组件卸载的时候,必须做一个cleanup操作,否则你的timer会执行的越来越快,因为新的timer会不断的产生,但是老timer的appliance并没有被清理掉。但它完美吗?不行,如果定时器运行数据中包含父组件传递的props,或者其他状态,则需要添加到dependency数组中,不仅不美观,而且容易出错。同时,这种方式还有一个问题,就是定时器每次变化都需要重新生成,必然会有很高的性能损耗。方法二:不添加依赖数组的方法(useRef)useRef是官方的hook。useRef定义的对象有一个current对象,可以存储数据,存储的数据可以被修改,并且会在组件的每次渲染中显示,可以从current中获取最新的数据。基于ref的这个特性,实现一个名为useInterval的自定义hook。从“反应”导入{useEffect,useRef};exportconstuseInterval=(cb:Function,time=1000)=>{constcbRef=useRef
();useEffect(()=>{cbRef.current=cb;});useEffect(()=>{constcallback=()=>{cbRef.current?.();};consttimer=setInterval(()=>{callback();},time);return()=>clearInterval(计时器);},[]);};在这个自定义钩子中,有两个参数:回调函数和轮询时间。使用useEffect将最新的回调函数赋值给ref.current,这样在第二个useEffect中就可以从ref.current中获取到最新的回调,然后在timer中执行。在项目中如何使用?useInterval(()=>{setCount(count+1);},1000);只需要引入一个自定义的钩子,按照上面的格式调用即可。方法三:更高级的方法(useReducer)☆☆☆回顾第一个例子,为什么不在useEffect中加入count就达不到想要的定时器效果呢?说白了就是因为读取了state数据,并且因为闭包的原因,获取不到最新的count数据,所以interval操作失败。其实借助useReducer,可以在不读取count的情况下更新count数据。importReact,{useEffect,useReducer}来自"react";functionreducer(state:{count:number}){return{count:state.count+1};}exportdefaultfunctionIntervalExp(){const[state,dispatch]=useReducer(reducer,{count:0});useEffect(()=>{setInterval(()=>{dispatch();},1000);},[]);return(CurrentCount{state.count}
);}在本例中,使用useReducer定义了一个简单的计数操作方法。区间内调用dispatch方法成功更新count数据。useReducer可以用于需要操作多个状态的复杂业务逻辑场景。虽然定义起来比较麻烦,但是可以将业务逻辑分离在组件中,写出更容易维护的代码。在当前场景下,使用Reducer比上面两种方法要优雅,也是本文推荐的方法。总结Hook是React中一个非常吸引人的发明,灵活的使用hook可以写出更加优质的代码。笔者写这篇文章的目的也是因为在实际开发中遇到了这个问题,所以希望这篇文章能够对其他开发者有所帮助。参考文章:usestate_ReactHooks中回调函数中使用setInterval的几种方法