React中向子组件传递状态数组时变量未定义问题排查
React向子组件传数组变undefined?问题出在这几个地方
问题根源
- 异步状态更新踩坑:你在
ListAttributions和ListInteractions里调用setAttributionsData后,马上就用attributionsData.map渲染,但React的setState是异步的,这时候状态还没更新,用的还是初始的空数组。而且父组件状态更新会触发重新渲染,每次渲染都会重新创建这两个嵌套组件,导致useQuery反复执行,陷入不必要的循环。 - 数组判断完全错了:
attributionsData !== []这种写法永远是true——因为两个空数组是不同的引用对象。这就导致TabbedResults一开始就会被渲染,而这时候API数据还没加载完,自然拿到的是空数组或者undefined。 - 嵌套组件定义位置不对:把
ListAttributions和ListInteractions写在ShowResults内部,每次父组件渲染都会重新生成这两个组件,不仅浪费性能,还会破坏useQuery的缓存机制,导致重复请求。
修复步骤
1. 父组件统一管API查询
把useQuery移到父组件里,直接在父组件拿数据,再传给子组件渲染,这样状态逻辑更清晰,也不会有异步更新的问题。
2. 改对数组判断逻辑
用attributionsData.length > 0或者!!attributionsData来判断数据是否加载完成,别再对比数组引用了。
3. 把嵌套组件移到父组件外面
避免每次父组件渲染都重新创建组件,保证组件稳定还能省性能。
修复后的代码
import { useQuery } from '@apollo/client'; import PropTypes from 'prop-types'; // 把列表组件移到外部,避免重复创建 function ListAttributions({ attributionsData, pk }) { return ( <div> {attributionsData.map(({ sk, nominal, organisation, attribution, file_name, datetime_added, exhibit }, index) => ( <AttributionsCard key={index} Sk={sk} Nominal={nominal} Organisation={organisation} Attribution={attribution} FileName={file_name} FoundInsidePhone={file_name.match(/[0-9]+/g)} DateTimeAdded={datetime_added} Exhibit={exhibit} Pk={pk} /> ))} </div> ); } function ListInteractions({ interactionsData, pk }) { return ( <div> {interactionsData.map(({ direction, interaction, partner, duration, datetime, exhibit, organisation, file_name, datetime_added }, index) => ( <InteractionsCard key={index} Interaction={interaction} Direction={direction} Partner={partner} Duration={duration} DateTime={datetime} Exhibit={exhibit} Organisation={organisation} FileName={file_name} DateTimeAdded={datetime_added} Pk={pk} /> ))} </div> ); } const ShowResults = props => { const { pk } = props; // 父组件统一执行两个API查询 const { loading: attributionsLoading, error: attributionsError, data: attributionsQueryData } = useQuery(GET_ATTRIBUTIONS, { variables: { pk } }); const { loading: interactionsLoading, error: interactionsError, data: interactionsQueryData } = useQuery(GET_INTERACTIONS, { variables: { pk } }); // 直接从查询结果取数据,没数据就用空数组兜底 const attributionsData = attributionsQueryData?.listAttributions?.items || []; const interactionsData = interactionsQueryData?.listInteractions?.items || []; // 处理全局加载状态 if (attributionsLoading || interactionsLoading) { return <h2>LOADING...</h2>; } // 处理错误 if (attributionsError || interactionsError) { return `Error! ${attributionsError?.message || interactionsError?.message}`; } return ( <div style={{display: "inline-grid", inlineSize: "max-content", width: "100%"}}> <center> <ListAttributions attributionsData={attributionsData} pk={pk}/> <ListInteractions interactionsData={interactionsData} pk={pk}/> </center> <br/> <br /> {/* 等两个数据都加载完成再渲染TabbedResults */} {attributionsData.length > 0 && interactionsData.length > 0 && ( <TabbedResults pk={pk} attributionsData={attributionsData} interactionsData={interactionsData} /> )} <br /> <br /> </div> ); } ShowResults.propTypes = { pk: PropTypes.string }; export default ShowResults
补充说明
- 如果之后需要修改数据(比如筛选、排序),再用
useState存处理后的数据;如果只是展示,直接用查询返回的数据就行,少搞不必要的状态。 useQuery自带缓存,移到父组件后能更好地利用缓存,避免重复请求。
内容的提问来源于stack exchange,提问作者Ben Parsonage
相关产品推荐
相关产品推荐

