React Context Provider中useState数据在子组件卸载后丢失如何修复
问题原因
- 核心原因是你直接修改了React状态的原始引用,没有遵循不可变更新原则:你在
getDataFromStock中直接对原stocks数组、数组内的stock对象做了属性修改,再将原引用传给setStocks。React状态更新依赖引用变化做浅比较,这种突变写法不会触发可靠的状态持久化,当Provider因路由切换等场景重渲染时,未被正确记录的突变内容(也就是你加的data字段)就会丢失。 - 次要问题:
useCallback依赖stocks会导致getDataFromStock实例每次stocks变化时重新生成,进而触发详情页useEffect重复执行,还可能出现闭包陷阱拿到旧的stocks值。 - 额外隐患:初始
stocks状态为undefined,未做空判断直接调用find方法会抛出运行时错误。
修复方案
1. 修正StockProvider代码
核心改动点:使用状态更新的回调语法拿到最新状态,采用不可变方式生成新的状态数组/对象,移除useCallback对stocks的依赖。
import { createContext, useState, useCallback, useMemo, useContext } from "react"; import axios from "axios"; export const StockContext = createContext(); export const useStock = () => useContext(StockContext); // 建议这里填入你初始的基础股票数据,至少设为空数组 const INITIAL_STOCKS = [{id: "a", name: "a"}]; export const StockProvider = ({ children }) => { // 初始化状态为基础数据,避免undefined报错 const [stocks, setStocks] = useState(INITIAL_STOCKS); const getDataFromStock = useCallback( async ({ id }) => { let method = "GET"; let url = `${config.base_url}data/${id}`; try { const { data: response } = await axios({ method, url }); const { succes, data, error } = response; // 使用函数式更新,拿到最新的prevStocks,无需依赖外部stocks setStocks(prevStocks => { // 不可变更新:返回新数组,匹配id的项返回新对象挂载data return prevStocks.map(stock => stock.id === id ? {...stock, data} : stock ) }); return succes; } catch(err) { // 建议补充错误处理 return false; } }, // 移除stocks依赖,函数实例不会频繁变化 [] ); const value = useMemo( () => ({ getDataFromStock, stocks, }), [getDataFromStock, stocks] ); return <StockContext.Provider value={value}>{children}</StockContext.Provider>; };
2. 修正StockDetailPage代码
核心改动点:补充空值判断,避免运行时报错。
export default function StockDetailPage() { const { id } = useParams(); const [currentStock, setCurrentStock] = useState({}); const { stocks, getDataFromStock } = useContext(StockContext); useEffect(() => { const getData = async () => { // 先判断stocks存在,再找对应id的stock,再判断data是否存在 const targetStock = stocks?.find(s => s.id === id); if (!targetStock) return; if (!targetStock.data) { console.log("getting"); await getDataFromStock({ id }); } else { console.log("not getting"); } }; getData(); }, [getDataFromStock, id, stocks]); return ( <div> Stock: {currentStock?.owner?.username} {stocks ?.find((s) => s.id === id) ?.data?.map((p) => ( <ProductPreview key={p.product_id} {...p} /> ))} </div> ); }
效果说明
修改后所有状态更新都会生成新的引用,React可以正确持久化你新增的data字段,哪怕路由切换页面卸载,只要StockProvider没有被卸载,缓存的data字段就会一直保留,下次进入详情页无需重复请求接口。
内容的提问来源于stack exchange,提问作者Jasper B
相关产品推荐
相关产品推荐

