反应:useContext 值未在嵌套函数中更新
React: useContext value is not updated in the nested function
我有一个简单的上下文,它设置了从后端获得的一些值,伪代码:
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
我认为错误的部分在 provider 值中,你这样做:
...
<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>
...
首先,您需要为您的上下文提供一些默认值,如果没有值则将默认值设置为 null。
export const FooContext = createContext(null);
大多数情况下,有两种方法可以在提供程序组件中传递值。您可以将 object
或 tuple
传递给 Provider 组件中的 value
道具。
我将通过在提供程序组件中传递 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'
Now, the question is why it is returning a single character value instead of the state value.
在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);
之所以你的值状态在函数内部返回一个值,而在嵌套函数内部返回一个空值,是因为这些原因。
第一个原因是您要传递一个箭头函数,这意味着它只会在 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>;
说明
这太经典了stale closure problem。我不知道闭包在哪里过时了,因为你没有向我们展示你如何使用 myFunction
,但我确定这就是原因。
你看,在 JS 中,每当你创建一个函数时,它都会在其闭包内捕获周围范围,将其视为创建时状态的“快照”。 value
问题中是其中一种状态。
但是调用 myFunction
可能会在稍后发生,因为您可以传递 myFunction
。比方说,你将它传递给某个地方的 setTimeout(myFunction, 1000)
。
现在在 1000 毫秒超时之前,假设 <SomeView />
组件已经 re-rendered 因为 axios.get
已完成,并且 value
更新为 'x'
.
此时创建了myFunction
的新版本,在其闭包中捕获了新值value = 'x'
。但是 setTimeout
被传递 myFunction
的旧版本 ,它捕获 value = null
。 1000 毫秒后,调用 myFunction
,并打印 2. value = null
。事情就是这样。
解决方案
与所有其他编程问题一样,正确处理陈旧闭包问题的最佳方法是充分了解根本原因。一旦意识到这一点,请谨慎编码,更改设计模式或其他任何方式。首先避免问题,不要让它发生!
此处讨论了该问题,请参阅 github 上的 #16956。在线程中建议了多种模式和良好实践。
我不知道你的具体案例的细节,所以我不能告诉你什么是最好的方式来回答你的问题。但是一个非常幼稚的策略是使用对象 属性 而不是 variable.
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
的稳定引用,该引用不会在 re-render 中发生变化。你把它放在 myFunction
的闭包中。它就像一个门户,可以“传送”跨闭包边界的状态更新。
现在即使过时的关闭问题仍然存在,有时您仍然可以调用过时的版本myFunction
,这是无害的!因为旧的 ref
和新的 ref
一样,它的 属性 ref.value
保证是 up-to-date 因为你总是 re-assign 它 ref.value = value
当 re-rendered.
我有一个简单的上下文,它设置了从后端获得的一些值,伪代码:
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'
。
我认为错误的部分在 provider 值中,你这样做:
...
<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>
...
首先,您需要为您的上下文提供一些默认值,如果没有值则将默认值设置为 null。
export const FooContext = createContext(null);
大多数情况下,有两种方法可以在提供程序组件中传递值。您可以将 object
或 tuple
传递给 Provider 组件中的 value
道具。
我将通过在提供程序组件中传递 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'
Now, the question is why it is returning a single character value instead of the state value.
在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);
之所以你的值状态在函数内部返回一个值,而在嵌套函数内部返回一个空值,是因为这些原因。
第一个原因是您要传递一个箭头函数,这意味着它只会在 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>;
说明
这太经典了stale closure problem。我不知道闭包在哪里过时了,因为你没有向我们展示你如何使用 myFunction
,但我确定这就是原因。
你看,在 JS 中,每当你创建一个函数时,它都会在其闭包内捕获周围范围,将其视为创建时状态的“快照”。 value
问题中是其中一种状态。
但是调用 myFunction
可能会在稍后发生,因为您可以传递 myFunction
。比方说,你将它传递给某个地方的 setTimeout(myFunction, 1000)
。
现在在 1000 毫秒超时之前,假设 <SomeView />
组件已经 re-rendered 因为 axios.get
已完成,并且 value
更新为 'x'
.
此时创建了myFunction
的新版本,在其闭包中捕获了新值value = 'x'
。但是 setTimeout
被传递 myFunction
的旧版本 ,它捕获 value = null
。 1000 毫秒后,调用 myFunction
,并打印 2. value = null
。事情就是这样。
解决方案
与所有其他编程问题一样,正确处理陈旧闭包问题的最佳方法是充分了解根本原因。一旦意识到这一点,请谨慎编码,更改设计模式或其他任何方式。首先避免问题,不要让它发生!
此处讨论了该问题,请参阅 github 上的 #16956。在线程中建议了多种模式和良好实践。
我不知道你的具体案例的细节,所以我不能告诉你什么是最好的方式来回答你的问题。但是一个非常幼稚的策略是使用对象 属性 而不是 variable.
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
的稳定引用,该引用不会在 re-render 中发生变化。你把它放在 myFunction
的闭包中。它就像一个门户,可以“传送”跨闭包边界的状态更新。
现在即使过时的关闭问题仍然存在,有时您仍然可以调用过时的版本myFunction
,这是无害的!因为旧的 ref
和新的 ref
一样,它的 属性 ref.value
保证是 up-to-date 因为你总是 re-assign 它 ref.value = value
当 re-rendered.