React异步函数中state判断失效 curstep状态无法按条件更新如何解决?
问题原因
你遇到的是典型的React闭包陷阱问题:
- 用来启动定时器的
useEffect依赖项为空,只会在组件首次挂载时执行一次 - 此时创建的
setInterval回调以及内部调用的detect函数,捕获的curstep永远是首次渲染时的初始值0 - 后续调用
setCurstep更新状态只会触发组件重渲染,不会更新已经创建的闭包里的curstep值,所以curstep === 1、curstep === 2的判断永远无法命中,去掉判断直接更新状态自然是正常的。
解决方案
有三种常用的方法可以规避这个问题:
方案1:使用状态的函数式更新(最推荐,改动最小)
setCurstep支持传入函数作为参数,函数的入参永远是最新的当前状态值,不需要依赖闭包捕获的变量:
const [curstep, setCurstep] = useState(0); const detect = async (net) => { const hand = await net.estimateHands(video); const gesture = await net.estimate(hand[0].landmarks, 4); setCurstep(prevStep => { if (prevStep === 0 && gesture === "Number 1") return 1; if (prevStep === 1 && gesture === "Number 2") return 2; if (prevStep === 2 && gesture === "Number 3") return 3; // 不满足条件时返回原有值,不触发状态更新 return prevStep; }) } useEffect(() => { let timer = null; const runHandpose = async () => { const net = await handpose.load(); timer = setInterval(() => {detect(net)}, 30); } runHandpose(); // 组件卸载时清除定时器,避免内存泄漏 return () => clearInterval(timer); }, []);
方案2:用useRef同步最新状态值
用ref存储最新的curstep,ref的current值是可变的,不会被闭包捕获:
const [curstep, setCurstep] = useState(0); // 创建ref同步状态 const curstepRef = useRef(curstep); // 状态更新时同步到ref useEffect(() => { curstepRef.current = curstep; }, [curstep]); const detect = async (net) => { const hand = await net.estimateHands(video); const gesture = await net.estimate(hand[0].landmarks, 4); // 读取ref的current值,永远是最新的 if (curstepRef.current === 0 && gesture === "Number 1") setCurstep(1); else if (curstepRef.current === 1 && gesture === "Number 2") setCurstep(2); else if (curstepRef.current === 2 && gesture === "Number 3") setCurstep(3); } // 定时器逻辑和方案1一致
方案3:给useEffect添加curstep依赖
把curstep加入useEffect的依赖数组,每次curstep变化时重新创建定时器,注意要在清理函数中销毁旧的定时器,避免重复创建多个定时器:
const [curstep, setCurstep] = useState(0); const detect = async (net) => { const hand = await net.estimateHands(video); const gesture = await net.estimate(hand[0].landmarks, 4); if (curstep === 0 && gesture === "Number 1") setCurstep(1); else if (curstep === 1 && gesture === "Number 2") setCurstep(2); else if (curstep === 2 && gesture === "Number 3") setCurstep(3); } useEffect(() => { let timer = null; const runHandpose = async () => { // 建议把加载好的net用useRef存起来,避免每次curstep变化都重复加载模型 const net = await handpose.load(); timer = setInterval(() => {detect(net)}, 30); } runHandpose(); // 每次依赖变化、组件卸载时清除旧定时器 return () => clearInterval(timer); }, [curstep]);
内容的提问来源于stack exchange,提问作者dragon_and_dracula
相关产品推荐
相关产品推荐

