React通过context传递的函数访问state始终返回初始值如何解决
问题根因
这是典型的React闭包陷阱问题:
你的Provider组件中,用于往context注入函数的useEffect依赖是空数组,仅会在组件首次挂载时执行1次。此时存入context的addStrings、logStrings函数创建时,闭包捕获的textArray就是初始的空数组。后续textArray更新时,你没有更新context中存储的函数,所以Consumer调用的永远是首次挂载时生成的旧函数,旧函数闭包里的textArray永远是初始值,自然只能打印出空数组。
你看到DevTools中state正常,是因为首次调用addStrings时,基于初始空数组拼接新值确实能完成一次状态更新,但如果多次调用添加方法,你会发现新值会覆盖旧值而不是追加,本质也是同一个闭包问题导致的。
解决方案
这里提供3种可行的修复方案,按合理程度排序:
方案1:调整Context架构(最推荐)
你当前的设计是把状态存在子组件,再把方法回传父组件注入Context,属于完全没必要的多层传递。直接把textArray状态和操作方法都放到Context的Provider(也就是你的TestContainer组件)中,直接注入Context即可,从根源规避闭包问题:
// TestContainer.tsx 调整后代码 import React, { useState, createContext } from "react"; import Consumer from "./Consumer"; export const context = createContext<AppContextType | undefined>(undefined); type AppContextType = { textArray: string[]; actions: { addStrings: (t: string[]) => void; logStrings: () => void; }; }; const TestContainer = () => { const [textArray, setTextArray] = useState<string[]>([]); const addStrings = (texts: string[]) => { console.log("PREVIOUS LINES: ", textArray); console.log("NEW LINES:", [...textArray, ...texts]); setTextArray([...textArray, ...texts]); } const logStrings = () => { console.log("Strings : ", textArray); } return ( <context.Provider value={{ textArray, actions: { addStrings, logStrings } }} > {/* 原Provider组件的渲染逻辑直接移到这里,或者把textArray传给原组件渲染 */} <> <h1>Strings : </h1> {textArray.map((line, index) => ( <p key={index}>{line}</p> ))} </> <Consumer /> </context.Provider> ); }; export default TestContainer;
方案2:更新Context中存储的函数
如果要保留现有架构,需要在textArray更新时,同步更新Context中的方法:
- 用
useCallback包裹两个方法,依赖设置为textArray,保证textArray变化时生成新的函数 - 给注入方法的
useEffect添加这两个方法为依赖,方法更新时同步到Context
// Provider.tsx 调整后核心代码 import React, { useEffect, useState, useCallback } from "react"; // ... 其余代码不变 const Provider = ({ setActions }: ProviderProps) => { const [textArray, setTextArray] = useState<string[]>([]); // 用useCallback包裹,依赖textArray const addStrings = useCallback((texts: string[]) => { console.log("PREVIOUS LINES: ", textArray); console.log("NEW LINES:", [...textArray, ...texts]); setTextArray([...textArray, ...texts]); }, [textArray]); const logStrings = useCallback(() => { console.log("Strings : ", textArray); }, [textArray]); // 依赖添加两个方法,变化时同步更新Context useEffect(() => { console.log("send functions to parent"); setActions({ addStrings, logStrings }); }, [addStrings, logStrings, setActions]); // ... 其余代码不变
方案3:用useRef存储最新状态
如果你不想频繁更新Context中的函数,可以用ref同步最新的textArray值,函数中直接读取ref即可:
// Provider.tsx 调整后核心代码 const Provider = ({ setActions }: ProviderProps) => { const [textArray, setTextArray] = useState<string[]>([]); // 创建ref同步最新状态 const textArrayRef = useRef(textArray); // 状态变化时更新ref useEffect(() => { textArrayRef.current = textArray; }, [textArray]); function addStrings(texts: string[]) { // 读ref拿最新值 console.log("PREVIOUS LINES: ", textArrayRef.current); console.log("NEW LINES:", [...textArrayRef.current, ...texts]); setTextArray([...textArrayRef.current, ...texts]); } function logStrings() { // 读ref拿最新值 console.log("Strings : ", textArrayRef.current); } useEffect(() => { setActions({ addStrings: addStrings, logStrings: logStrings }); }, []); // ... 其余代码不变
内容的提问来源于stack exchange,提问作者Kian Soltani
相关产品推荐
相关产品推荐

