反应:useContext值未在嵌套函数中更新 说明解决方案

问题描述

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

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。

解决方法

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

export const FooContext = createContext(null);

通常,有两种方法可以在提供程序组件中传递值。您可以在提供程序组件中通过objecttuplevalue道具。

我将通过在提供程序组件中传递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);

,

说明

这是经典的stale closure problem。因为您没有向我们展示如何使用library(tidyverse) library(DT) # custom function that uses CSS gradients to make the kind of bars I need color_from_middle <- function (data,color1,color2) { max_val=max(abs(data)) JS(sprintf("isNaN(parseFloat(value)) || value < 0 ? 'linear-gradient(90deg,transparent,transparent ' + (50 + value/%s * 50) + '%%,%s ' + (50 + value/%s * 50) + '%%,%s 50%%,transparent 50%%)': 'linear-gradient(90deg,transparent 50%%,%s 50%%,transparent ' + (50 + value/%s * 50) + '%%)'",max_val,color2,max_val)) } mtcars %>% rownames_to_column() %>% select(rowname,mpg) %>% head(10) %>% mutate(mpg = (mpg - 20) %>% round) %>% datatable() %>% formatStyle( "mpg",background = color_from_middle(mtcars$mpg,'red','green') ) ,所以我无法确定闭包在哪里过时了,但是我敢肯定这就是原因。

您看到,在JS中,只要创建一个函数,它将在其闭包内部捕获周围的作用域,就可以将其视为创建状态时的状态的“快照” 。问题中的myFunction是这些状态之一。

但是调用value可能会在以后的某个时间发生,因为您可以传递myFunction。假设您将其传递到某个地方的myFunction

现在,在1000毫秒超时之前,假设setTimeout(myFunction,1000)已经重新渲染到<SomeView />完成时,并且axios.get已更新为value

此时,将创建'x'新版本,并在其结尾处捕获新值myFunction。但是value = 'x'已通过较旧版本的setTimeout ,该版本捕获了myFunction。 1000毫秒后,将调用value = null,并打印myFunction。就是这样。


解决方案

像其他所有编程问题一样,正确处理陈旧的关闭问题的最佳方法是对根本原因有充分的了解。一旦意识到这一点,请谨慎编写代码,更改设计模式或进行其他操作。只是首先要避免该问题,不要让它发生!

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

我不知道您的具体情况的详细信息,所以我无法告诉您解决问题的最佳方法。但是,非常幼稚的策略是使用对象属性而不是变量。

2. value = null

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

function SomeView() { const [value] = useContext(FooContext); const ref = useRef({}).current; ref.value = value; console.log('1. value =',ref.value); } return (<div>SomeView</div>) } 创建相同对象ref = useRef({}).current的稳定引用,该引用在重新渲染期间不会更改。然后将其放在ref的闭包内。它就像门户一样,在封闭的边界上“传送”状态更新。

现在,即使仍然出现过时的关闭问题,有时您仍可以调用过时的myFunction版本,这是无害的!由于旧的myFunction与新的ref相同,并且属性ref保证是最新的,因为重新分配时总是将其ref.value重新分配-呈现。

,

我认为提供者值中包含错误的部分,您可以这样做:

partitioner.class

,并且在... <FooContext.Provider value={value}> // <-- 'value' is simply a value {props.children} </FooContext.Provider> ... 中,您可以通过以下方式来解构上下文值:

<SomeComponent />

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

解决方案,要使销毁工作正常进行,您应该像这样设置提供程序

const [value] = useContext(FooContext);
,

您的值状态之所以在函数内部提供值,而在嵌套函数内部提供空值,是因为这些原因。

  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>;