我正在使用React钩子,并试图从回调中读取状态。每次回调访问它时,它都会恢复为默认值。
使用以下代码。无论我单击多少次,控制台都将继续打印Count is: 0
。
function Card(title) {
const [count, setCount] = React.useState(0)
const [callbackSetup, setCallbackSetup] = React.useState(false)
function setupConsoleCallback(callback) {
console.log("Setting up callback")
setInterval(callback, 3000)
}
function clickHandler() {
setCount(count+1);
if (!callbackSetup) {
setupConsoleCallback(() => {console.log(`Count is: ${count}`)})
setCallbackSetup(true)
}
}
return (<div>
Active count {count} <br/>
<button onClick={clickHandler}>Increment</button>
</div>);
}
const el = document.querySelector("#root");
ReactDOM.render(<Card title='Example Component' />, el);
您可以找到此代码here
我在回调中设置状态没有问题,只是在访问最新状态时。
如果我要猜测的话,我认为状态的任何更改都会创建Card函数的新实例。而且回调是指旧的回调。根据{{3}}上的文档,我想到了一种在回调中调用setState并将函数传递给setState的方法,以查看是否可以从setState中访问当前状态。更换
setupConsoleCallback(() => {console.log(`Count is: ${count}`)})
使用
setupConsoleCallback(() => {setCount(prevCount => {console.log(`Count is: ${prevCount}`); return prevCount})})
您可以找到此代码https://reactjs.org/docs/hooks-reference.html#functional-updates
该方法也不起作用。 编辑:实际上,第二种方法确实有效。我刚在回调中输入错误。这是正确的方法。我需要调用setState来访问以前的状态。即使我无意设置状态。
我觉得我对React类采取了类似的方法。为了保持代码的一致性,我需要坚持使用React Effects。
如何从回调中访问最新的状态信息?
答案 0 :(得分:34)
对于您的方案(无法继续创建新的回调并将其传递给第三方库),可以使用templates/header.html:8:68 Block closures should also have names `{% endblock %}`
来使可变对象保持当前状态。像这样:
useRef
您的回调可以引用可变对象以“读取”当前状态。它将在其闭包中捕获可变对象,并且每次渲染可变对象时都会使用当前状态值进行更新。
答案 1 :(得分:6)
您可以使用setState
例如
var [state,setState]=useState(defaultValue)
useEffect(()=>{
var updatedState
setState(currentState=>{ // Do not change the state by get the updated state
updateState=currentState
return currentState
})
alert(updateState) // the current state.
})
答案 2 :(得分:4)
我遇到了类似的错误,试图做与您的示例完全相同的操作-在回调中使用setInterval
,该回调引用了React组件中的props
或state
希望我可以从一个略有不同的方向来解决这个问题,从而增加已经存在的答案-意识到这甚至不是一个React问题,而是一个普通的Javascript问题。
我认为,从React钩子模型的角度出发,在这里,状态变量(毕竟只是局部变量)可以被视为在React组件的上下文中是有状态的。您可以确定,在运行时,变量的值将始终是React在特定状态下持有的值。
但是,一旦您脱离React组件上下文-例如在setInterval
内的函数中使用变量,抽象就会中断,您又回到了状态变量确实是只是一个拥有值的局部变量。
抽象允许您编写代码,就像一样,运行时的值将始终反映状态。在React的情况下就是这种情况,因为发生的情况是,只要您设置状态,整个函数就会再次运行,并且React会将变量的值设置为更新后的状态值。但是,在回调内部没有发生这样的事情-该变量不会神奇地更新以在调用时反映基础的React状态值。正是定义了回调(在这种情况下为0
),并且永不更改的情况。
这是解决问题的地方:如果该局部变量所指向的值实际上是对可变对象的引用,那么情况就会改变。该值(作为参考)在堆栈上保持不变,但是可以更改其在堆 上引用的可变值。
这就是为什么接受的答案中的技术起作用的原因-React ref正是提供了对可变对象的引用。但是我认为必须强调,“反应”部分仅仅是一个巧合,这一点非常重要。像问题一样,解决方案与React本身无关,只是React ref恰好是获取对可变对象的引用的一种方法。
例如,您还可以使用纯Javascript类,将其引用保持在React状态。明确地说,我并不是说这是一个更好的解决方案,甚至不是可取的(它可能不是!),而是用它来说明该解决方案没有“反应”方面的观点-只是Javascript:< / p>
class Count {
constructor (val) { this.val = val }
get () { return this.val }
update (val) {
this.val += val
return this
}
}
function Card(title) {
const [count, setCount] = React.useState(new Count(0))
const [callbackSetup, setCallbackSetup] = React.useState(false)
function setupConsoleCallback(callback) {
console.log("Setting up callback")
setInterval(callback, 3000)
}
function clickHandler() {
setCount(count.update(1));
if (!callbackSetup) {
setupConsoleCallback(() => {console.log(`Count is: ${count.get()}`)})
setCallbackSetup(true)
}
}
return (
<div>
Active count {count.get()} <br/>
<button onClick={clickHandler}>Increment</button>
</div>
)
}
const el = document.querySelector("#root");
ReactDOM.render(<Card title='Example Component' />, el);
您可以看到,只需将状态指向不变的引用,然后将引用所指向的基础值进行变异,即可在{{1} }并在React组件中。
同样,这不是惯用的React,而是在这里说明了引用是最终问题的要点。希望对您有所帮助!
答案 3 :(得分:2)
使用useEffect
而不是尝试访问回调中的最新状态。使用setState
返回的函数设置状态不会立即更新您的值。状态更新已分批更新
如果您将useEffect()
当作setState
的第二个参数(来自基于类的组件),可能会有所帮助。
如果要使用最新状态进行操作,请使用useEffect()
,该状态会在状态更改时出现:
const {
useState,
useEffect
} = React;
function App() {
const [count, setCount] = useState(0);
const decrement = () => setCount(count-1);
const increment = () => setCount(count+1);
useEffect(() => {
console.log("useEffect", count);
}, [count]);
console.log("render", count);
return (
<div className="App">
<p>{count}</p>
<button onClick={decrement}>-</button>
<button onClick={increment}>+</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render( < App / > , rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>
更新
您可以为您的setInterval
创建一个钩子,并这样命名:
const {
useState,
useEffect,
useRef
} = React;
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
function Card(title) {
const [count, setCount] = useState(0);
const callbackFunction = () => {
console.log(count);
};
useInterval(callbackFunction, 3000);
useEffect(()=>{
console.log('Count has been updated!');
}, [count]);
return (<div>
Active count {count} <br/>
<button onClick={()=>setCount(count+1)}>Increment</button>
</div>);
}
const el = document.querySelector("#root");
ReactDOM.render(<Card title='Example Component'/>, el);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>
答案 4 :(得分:0)
我知道的唯一方法是调用setState(current_value => ...)函数并在ur逻辑中使用current_value。只要确保您将其退回即可。例如:
const myPollingFunction = () => {
setInterval(() => {
setState(latest_value => {
// do something with latest_value
return latest_value;
}
}, 1000);
};
答案 5 :(得分:0)
我将结合使用var data=[ "2020-06-20T11:18:40.359Z", "2020-06-15T11:17:45.511Z", "2020-05-13T11:19:45.511Z", "2019-04-20T11:49:27.828Z"];
var result = data.reduce((acc, elem)=>{
const [year, month, day] = [new Date(elem).getFullYear(), new Date(elem).getMonth(), new Date(elem).getDate()];
acc[year] = acc[year] || {};
acc[year][month] = [...(acc[year][month] || []), day];
return acc;
},{});
console.log(result);
和setInterval()
。
useEffect()
本身是有问题的,因为在卸载组件后它可能会弹出。在您的玩具示例中,这不是问题,但在现实世界中,您的回调很可能希望改变组件的状态,然后就会成为问题。setInterval()
本身不足以在一段时间内导致某些事情发生。useEffect()
确实适合那些需要打破React功能模型的罕见情况,因为您必须使用某些不合适的功能(例如,集中输入等),因此我会避免使用它这样的情况。您的示例没有做任何非常有用的事情,并且我不确定您是否关心计时器弹出的频率如何。因此,使用此技术大致实现所需目标的最简单方法如下:
useRef()
需要注意的是,如果弹出计时器,则单击一秒钟后,下一个日志将在上一个日志之后4秒钟,因为单击时会重置计时器。
如果您想解决该问题,那么最好的办法是使用import React from 'react';
const INTERVAL_FOR_TIMER_MS = 3000;
export function Card({ title }) {
const [count, setCount] = React.useState(0)
React.useEffect(
() => {
const intervalId = setInterval(
() => console.log(`Count is ${count}`),
INTERVAL_FOR_TIMER_MS,
);
return () => clearInterval(intervalId);
},
// you only want to restart the interval when count changes
[count],
);
function clickHandler() {
// I would also get in the habit of setting this way, which is safe
// if the increment is called multiple times in the same callback
setCount(num => num + 1);
}
return (
<div>
Active count {count} <br/>
<button onClick={clickHandler}>Increment</button>
</div>
);
}
查找当前时间,并使用新的Date.now()
存储所需的下一个弹出时间,然后使用useState()
而不是setTimeout()
。
这有点复杂,因为您必须存储下一个计时器弹出窗口,但是还不错。同样,只需使用新功能就可以抽象出复杂性。因此,总而言之,这是使用钩子启动定期计时器的安全“反应式”方法。
setInterval()
只要您在import React from 'react';
const INTERVAL_FOR_TIMER_MS = 3000;
const useInterval = (func, period, deps) => {
const [nextPopTime, setNextPopTime] = React.useState(
Date.now() + period,
);
React.useEffect(() => {
const timerId = setTimeout(
() => {
func();
// setting nextPopTime will cause us to run the
// useEffect again and reschedule the timeout
setNextPopTime(popTime => popTime + period);
},
Math.max(nextPopTime - Date.now(), 0),
);
return () => clearTimeout(timerId);
}, [nextPopTime, ...deps]);
};
export function Card({ title }) {
const [count, setCount] = React.useState(0);
useInterval(
() => console.log(`Count is ${count}`),
INTERVAL_FOR_TIMER_MS,
[count],
);
return (
<div>
Active count {count} <br/>
<button onClick={() => setCount(num => num + 1)}>
Increment
</button>
</div>
);
}
数组中传递间隔函数的所有依赖项(与deps
一样),就可以在间隔函数中做任何您想做的事情(设置状态等)。 ),并确保一切都不会过时。
答案 6 :(得分:0)
您可以在state
回调中访问最新的setState
。但是意图并不明确,在这种情况下我们永远不想setState
,这可能会使其他人在阅读您的代码时感到困惑。因此,您可能希望将其包装在另一个可以表达您想要的东西的钩子中
function useExtendedState<T>(initialState: T) {
const [state, setState] = React.useState<T>(initialState);
const getLatestState = () => {
return new Promise<T>((resolve, reject) => {
setState((s) => {
resolve(s);
return s;
});
});
};
return [state, setState, getLatestState] as const;
}
const [counter, setCounter, getCounter] = useExtendedState(0);
...
getCounter().then((counter) => /* ... */)
// you can also use await in async callback
const counter = await getCounter();
答案 7 :(得分:0)
onClick={() => { clickHandler(); }}
通过这种方式,您可以在单击函数时运行定义的函数,而不是在声明onClick处理程序时运行函数。
React每次发生更改时都会重新运行hook函数,当更改时,它会重新定义您的clickHandler()
函数。
为了记录,您可以清理它。由于您不在乎箭头函数返回的内容,因此可以这样编写。
onClick={e => clickHandler()}
答案 8 :(得分:0)
我真的很喜欢@davnicwil 的回答,希望通过 Venue.near([40.71, -100.23], 50, latitude: :latitude3, longitude: :longitude3)
的源代码,他的意思可能会更清楚。
useState
在用法中,如果 // once when the component is mounted
constructor(initialValue) {
this.args = Object.freeze([initialValue, this.updater]);
}
// every time the Component is invoked
update() {
return this.args
}
// every time the setState is invoked
updater(value) {
this.args = Object.freeze([value, this.updater]);
this.el.update()
}
以数字或字符串开头,例如。 1.
initialValue
演练
const Component = () => {
const [state, setState] = useState(initialValue)
}
,useState
= [1, ]
this.args
,useState
没有变化this.args
,setState
= [2, ]
this.args
时,useState
没有变化现在,如果您执行某些操作,特别是延迟使用该值。
this.args
您将获得一个“旧”值,因为您首先将当前副本(当时)传递给它。尽管 function doSomething(v) {
// I need to wait for 10 seconds
// within this period of time
// UI has refreshed multiple times
// I'm in step 4)
console.log(v)
}
// Invoke this function in step 1)
doSomething(value)
每次都会获得最新的副本,但这并不意味着旧副本会被更改。您传递的值不是基于引用的。这可能是一个功能!!
总结
为了改变它,
this.args
作为值;object
获取useRef
值;尽管以上方法都修复了它(在其他答案中),但问题的根本原因是您将旧值传递给函数并期望它以未来值运行。我认为这是它首先出错的地方,如果您只看解决方案,这不是很清楚。