React中能否编写可访问state的递归函数实现实时队列逐次渲染?
问题根源
你遇到的是React函数组件的闭包陷阱:animateDisplay函数在首次渲染创建时,捕获了当前渲染周期的items变量,后续组件重渲染生成的新items不会被已经启动的递归函数访问,所以函数内部读取的永远是初始执行时的旧值,items.length>0的判断永远为真,就会出现无限循环。
解决方案1:依赖驱动(推荐,无闭包风险)
放弃递归实现,依靠useEffect的items依赖触发每一轮的展示逻辑,每次执行都能拿到最新的队列值,天然支持websocket动态新增元素:
import { useState, useEffect, useRef } from 'react' // 你用到的timeout工具函数 const timeout = (ms) => new Promise(resolve => setTimeout(resolve, ms)) function QueueDisplay() { const [items, setItems] = useState([]) const [display, setDisplay] = useState({}) const runningRef = useRef(false) // websocket接收新元素示例 useEffect(() => { const ws = new WebSocket('你的websocket服务地址') ws.onmessage = (event) => { const newItem = JSON.parse(event.data) // 新元素追加到队列末尾 setItems(prev => [...prev, newItem]) } return () => ws.close() }, []) // 核心动画驱动逻辑 useEffect(() => { // 队列为空或正在运行时跳过 if (items.length === 0 || runningRef.current) return const runSingleDisplay = async () => { runningRef.current = true // 展示当前队列第一个元素 setDisplay(items[0]) await timeout(4000) // 隐藏当前元素 setDisplay({}) // 移除已展示的队首元素 setItems(prev => prev.slice(1)) await timeout(1000) // 两个元素展示的间隔可自行调整 runningRef.current = false } runSingleDisplay() }, [items]) // 你的渲染逻辑,此处仅为示例 return <div className="display-area">{JSON.stringify(display)}</div> }
该方案优势:
- 完全规避闭包问题,每一轮执行都读取最新的
items值 - 动态新增元素自动触发检查,当前轮次动画执行完成后会自动展示新增的元素
- 逻辑简单易维护
解决方案2:递归适配方案
如果要保留递归写法,需要用useRef存储items的最新值,绕过闭包限制:
const [items, setItems] = useState([]) // 新增ref存储最新的items值 const itemsRef = useRef(items) const running = useRef(false) // 每次items变化时更新ref的值 useEffect(() => { itemsRef.current = items }, [items]) const animateDisplay = async () => { running.current = true // 所有读取items的地方替换为读取ref的current属性 setDisplay(itemsRef.current[0]) await timeout(4000) setDisplay({}) setItems(prevState => prevState.slice(1)) console.log(`inside ${itemsRef.current}`) if (itemsRef.current.length > 0) { await timeout(4000) await animateDisplay() } running.current = false } useEffect(() => { if (running.current === false && items.length > 0) { animateDisplay() } }, [items])
内容的提问来源于stack exchange,提问作者codego
相关产品推荐
相关产品推荐

