React Context嵌套组件初始data为空问题的原因与解决
Context初始data为空的问题分析与解决
我在ResultsContextProvider中通过useEffect钩子异步加载数据,Provider代码如下:
import React, { createContext, useState, useEffect } from "react"; export const ResultsContext = createContext({ currResultsTab: "", changeCurrResultsTab: () => {}, data: {}, currOpt: "", processed: false, loading:true }); export const ResultsContextProvider = ({ children }) => { const [currResultsTab, setCurrResultsTab] = useState("IRR"); const [data, setData] = useState({}) const [loading, setLoading] = useState(true) const changeCurrResultsTab = (e, v) => v && setCurrResultsTab(v); useEffect(() => { const loadData = async () =>{ const data = await getStuff() setData(data) } loadData() setLoading(false) }, []); const resultsContextValue = { currResultsTab: currResultsTab, changeCurrResultsTab: changeCurrResultsTab, data:data, currOpt: "", loading:loading }; return ( <ResultsContext.Provider value={resultsContextValue}> {children} </ResultsContext.Provider> ); };
嵌套子组件ResultsTab已被包裹在Provider中,访问上下文的代码如下:
import {ResultsContext} from '../store/resultsContext'; export default memo(function ResultsTab({ currOpt, processed }) { console.log("RENDER RESULTS TAB") const resultsContext = useContext(ResultsContext) const { currResultsTab, changeCurrResultsTab, data, loading } = resultsContext console.log(resultsContext) console.log(data) return ( <div className='row mx-auto align-items-center'> <BasicToggle currOption={currResultsTab} options={['IRR', 'Credit', 'RBC']} onChange={changeCurrResultsTab} /> <div> {/* map and show data */} </div> </div> ) })
遇到的问题:初始渲染时,子组件解构出的data是空对象;但打印resultsContext时展开却能看到data不为空。切换标签触发组件重渲染后,data才显示正常值。
问题原因
- 异步时序错误:
useEffect中setLoading(false)是同步执行的,在getStuff()异步请求还未返回、setData(data)还未执行时,就把loading设为false。此时Provider的data还是初始的空对象,子组件第一次渲染拿到的就是这个空值。 - 控制台打印特性:浏览器控制台打印的引用类型(如对象)是实时快照,当你手动展开
resultsContext时,异步请求已经完成,data已经被更新,所以看起来有值,但子组件渲染时拿到的是当时的真实状态(空对象)。 - 重渲染触发时机:切换标签时调用
changeCurrResultsTab更新了currResultsTab,触发Provider重渲染,此时data已经加载完成,子组件就能拿到正确的数据。
解决方案
1. 修正异步加载的时序逻辑
把setLoading(false)移到异步函数内部,确保数据加载完成后再更新loading状态,避免子组件提前拿到未更新的data:
useEffect(() => { const loadData = async () =>{ const data = await getStuff() setData(data) // 数据加载完成后再设置loading为false setLoading(false) } loadData() }, []);
2. 子组件中根据loading状态做渲染控制
在子组件中判断loading状态,显示加载提示,等数据加载完成后再渲染数据内容:
return ( <div className='row mx-auto align-items-center'> <BasicToggle currOption={currResultsTab} options={['IRR', 'Credit', 'RBC']} onChange={changeCurrResultsTab} /> <div> {loading ? ( <div>加载中...</div> ) : ( // 这里渲染data相关内容 Object.keys(data).map(key => <div key={key}>{data[key]}</div>) )} </div> </div> )
内容的提问来源于stack exchange,提问作者Logan McNulty
相关产品推荐
相关产品推荐

