【问题标题】:React: useContext value is not updated in the nested function反应:useContext 值未在嵌套函数中更新
【发布时间】: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

标签: reactjs nested-function use-context


【解决方案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 的原因是由于这些原因。

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

      const myFunction = () => {
           console.log('2. value = ', value);
       }
      
    2. 第二个原因是你没有调用你做的嵌套函数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>;
      

    【讨论】:

      猜你喜欢
      • 2021-08-23
      • 1970-01-01
      • 2018-09-29
      • 2020-02-16
      • 2020-03-25
      • 2020-07-26
      • 2021-06-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多