Author: Shadeed
Translator: Frontend Xiaozhi
Source: dmitripavlutin
If you have dreams and dry goods, search on [Daily Move to the World] attention to the brushing wisdom who is still doing dishes in the early morning.
This article GitHub https://github.com/qq449245884/xiaozhi has been included, the first-line interview complete test site, information and my series of articles.
Hooks simplify the management of the internal state and side effects of React components. In addition, the repeated logic can be extracted into custom Hooks to be reused throughout the application.
Hooks rely heavily on JS closures. This is why Hooks are so expressive and simple, but closures are sometimes tricky.
One problem you may encounter when using Hooks is outdated closures, which may be difficult to solve.
Let's start with outdated decorations. Then, take a look at how obsolete closures affect React Hooks and how to solve the problem.
1. Obsolete closures
The factory function createIncrement(incBy)
returns a log
functions increment
and 061088e104208b. When you call, increment()
function inside value
increase incBy
, while log()
print only one message, which contains information about the current value
information:
function createIncrement(incBy) {
let value = 0;
function increment() {
value += incBy;
console.log(value);
}
const message = `Current value is ${value}`; function log() { console.log(message); }
return [increment, log];
}
const [increment, log] = createIncrement(1);
increment(); // 1
increment(); // 2
increment(); // 3
// 不能正确工作!
log(); // "Current value is 0"
[increment, log] = createIncrement(1)
returns a function tuple: one function increases the internal value, another function records the current value.
Then, increment()
3 invocation will value
incremented to 3.
Finally, the log()
call to print the message is Current value is 0
, which is a bit unexpected, because at this time value
is 3
.
log()
is an obsolete closure. Closure log()
capture value "Current value is 0"
of message
variable.
Even if the value
variable is increased multiple times when increment()
message
variable will not be updated and always maintain an outdated value "Current value is 0"
.
Obsolete closures capture variables with obsolete values.
2. Fix obsolete closures
To fix the outdated log()
problem, you need to close the actual changed variable: the closure of value
We move the statement const message = ...;
to the log()
function 061088e104228b:
function createIncrement(incBy) {
let value = 0;
function increment() {
value += incBy;
console.log(value);
}
function log() {
const message = `Current value is ${value}`; console.log(message);
}
return [increment, log];
}
const [increment, log] = createIncrement(1);
increment(); // 1
increment(); // 2
increment(); // 3
// Works!
log(); // "Current value is 3"
Now, after calling the increment()
three times, calling log()
records the actual value
: "Current value is 3"
.
3. Obsolete closures in Hooks
3.1 useEffect()
Let's take a look at the common cases of useEffect()
In assembly <WatchCount>
in, useEffect()
in a recorded every 2 seconds count
value
function WatchCount() {
const [count, setCount] = useState(0);
useEffect(function() {
setInterval(function log() {
console.log(`Count is: ${count}`);
}, 2000);
}, []);
return (
<div> {count} <button onClick={() => setCount(count + 1) }> Increase </button> </div>
);
}
Open the case ( https://codesandbox.io/s/stale-closure-use-effect-broken-2-gyhzk )
And click the add button a few times. Then look at the console, Count is: 0
appears every 2 seconds, even though the count
state variable has actually been increased several times.
Why is this happening?
When rendering for the first time, the state variable count
initialized to 0
.
After the component is installed, useEffect()
calls the setInterval(log, 2000)
timer function, which is scheduled to call the log()
function every 2 seconds. Here, the closure log()
captured count
variable 0
.
After that, even if count
increases when the Increase
button is clicked, the timer function calls log()
every 2 seconds, and the value count
0
. log()
becomes an obsolete closure.
The solution is to let useEffect()
know that the closure log()
depends on count
, and correctly handle the reset of the interval when the count
function WatchCount() {
const [count, setCount] = useState(0);
useEffect(function() {
const id = setInterval(function log() {
console.log(`Count is: ${count}`);
}, 2000);
return function() {
clearInterval(id);
}
}, [count]);
return (
<div>
{count}
<button onClick={() => setCount(count + 1) }>
Increase
</button>
</div>
);
}
After the dependencies are set correctly, once count
changes, useEffect()
will update the closure.
3.2 useState()
<DelayedCount>
component has one button
, which asynchronously increments the counter with a delay of 1 second.
function DelayedCount() {
const [count, setCount] = useState(0);
function handleClickAsync() {
setTimeout(function delay() {
setCount(count + 1);
}, 1000);
}
return (
<div> {count} <button onClick={handleClickAsync}>Increase async</button> </div>
);
}
Now open the demo ( https://codesandbox.io/s/use-state-broken-0q994). Click the button twice quickly. The counter is only updated to 1
instead of 2
expected.
Each click setTimeout(delay, 1000)
delay()
after 1 second. delay()
this time captured count
is 0
.
Both delay()
update the status to the same value :setCount(count + 1) = setCount(0 + 1) = setCount(1)
.
This is because the delay()
closure of the second click has captured the outdated count
variable as 0
.
To solve this problem, we use the functional method s etCount(count => count + 1)
to update the count
state
function DelayedCount() {
const [count, setCount] = useState(0);
function handleClickAsync() {
setTimeout(function delay() {
setCount(count => count + 1); }, 1000);
}
function handleClickSync() {
setCount(count + 1);
}
return (
<div>
{count}
<button onClick={handleClickAsync}>Increase async</button>
<button onClick={handleClickSync}>Increase sync</button>
</div>
);
}
Open the demo ( https://codesandbox.io/s/use-state-fixed-zz78r). quickly click the button 2
times again. The counter shows the correct value 2
.
When a callback function that returns a new state based on the previous state is provided to the state update function, React ensures that the latest state value is provided as a parameter of the callback function
setCount(alwaysActualStateValue => newStateValue);
This is why the outdated decoration problem in the state update process can be solved by the function.
4. Summary
When the closure captures obsolete variables, the obsolete closure problem occurs.
An effective way to resolve obsolete closures is to set the dependencies of React hooks correctly. Or, in the case of a failure state, use a function to update the state.
~End, I’m Xiaozhi, I’m going to wash the dishes.
possible bugs after code deployment cannot be known in real time. In order to solve these bugs afterwards, a lot of time was spent on log debugging. By the way, I would like to recommend a useful BUG monitoring tool Fundebug .
Original: https://dmitripavlutin.com/react-hooks-stale-closures/
comminicate
There are dreams and dry goods. search 161088e10427c8 [Daily Move to the World] still doing dishes in the early morning.
This article GitHub https://github.com/qq449245884/xiaozhi has been included, the first-line interview complete test site, information and my series of articles.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。