React树形组件中如何替代轮询,等待API响应完成后正确访问子项数据
兄弟,我太懂你用轮询的无奈了——不仅有1秒的延迟感,总觉得这是“笨办法”,而且还浪费资源。其实咱们完全可以利用API请求本身的异步特性,或者React的状态更新机制来解决这个问题,根本不用轮询~
我给你几个更优雅的方案,你可以根据自己的场景选:
方案一:直接在API请求的回调里处理逻辑
既然你是调用loadItems获取数据,那咱们就把“获取第一个子项”的逻辑直接放在API请求成功的回调里,这样只有当API返回数据后才会执行,完全不会有 race condition。
修改你的loadItemsFn就行:
const loadItemsFn = useCallback( (item, targetId, isExpand = true) => { if (loadItems) { loadItems(params) .then((data) => { setTree(data); // API已经返回,直接拿第一个子项 const firstChild = data?.[0]?.items?.[0]; if (firstChild) { // 这里写你要显示第一个子项的逻辑,比如设置选中状态、展开它等等 // 举个例子:setSelectedNode(firstChild); } }) .catch((err) => { // 别忘了处理请求失败的情况哦 console.error("加载子项失败啦", err); }); } }, [loadItems] // 这里可以去掉tree依赖,因为咱们用的是API返回的data,不是当前状态的tree );
这个方案最直接,逻辑和数据加载强绑定,不会出现“状态还没更新就去读”的问题。
方案二:用useEffect监听tree状态变化
如果你的“显示第一个子项”逻辑和加载数据的函数是分离的,或者tree可能从其他地方被更新,那用React的useEffect监听tree的变化就非常合适。只要tree更新了,咱们就检查有没有子项,有就处理:
import { useEffect } from 'react'; // 放在组件里 useEffect(() => { // 先判断tree和子项是否存在 if (tree?.[0]?.items?.length > 0) { const firstChild = tree[0].items[0]; // 执行你的显示逻辑,比如设置选中、展开 // setActiveItem(firstChild); } }, [tree]); // 只有当tree状态变化时,这个effect才会触发
React会保证在tree状态更新完成后再执行这个effect,所以你完全不用担心tree[0].items是空的情况——只要进入这个逻辑,就说明数据已经准备好了。
方案三:让loadItemsFn返回Promise,直接await它
如果需要在点击节点后,等待数据加载完成再做后续操作(比如你原来的getFirstItemAsync函数的场景),那可以让loadItemsFn返回API请求的Promise,然后在点击事件里直接await它,这样就不用轮询tree状态了:
// 先修改loadItemsFn,让它返回Promise const loadItemsFn = useCallback( (item, targetId, isExpand = true) => { if (!loadItems) return Promise.resolve(null); return loadItems(params) .then((data) => { setTree(data); return data; // 把返回的数据带出去,方便后续使用 }) .catch((err) => { console.error("加载失败", err); throw err; // 抛出错误,让调用者可以处理 }); }, [loadItems] ); // 然后在点击事件里使用 const handleRootNodeClick = useCallback(async () => { try { const data = await loadItemsFn(); const firstChild = data?.[0]?.items?.[0]; if (firstChild) { // 这里处理显示逻辑 } } catch (err) { // 处理请求失败的情况 } }, [loadItemsFn]);
这样点击后,代码会等待API请求完成,拿到返回的data后再去获取第一个子项,完全避免了“还没加载完就去读”的问题,比轮询靠谱多了。
总结一下:这三个方案都比轮询高效且优雅,推荐根据你的实际场景选择——如果逻辑和加载数据强关联,选方案一或三;如果逻辑独立或者tree有多个更新来源,选方案二就好。
备注:内容来源于stack exchange,提问作者Giii

