React.js:如何让setInterval感知状态变量的变化?
问题:setInterval无法感知React状态变量的变化
我遇到一个问题:setInterval函数中的if语句始终将tracking状态变量判定为false。该变量初始值为false,但我后续在未展示的函数中将其状态改为true,然而setInterval仍认为它是false。
有没有办法确保setInterval能感知变量或状态的变化?
我尝试过使用localStorage变量、useRef React钩子,以及useInterval钩子,但都没有效果。
相关JavaScript代码:
useEffect(()=> { Promise.all([ faceapi.nets.tinyFaceDetector.loadFromUri('/models'), faceapi.nets.faceLandmark68Net.loadFromUri('/models'), faceapi.nets.faceRecognitionNet.loadFromUri("/models"), faceapi.nets.faceExpressionNet.loadFromUri('/models') ]).then(()=> { if(navigator.mediaDevices.getUserMedia) { navigator.mediaDevices .getUserMedia({ audio:false, video:true }) .then((stream) => { console.log(stream) const video = document.getElementById("video"); videoRef.current.srcObject = stream; console.log("video is " + video) video.play(); setisLoaded(true); addEvent(); }) .catch((e) => { console.log(e); }); } }); }, []); const addEvent = () => { console.log("Inside") const video = document.getElementById("video"); video.addEventListener("play", () => { const canvas = faceapi.createCanvas(video); canvas.id = "canvas"; document.querySelector("#video").append(canvas); document.body.append(canvas); const displaySize = { width:width, height:height }; faceapi.matchDimensions(canvas, displaySize); setInterval(async () => { const detections = await faceapi.detectAllFaces('video', new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks().withFaceExpressions(); const resizedDetections = faceapi.resizeResults(detections, displaySize); canvas .getContext("2d") .clearRect(0, 0, canvas.width, canvas.height); faceapi.draw.drawFaceExpressions(canvas, resizedDetections); if(resizedDetections[0].expressions && tracking) { setData(arr => [...arr, resizedDetections[0].expressions]); } }, 100); }) }
解决方案
问题根源
这是典型的闭包陷阱:setInterval的回调函数在组件初始渲染时被创建,它捕获的是当时tracking状态的快照值。React状态更新会触发组件重新渲染,但已经创建的setInterval回调不会重新生成,所以它始终使用初始渲染时的tracking值,无法感知后续的状态变化。
方法一:用useRef同步最新状态
useRef的current属性是可变的,不会被闭包锁定,能始终拿到最新的状态值:
- 创建ref并同步
tracking的最新值:
// 在组件顶部定义 const trackingRef = useRef(tracking); // 监听tracking变化,实时更新ref useEffect(() => { trackingRef.current = tracking; }, [tracking]);
- 修改setInterval中的判断逻辑,使用
trackingRef.current:
setInterval(async () => { // ... 其他代码保持不变 if(resizedDetections[0].expressions && trackingRef.current) { setData(arr => [...arr, resizedDetections[0].expressions]); } }, 100);
方法二:自定义useInterval钩子
自己实现一个能自动感知依赖变化的useInterval,确保每次回调都能拿到最新的状态:
// 在组件外部或内部定义自定义钩子 function useInterval(callback, delay) { const savedCallback = useRef(); // 保存最新的回调函数,确保依赖变化时更新 useEffect(() => { savedCallback.current = callback; }, [callback]); // 管理定时器的创建与销毁 useEffect(() => { function tick() { savedCallback.current(); } if (delay !== null) { const intervalId = setInterval(tick, delay); return () => clearInterval(intervalId); } }, [delay]); }
然后替换原生setInterval,直接使用这个钩子:
// 在addEvent的play事件回调中: useInterval(async () => { const detections = await faceapi.detectAllFaces('video', new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks().withFaceExpressions(); const resizedDetections = faceapi.resizeResults(detections, displaySize); canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height); faceapi.draw.drawFaceExpressions(canvas, resizedDetections); if(resizedDetections[0].expressions && tracking) { setData(arr => [...arr, resizedDetections[0].expressions]); } }, 100);
使用这个钩子时,确保tracking被包含在回调函数的依赖中(ESLint会自动提示),这样每次tracking变化时,回调函数会更新,钩子就能拿到最新的状态值。
内容的提问来源于stack exchange,提问作者ilovetocode123
相关产品推荐
相关产品推荐

