问题的出现------组件卸载怎么中止递归方法
朋友在处理弹幕相关业务的时候,出现了一个bug:组件中的方法在组件卸载后仍然在执行。代码片段发给我看,但是变量的用意我也不懂,只看到有方法调用自身方法,这不就是递归嘛(最后发现是定时器没有清除的问题)🥲。
递归的中止这还不简单,在方法中添加一个中止条件不就好了。
tsx
const Children = () => {
const [isStop, setStop] = useState(false)
// 递归
const recursion = async (num: number) => {
if (isStop) return
console.log(num)
await sleep(1000)
recursion(num + 1)
}
useEffect(() => {
recursion(1)
return () => {
setStop(() => true)
}
}, [])
return (
<div>Children</div>
)
}
const HomePage: React.FC = () => {
const [flag, setFlag] = useState(false)
return (
<div>
<Button onClick={() => setFlag(!flag)}>点击</Button>
{
flag && <Children></Children>
}
</div>
);
};
结果与预期好像不太一样,突然仔细想一下,当组件卸载时,所有与该组件相关的状态(通过useState
定义的状态)都会被清除,这怎么可能会有效呢!
简单闭包处理
既然状态在组件卸载的时候被清理了,那么在递归内部增加状态来控制递归不就行了。闭包就很适合现在的场景。
tsx
const controller = () => {
// 递归的状态(开始/结束)
let state = false
// 递归方法
const recursion = async (num: number) => {
// 结束条件
if (state === false) return
console.log(num)
await sleep(1000)
recursion(num + 1)
}
return {
// 开启递归
start: (num: number) => {
state = true
recursion(num)
},
// 结束递归
close: () => state = false
}
}
我们在挂载结束后创建一个单一实例用来控制递归方法,并在卸载的时候将递归的状态改为false
即可。如果需要在组件的其他位置使用,还是使用useRef
对实例进行包裹比较好。
tsx
useEffect(() => {
let c = controller()
c.start(1)
return () => {
c.close()
}
}, [])
Hook useRef处理
tsx
const Children = () => {
const isUnmounted = useRef(false);
// 递归
const recursion = async (num: number) => {
if (isUnmounted.current) return
console.log(num)
await sleep(1000)
recursion(num + 1)
}
useEffect(() => {
recursion(1)
return () => {
isUnmounted.current = true
}
}, [])
return (
<div>Children</div>
)
}
const HomePage: React.FC = () => {
const [flag, setFlag] = useState(false)
return (
<div>
<Button onClick={() => setFlag(!flag)}>点击</Button>
{
flag && <Children></Children>
}
</div>
);
};
来自CHAT-GPT
这是因为
useRef
创建的引用对象是在组件的闭包中存在的,而不是作为组件的状态存在。所以即使组件卸载,引用对象仍然可以被访问和修改。
个人理解,useRef
创建的引用对象在递归方法中被引用,类似闭包的原理,所以在组件卸载的时候没有被清理。(如果理解错误,欢迎指正,轻点喷🫠)
用途
大家都是大佬,谁写递归没有中止条件呀 ,对吧😎。
我觉得在页面请求完数据之后,需要对数据进行复杂递归处理的时候,又把页面关闭,这种情况可能需要。
还有其他情况吗?欢迎大家补充哦!