ReactJS中如何确保异步请求后state数组按指定顺序存储?
我是ES6、ReactJS新手,恳请各位不吝赐教。我开发了一个ReactJS项目,可在地图上放置标记点,点击标记点会显示关联视频。项目基于WordPress,包含「地点」和「视频」两种自定义文章类型,两者通过Advanced Custom Fields的重复字段关联。
目前我通过WordPress API查询地点数据,接口会返回地点信息及关联视频的正确顺序。随后我遍历视频ID获取详细数据并添加到state的sources数组中,但由于请求是异步的,返回数据顺序与请求顺序不一致,导致sources数组内视频数据顺序错乱。请问如何让state数组按期望顺序存储数据?
附上当前代码片段:
class PlacePopup extends React.Component { constructor(props){ super(props); this.state ={ sources: [], } } getAudioUrl = (props) => { this.props.audio.map((file , index) => { axios.get(`/wp-json/wp/v2/audio/${file.ID}`) .then(response => { this.setState({ sources: [...this.state.sources, response.data]}, function() { }); }) .catch(error => { console.log('Error fetching and parsing data', error); }); }) }
别担心,这个异步请求顺序的问题在新手阶段太常见了,我给你两种简单靠谱的解决办法:
方法一:用Promise.all严格保证顺序
axios.get返回的是Promise对象,我们可以先把所有音频请求都收集到一个Promise数组里,然后用Promise.all等待所有请求完成。它会严格按照你发起请求的顺序返回结果数组,直接把这个数组赋值给state的sources,就能完美保持和this.props.audio一致的顺序了。
修改后的代码如下:
getAudioUrl = () => { // 把每个请求转换成Promise,统一收集 const audioRequestPromises = this.props.audio.map(file => axios.get(`/wp-json/wp/v2/audio/${file.ID}`) .then(res => res.data) // 只提取我们需要的音频数据 .catch(err => { console.log(`获取ID为${file.ID}的音频失败`, err); return null; // 请求失败时返回null,避免整个Promise.all报错中断 }) ); // 等待所有请求完成后统一更新state Promise.all(audioRequestPromises) .then(audioDataList => { // 过滤掉请求失败返回的null数据 const validAudioData = audioDataList.filter(item => item !== null); this.setState({ sources: validAudioData }); }) .catch(overallErr => { console.log('处理音频请求时出现全局错误', overallErr); }); }
这种方式代码逻辑清晰,还能统一处理所有请求的结果,是我比较推荐的方案。
方法二:按索引位置存入数组(支持逐步更新)
如果你不想等所有请求都完成再更新UI,希望某个音频加载完成就立刻显示对应的内容,可以在请求时保留当前的索引,把数据放到数组的对应位置上:
首先修改constructor里的state初始化,先创建和音频数量一致的空数组占位:
constructor(props){ super(props); this.state ={ sources: new Array(props.audio.length).fill(null), // 提前创建对应长度的数组 } }
然后修改getAudioUrl方法:
getAudioUrl = () => { this.props.audio.map((file, index) => { axios.get(`/wp-json/wp/v2/audio/${file.ID}`) .then(response => { // 复制当前state的sources数组,避免直接修改原数组 const updatedSources = [...this.state.sources]; // 把新数据放到对应的索引位置 updatedSources[index] = response.data; this.setState({ sources: updatedSources }); }) .catch(error => { console.log(`获取ID为${file.ID}的音频失败`, error); const updatedSources = [...this.state.sources]; updatedSources[index] = null; // 失败时也标记对应位置 this.setState({ sources: updatedSources }); }); }); }
这样每个请求完成后,都会把数据放到正确的位置,哪怕请求返回顺序乱了,最终sources数组的顺序还是和原音频ID列表一致,还能实现加载一个显示一个的效果。
两种方法都能解决你的问题,你可以根据项目需求选择合适的方案~
内容的提问来源于stack exchange,提问作者jamessy

