Javascript React hooks:从回调中访问最新状态
编辑(2020年6月22日):由于这个问题重新引起了人们的兴趣,我意识到可能存在一些困惑。因此,我想强调:问题中的示例是一个玩具示例。这不是问题的反映。引发这个问题的问题是使用第三方库(控制有限),该库将回调作为函数的参数。为回调提供最新状态的正确方法是什么。在react类中,这可以通过使用Javascript React hooks:从回调中访问最新状态,javascript,reactjs,react-hooks,Javascript,Reactjs,React Hooks,编辑(2020年6月22日):由于这个问题重新引起了人们的兴趣,我意识到可能存在一些困惑。因此,我想强调:问题中的示例是一个玩具示例。这不是问题的反映。引发这个问题的问题是使用第三方库(控制有限),该库将回调作为函数的参数。为回调提供最新状态的正确方法是什么。在react类中,这可以通过使用this来完成。在React钩子中,由于状态封装在函数React.useState()中的方式,如果回调通过React.useState()获取状态,它将过时(设置回调时的值)。但是如果它设置了状态,它将通过
this
来完成。在React钩子中,由于状态封装在函数React.useState()
中的方式,如果回调通过React.useState()
获取状态,它将过时(设置回调时的值)。但是如果它设置了状态,它将通过传递的参数访问最新的状态。这意味着,通过将状态设置为原来的状态,我们可以通过React钩子在这样的回调中获得最新的状态。这是可行的,但与直觉相反
--原问题继续如下--
我正在使用React钩子并试图从回调中读取状态。每次回调访问它时,它都会返回其默认值
使用以下代码。无论我点击多少次,控制台都将继续打印计数为: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);
与
你可以找到这个代码
这种方法也没有奏效。
编辑:实际上,第二种方法确实有效。我刚才回电话时打错了。这是正确的做法。我需要调用setState来访问上一个状态。即使我无意设定国家
我觉得我对React类采取了类似的方法,但是。为了代码的一致性,我需要坚持使用React效果
如何从回调中访问最新状态信息?使用
useffect
而不是尝试访问回调中的最新状态。使用从setState
返回的函数设置您的状态不会立即更新您的值。状态更新是批处理和更新的
如果您想到useffect()
类似于setState
的第二个参数(来自基于类的组件),它可能会有所帮助
如果要使用最新状态执行操作,请使用useffect()
,当状态更改时,将点击该选项:
const{
useState,
使用效果
}=反应;
函数App(){
const[count,setCount]=useState(0);
常量减量=()=>setCount(count-1);
常量增量=()=>setCount(count+1);
useffect(()=>{
console.log(“useffect”,count);
},[计数];
console.log(“呈现”,计数);
报税表(
{count}
-
+
);
}
const rootElement=document.getElementById(“根”);
render( ,rootElement)代码>
我知道的唯一方法是调用setState(current_value=>…)函数并在逻辑中使用current_值。一定要把它还给我。例:
const myPollingFunction = () => {
setInterval(() => {
setState(latest_value => {
// do something with latest_value
return latest_value;
}
}, 1000);
};
对于您的场景(您无法继续创建新回调并将其传递给第三方库),您可以使用useRef
以保持具有当前状态的可变对象。像这样:
function Card(title) {
const [count, setCount] = React.useState(0)
const [callbackSetup, setCallbackSetup] = React.useState(false)
const stateRef = useRef();
// make stateRef always have the current count
// your "fixed" callbacks can refer to this object whenever
// they need the current value. Note: the callbacks will not
// be reactive - they will not re-run the instant state changes,
// but they *will* see the current value whenever they do run
stateRef.current = count;
function setupConsoleCallback(callback) {
console.log("Setting up callback")
setInterval(callback, 3000)
}
function clickHandler() {
setCount(count+1);
if (!callbackSetup) {
setupConsoleCallback(() => {console.log(`Count is: ${stateRef.current}`)})
setCallbackSetup(true)
}
}
return (<div>
Active count {count} <br/>
<button onClick={clickHandler}>Increment</button>
</div>);
}
功能卡(标题){
const[count,setCount]=React.useState(0)
const[callbackSetup,setCallbackSetup]=React.useState(false)
const stateRef=useRef();
//使stateRef始终具有当前计数
//无论何时,您的“固定”回调都可以引用此对象
//它们需要当前值。注意:回调不会
//被动-他们不会重新运行即时状态更改,
//但无论何时运行,它们都会看到当前值
stateRef.current=计数;
函数设置控制台回调(回调){
log(“设置回调”)
设置间隔(回调,3000)
}
函数clickHandler(){
设置计数(计数+1);
如果(!callbackSetup){
setupConsoleCallback(()=>{console.log(`Count为:${stateRef.current}`)})
setCallbackSetup(true)
}
}
返回(
活动计数{count}
增量
);
}
回调可以引用可变对象来“读取”当前状态。它将在闭包中捕获可变对象,每次渲染时,可变对象都将使用当前状态值进行更新。我遇到了一个类似的错误,试图执行与示例中完全相同的操作-在引用React组件中的道具或状态的回调上使用setInterval
希望我能从一个稍微不同的方向来解决这个问题,从而补充这里已经给出的好答案——认识到这甚至不是一个React问题,而是一个普通的老Javascript问题
我认为这里吸引人的是React-hooks模型,其中状态变量,毕竟只是一个局部变量,可以被视为在React组件的上下文中是有状态的。您可以确定,在运行时,变量的值将始终是React在特定状态下保持的值
然而,一旦您脱离了React组件上下文(例如,在setInterval
内的函数中使用变量),抽象就中断了,您回到了这样一个事实:状态变量实际上只是一个持有值的局部变量
抽象允许您编写代码,就好像运行时的值总是反映状态一样。在React的上下文中,情况就是这样,因为只要设置状态,整个函数就会再次运行,并且变量的值由React设置为更新后的状态值。然而,在回调内部,没有发生这样的事情——该变量不会神奇地更新以反映underlyn
const myPollingFunction = () => {
setInterval(() => {
setState(latest_value => {
// do something with latest_value
return latest_value;
}
}, 1000);
};
function Card(title) {
const [count, setCount] = React.useState(0)
const [callbackSetup, setCallbackSetup] = React.useState(false)
const stateRef = useRef();
// make stateRef always have the current count
// your "fixed" callbacks can refer to this object whenever
// they need the current value. Note: the callbacks will not
// be reactive - they will not re-run the instant state changes,
// but they *will* see the current value whenever they do run
stateRef.current = count;
function setupConsoleCallback(callback) {
console.log("Setting up callback")
setInterval(callback, 3000)
}
function clickHandler() {
setCount(count+1);
if (!callbackSetup) {
setupConsoleCallback(() => {console.log(`Count is: ${stateRef.current}`)})
setCallbackSetup(true)
}
}
return (<div>
Active count {count} <br/>
<button onClick={clickHandler}>Increment</button>
</div>);
}
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);
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>
);
}
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>
);
}
import useState from 'react-usestateref'
const [count, setCount,counterRef] useState(0)
console.log(couterRef.current) // it will always has the latest state value
setCount(20);
console.log(counterRef.current)
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();
onClick={() => { clickHandler(); }}
onClick={e => clickHandler()}
// 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()
}
const Component = () => {
const [state, setState] = useState(initialValue)
}
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)