问题
I am having trouble accessing the component state from a callback.
The value of the state num
changes correctly, but such changes are not visible to the callback function defined at load time.
import React, {useState} from "react";
class MyObject {
callback: (() => void);
constructor(callback: () => void) {
this.callback = callback;
}
};
export const TestPage = () => {
const [num, setNum] = useState<number>(0);
// will print the changing values
console.log(`num: ${num}`);
const counter = useState<MyObject>(() => new MyObject(() => {
// will always print 0
console.log(`from callback. num: ${num}`);
}))[0];
const btnClick = () => {
setNum((new Date()).getTime());
counter.callback();
};
return (
<div>Test, num: {num}
<div>
<button onClick={btnClick}>click to update state</button>
</div>
</div>
);
}
the above doesn't happen with the plain non-React code as follows
let myVar = 0;
const obj = new MyObject(() => console.log(`myVar: ${myVar}`));
obj.callback(); // prints 0
myVar = 1;
obj.callback(); // prints 1
edit: I've tried to reproduce the problem with closures, but I cannot
const makeFun = (callback: (() => void)) => {
return () => {
callback();
}
}
let myVar = 0;
const f1 = makeFun(() => console.log(`myVar: ${myVar}`));
myVar = 1;
const f2 = makeFun(() => console.log(`myVar: ${myVar}`));
f1(); // prints 1
f2(); // prints 1
so I suspect is something specific to React and useState.
Any ideas?
edit#2: solution. this is how to make it visible in non-React code.
let callbackPersistent: (() => void);
const createCallback = (callback: (() => void)) => {
if (!callbackPersistent) {
callbackPersistent = callback;
}
return callbackPersistent;
}
const myFunction = (num:number) => {
const callback = createCallback(() => console.log(`callback num: ${num}`));
callback();
};
and then call it as
myFunction(0); // prints 0
myFunction(1); // prints 0
there's only one instance of the callback and it points to the value it saw once first created.
edit#3:
A reasonable solution can be found using useRef
and useEffect
, as follows:
import React, {useEffect, useRef, useState} from "react";
class MyObject {
callback: (() => void);
constructor(callback: () => void) {
this.callback = callback;
}
};
export const TestPage = () => {
const [num, setNum] = useState<number>(0);
const numRef = useRef(num);
// will print the changing values
console.log(`num: ${num}`);
const counter = useState<MyObject>(() => new MyObject(() => {
// will always print the latest value
console.log(`from callback. num: ${numRef.current}`);
}))[0];
useEffect(()=> {numRef.current = num}, [num]);
const btnClick = () => {
setNum(new Date().getTime());
counter.callback();
};
return (
<div>Test, num: {num}
<div>
<button onClick={btnClick}>click to update state</button>
</div>
</div>
);
}
useRef
guarantees that numRef
is always the same object over multiple invocations. When the value of num
changes the onEffect
call will update the numRef.current
value, which is then accesses by the the callback. Only one instance of the callback and of numRef
exist, making sure the changes are correctly picked up
回答1:
Callback function has a closure over the num
state and its value is zero because when callback was defined, num
had a value of zero.
To fix the problem, you can use useEffect
hook that creates a new MyObject
with a new callback function that has a closure over the latest value of num
.
useEffect(() => {
// update counter state
}, [num]);
Edit
Reason why non-react code works as you expect is because functions close over the variables, not their values. So in the second code example, callback function passed to MyObject
's constructor has a closure over the myVar
variable. This means that if you change the value of myVar
, callback function, when called, will log the latest value of myVar
.
Now you might ask, if closures are over the variables and not their values, then shouldn't the callback function in React code see the latest value of num
?
Reason why callback function always logs zero is because in React, callback functions see the value of state
or prop
that was available in the component at the time when that callback function was defined.
In my opinion, you shouldn't be using MyObject
class at all. If you have some complex state, then i would suggest you to consider using useReducer hook for managing the state.
来源:https://stackoverflow.com/questions/64010671/accessing-state-from-callback