反应: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 毫秒超时之前,假设 <SomeView />
组件已经重新渲染,因为 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 组件中传递值通常有两种方式。您可以通过 object
或 tuple
传递给 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>
...
在<SomeComponent />
中,您以这种方式解构上下文值:
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 值未在嵌套函数中更新的主要内容,如果未能解决你的问题,请参考以下文章