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” :

  1. The initial render generates a log function (value = 1)
  2. If value is 1, click the Alert button to execute log (value = 1)
  3. Click on the button to add value, such as value to 6, component render, generate a new log function (value = 6)
  4. 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?

  1. UseRef is described above
  2. 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:

  1. UseEffect, useLayoutEffect internal side effect functions are executed, and the side effect function retrieves the latest values of all current dependencies.
  2. 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:

  1. React Hooks: Closure caches the state of the current render every time render
  2. You can solve this problem with the useRef, state update callback function
  3. 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