反应:useContext 值未在嵌套函数中更新

Posted

技术标签:

【中文标题】反应:useContext 值未在嵌套函数中更新【英文标题】:React: useContext value is not updated in the nested function 【发布时间】:2021-01-23 08:17:27 【问题描述】:

我有一个简单的上下文来设置它从后端获得的一些值,伪代码:

export const FooContext = createContext();

export function Foo(props) 
    const [value, setValue] = useState(null);

    useEffect(() => 
        axios.get('/api/get-value').then((res) => 
            const data = res.data;
            setValue(data);
        );
    , []);

    return (
        <FooContext.Provider value=[value]>
            props.children
        </FooContext.Provider>
    );


function App() 
    return (
        <div className="App">
            <Foo>
                <SomeView />
            </Foo>
        </div>
    );


function SomeView() 
    const [value] = useContext(FooContext);
    
    console.log('1. value =', value);
    
    const myFunction = () => 
        console.log('2. value = ', value);
    
    
    return (<div>SomeView</div>)

有时我会得到:

1. value = 'x'
2. value = null

所以基本上出于某种原因,尽管已更新为 'x',但值在嵌套函数内仍为 null。

【问题讨论】:

你在哪里打电话给myFunction 这里的代码中好像没有调用myFunction 对我来说看起来像是一个陈旧的闭包,尽管我无法判断,因为您的伪代码函数在任何地方都没有被调用 每次单击按钮时都会调用 myFunction(不在代码中)。按钮是组件的一部分。 添加了我的更新答案,其中您如何在父函数之外调用嵌套函数?@Bob Sacamano 【参考方案1】:

说明

这是一个非常经典的stale closure problem。由于您没有向我们展示您如何使用 myFunction,因此我无法判断关闭在哪里过时了,但我确信这就是原因。

您会看到,在 JS 中,每当您创建一个函数时,它都会在其闭包内捕获周围的范围,将其视为创建时状态的“快照”。问题中的value 是这些状态之一。

但是调用myFunction 可能会在稍后的某个时间发生,因为你可以传递myFunction。假设您将其传递给某处的setTimeout(myFunction, 1000)

现在在 1000 毫秒超时之前,假设 &lt;SomeView /&gt; 组件已经重新渲染,因为 axios.get 已完成,value 已更新为 'x'

此时,myFunction新版本被创建,在其闭包中捕获了新值 value = 'x'。但是setTimeout 传递了myFunction旧版本,它捕获了value = null。 1000ms 后,调用myFunction,并打印2. value = null。事情就是这样发生的。


解决方案

与所有其他编程问题一样,正确处理陈旧闭包问题的最佳方法是充分了解根本原因。一旦您意识到这一点,请谨慎编码,更改设计模式或其他方式。一开始就避免问题,不要让它发生!

这里讨论了这个问题,请参阅 github 上的#16956。在线程中建议了多种模式和良好实践。

我不知道你的具体情况的细节,所以我无法告诉你问题的最佳方法是什么。但是一个非常幼稚的策略是使用对象属性而不是变量。

function SomeView() 
    const [value] = useContext(FooContext);

    const ref = useRef().current;
    ref.value = value;
    
    console.log('1. value =', value);
    
    const myFunction = () => 
        console.log('2. value = ', ref.value);
    

    return (<div>SomeView</div>)

想法是依赖于对象的稳定引用。

ref = useRef().current 为同一对象ref 创建一个稳定的引用,在重新渲染时不会改变。你在myFunction的封闭范围内携带它。它就像一个门户,将状态更新“传送”到闭包的边界。

现在即使过时的关闭问题仍然存在,有时你可能仍然会调用myFunction的过时版本,它是无害的!因为旧的ref 与新的ref 相同,并且它的属性ref.value 保证是最新的,因为您总是在重新渲染时重新分配ref.value = value

【讨论】:

这个答案很有帮助。是的,这确实是一个陈旧的更接近的问题:)【参考方案2】:

首先,如果没有值,则需要为上下文提供一些默认值,然后将默认值设置为 null。

export const FooContext = createContext(null);

在 Provider 组件中传递值通常有两种方式。您可以通过 objecttuple 传递给 Provider 组件中的 value 属性。

我将通过在 Provider 组件中传递一个 object 来给你一个例子。 @dna 给出了一个tuple 的例子。

<FooContext.Provider value=value,setValue>
     props.children
</FooContext.Provider>

现在,如果您想在另一个组件中使用该值,您需要像这样解构对象

const value, setValue = useContext(FooContext);

如果您正确调用了嵌套的myFunction(),如下所示,则该值也将是 x,而不是null。

function SomeView() 
    const [value] = useContext(FooContext);
    
    console.log('1. value =', value);
    
    const myFunction = () => 
        console.log('2. value = ', value);
    
    SomeView.myFunction = myFunction; //updated line
    return (<div>SomeView</div>)

<button onClick=SomeView.myFunction>Click</myFunction>

输出:

1. value = 'x'
2. value = ' x'

现在,问题是为什么它返回单个字符值而不是状态值。

javascript 中,字符串是一个字符数组。 例如。

const string = ['s','t','r','i','n','g'];
//This is equivalent to
const string = "string";

在您的情况下,您的状态值可能是一个字符串。所以,当你解构字符串时,你会得到字符串的第一个字符。

我举个例子你会明白的。

const string = "subrato";

const [str] = string;
console.log(str);

【讨论】:

【参考方案3】:

我认为错误的部分是在提供者值中,你这样做:

...
<FooContext.Provider value=value> // <-- 'value' is simply a value
    props.children
</FooContext.Provider>
...

&lt;SomeComponent /&gt; 中,您以这种方式解构上下文值:

const [value] = useContext(FooContext);

但这是错误的,因为您将提供者值设置为简单值而不是元组。

解决方案为了使你的解构工作,你应该像这样设置你的提供者

...
<FooContext.Provider value=[value]> // <---- USE TUPLE NOTATION
    props.children
</FooContext.Provider>
...

【讨论】:

很抱歉误导了您。我将提供程序中的值设置为元组。我没有正确编写伪代码。在实际情况下,它是一个元组。【参考方案4】:

您的值状态在函数内部返回值并在嵌套函数内部返回 null 的原因是由于这些原因。

    第一个原因是你传递了一个箭头函数,这意味着它只会在 onClick 函数上返回一个值。

    const myFunction = () => 
         console.log('2. value = ', value);
     
    

    第二个原因是你没有调用你做的嵌套函数myFunction

而不是这样做,它会起作用:

SomeView() 中调用你的函数。

 myFunction();

更新你的函数。

  function myFunction() 
    console.log("2. value = ", value);
  

或者,如果您希望它与旧代码一起运行,请传递一个 onClick 侦听器,该侦听器将触发您的 myFunction() 并将 console.log 值。

 const myFunction = () => 
    console.log("2. value = ", value);
  ;

 return <button onClick=myFunction>Click me</button>;

【讨论】:

以上是关于反应:useContext 值未在嵌套函数中更新的主要内容,如果未能解决你的问题,请参考以下文章

Python:函数返回的值未在熊猫数据框中更新

useContext 未在回调中更新其值

使用React useContext hook进行分派调用后,反应状态不会更新

由于缺少引号,布尔值未在 MERN 堆栈中更新

IBInspectable 属性值未在 xib 中更新

输入值未在我传入 Intents 的另一个活动中更新