This is the 9th day of my participation in Gwen Challenge
What is a closure trap
const FunctionComponent = () = > {
const [value, setValue] = useState(1)
const log = () = > {
setTimeout(() = > {
alert(value)
}, 1000);
}
return (
<div>
<p>FunctionComponent</p>
<div>value: {value}</div>
<button onClick={()= > setValue(value + 1)}>add</button>
<br/>
<button onClick={log}>alert</button>
</div>)}Copy the code
In the functional component above, clicking the Alert button will pop up the value of the value for 2 seconds, during which time we can click the Add button to increase the value of the value.
The picture above is the result of our operation. We found that the pop-up value is different from the value displayed on the current page. In other words: the value in the log method is the same as the value at the moment the click is triggered, and subsequent changes in value do not affect the value in the log method. This phenomenon is called the closure trap, or Capture Value: Each time the functional component render produces a new log function, the new log function produces a closure with the Value of the current phase.
Analysis of the above example “closure trap” :
- The initial render generates a log function (value = 1)
- If value is 1, click the Alert button to execute log (value = 1)
- Click on the button to add value, such as value to 6, component render, generate a new log function (value = 6)
- The timer fires and the log function (value = 1) pops up with value 1 inside the closure
Solutions to closure traps
Solve closure traps with useRef
const FunctionComponent = () => { const [value, setValue] = useState(1) const countRef = useRef(value) useEffect(() => { countRef.current = value }, [value]) const log = useCallback( () => { setTimeout(() => { alert(countRef.current) }, 1000); }, [value], ) return ( <div> <p>FunctionComponent</p> <div>value: {value}</div> <button onClick={() => setValue(value + 1)}>add</button> <br/> <button onClick={log}>alert</button> </div> )}Copy the code
UseRef returns an object of the same reference type each time we render, so we can get the latest value by processing both the set and read values on this object.
The callback function when updating State
Effect Hook allows you to perform side effects in function components
Suppose we now want to open a counter that increments per second, we would typically write code like this:
const Counter = () => {
const [value, setValue] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
console.log('new value:', value+1)
setValue(value + 1)
}, 1000);
return () => {
clearInterval(timer)
}
}, [])
return (
<div>
<p>Counter</p>
<div>count: {value}</div>
</div>
)
}
Copy the code
In the above code, we constantly updated the value of value in useEffect, but combined with the analysis of the previous closure trap problem, we can find that the value of the timer will always be 0, resulting in the value of 1 each time. The following is the result of operation.
The biggest problem with the “closure trap” is that you can’t get the latest state value in the number of functions. What does React offer to fix this?
- UseRef is described above
- A callback function is passed in when useState updates the value
In addition to the useRef method described above, we can also pass in a callback function when updating state (the value fetched from the callback function is the latest).
const [value, setValue] = useState(0)
useEffect(() = > {
const timer = setInterval(() = > {
// The latest value of the callback function
setValue(value= > value + 1)},1000);
return () = > {
clearInterval(timer)
}
}, [])
Copy the code
Closure traps and Hooks dependencies
UseEffect, useLayoutEffect, useCallback, useMemo the second parameter is a dependency array. Any dependency change (shallow comparison) in the dependency array will have the following effect:
- UseEffect, useLayoutEffect internal side effect functions are executed, and the side effect function retrieves the latest values of all current dependencies.
- UseCallback and useMemo will return new functions or objects, and the internal functions can also get the latest values of all current dependencies.
This mechanism theory can be used to solve the “closure trap”, but it is not applicable in some cases:
const Counter = () => {
const [value, setValue] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
console.log('tick:', value+1)
setValue(value + 1)
}, 1000);
return () => {
console.log('clear')
clearInterval(timer)
}
- }, [])
+ }, [value])
return (
<div>
<p>Counter</p>
<div>count: {value}</div>
</div>
)
}
Copy the code
In the above code, we add value as a dependency to the dependency array, which is functional, but each time we go through the clearInterval -> setValue ->clearInterval loop. This creates an unnecessary performance drain. At the other extreme, if we do not return a function to cancel the timer, new timers will be added.
Considerations for using Hook dependencies
Event subscription
Now we have the following scenario
useEffect(() =>{
someThing.subscribe(() => {
// do something with value
})
}, [value])
Copy the code
In the code above, value changes are constantly subscribed to new events. So in EffectHook we remember to return a function to cancel side effects
UseEffect (() =>{someThing. Subscribe (() =>{// do someThing with value}) return () =>{+ // add unsubscribe function}}, [value])Copy the code
If the throttle
function BadDemo() { const [count, setCount] = useState(1); const [, setRerender] = useState(false); const handleClick = debounce(() => { setCount(c => ++c); }, 1000); UseEffect (() => {// Every 500ms, the component rerender setInterval(() => {setRerender(r =>! r); }, 500); } []); return <div onClick={handleClick}>{count}</div>; } the author: ants insurance experience technical links: https://juejin.cn/post/6844904090032406536 source: the nuggets copyright owned by the author. Commercial reprint please contact the author for authorization, non-commercial reprint please indicate the source.Copy the code
For example, in the code above, we have a handleClick function that needs to be defused, but our function will render from time to time, and every time render generates a new function, the function will be defused.
conclusion
To sum up:
- React Hooks: Closure caches the state of the current render every time render
- You can solve this problem with the useRef, state update callback function
- Be careful to eliminate side effects when using the EffectHook dependency
The resources
- Official documents of Hook
- Function VS Class Components
- Close reading complete guide to useEffect
- Remember to read before writing React Hooks