React中使用useState更新对象不生效问题求助
问题:React Context无法保留多次API调用的合并结果
我用React Context存储一个关联camId的文件名对象,预期结构如下:
{ cam1: [file1, file2, file3], cam2: [filea, fileb, filec] // ...更多camId对应的文件名数组 }
Context与Provider代码
export const FilenamesContext = createContext() export const FilenamesProvider = ({ children }) => { const [filenamesObj, setFilenamesObj] = useState({}) const addFilenames = ({ camId, incomingFilenamesArr }) => { let newFilenamesArr if (filenamesObj[camId]) { newFilenamesArr = combineAndSortArrays(filenamesObj[camId], incomingFilenamesArr) } else { newFilenamesArr = incomingFilenamesArr } console.log('newFilenamesArr :>> ', newFilenamesArr); // 这里打印的结果是正确的 setFilenamesObj({ ...filenamesObj, [camId]: newFilenamesArr }) } return ( <FilenamesContext.Provider value={{ filenamesObj, addFilenames, }} > {children} </FilenamesContext.Provider> ) }
展示组件代码
export default function CameraDetails() { let { camId } = useParams() let { filenamesObj } = useFilenames() return ( <> <FilenamesLoader camId={camId} /> <Typography>Cam file names {camId}</Typography> {JSON.stringify(filenamesObj)} <Link to={"/"}>Go HOME</Link> </> ) }
数据加载组件代码
export default function CameraLoader({ camId }) { const { filenamesObj, addFilenames } = useContext(FilenamesContext) useEffect(() => { async function getAllFilenames(camId, before, count = 1) { if (count > 5) { console.log("count reached 5") // 防止无限循环 return } let returnedFilenames = await api.getFilenames({ camId, before }) addFilenames({ camId, incomingFilenamesArr: returnedFilenames }) await getAllFilenames(camId, getDateFromFilename(returnedFilenames[returnedFilenames.length - 1]), count + 1) } getAllFilenames(camId) }, []) return null }
问题现象
Context中的filenamesObj无法保留历史值,每次API调用后只显示当前调用的结果,最终仅保留第五次API返回的数据,而非5次结果的合并。推测是setFilenamesObj调用时引用了过期的filenamesObj值,导致每次更新都是基于初始空对象展开,而非最新的上下文状态。
解决方案
问题根源在于addFilenames中使用了闭包捕获的旧状态值。当多次连续调用addFilenames时,每次调用的filenamesObj都是组件初始化时的空对象,因此每次更新都会覆盖之前的结果。
1. 改用函数式更新状态
修改setFilenamesObj为函数式更新,确保每次更新都基于最新的状态值:
const addFilenames = ({ camId, incomingFilenamesArr }) => { setFilenamesObj(prevState => { const newFilenamesArr = prevState[camId] ? combineAndSortArrays(prevState[camId], incomingFilenamesArr) : incomingFilenamesArr; console.log('newFilenamesArr :>> ', newFilenamesArr); return { ...prevState, [camId]: newFilenamesArr }; }); }
2. 稳定addFilenames函数引用
用useCallback包裹addFilenames,避免组件重渲染时生成新的函数引用:
export const FilenamesProvider = ({ children }) => { const [filenamesObj, setFilenamesObj] = useState({}) const addFilenames = useCallback(({ camId, incomingFilenamesArr }) => { setFilenamesObj(prevState => { const newFilenamesArr = prevState[camId] ? combineAndSortArrays(prevState[camId], incomingFilenamesArr) : incomingFilenamesArr; console.log('newFilenamesArr :>> ', newFilenamesArr); return { ...prevState, [camId]: newFilenamesArr }; }); }, []); // 依赖为空,内部使用函数式更新不依赖外部状态 return ( <FilenamesContext.Provider value={{ filenamesObj, addFilenames, }} > {children} </FilenamesContext.Provider> ) }
3. 修正useEffect依赖项
在CameraLoader的useEffect中添加addFilenames作为依赖,确保使用最新的函数:
useEffect(() => { async function getAllFilenames(camId, before, count = 1) { if (count > 5) { console.log("count reached 5") return } let returnedFilenames = await api.getFilenames({ camId, before }) addFilenames({ camId, incomingFilenamesArr: returnedFilenames }) await getAllFilenames(camId, getDateFromFilename(returnedFilenames[returnedFilenames.length - 1]), count + 1) } getAllFilenames(camId) }, [addFilenames])
以上修改后,每次状态更新都会基于最新的prevState,多次API调用的结果会被正确合并到filenamesObj中。
内容的提问来源于stack exchange,提问作者lowcrawler
相关产品推荐
相关产品推荐

