Why react useEffect can memorize the state value?

Viewed 238

Here's an example:

function Page() {
  const [a, setA] = React.useState(0);
  useEffect(() => {
    const interval = setInterval(() => { console.log(a) }, 2000)
    return () => clearInterval(interval)
  }, []);
  return (
    <div>
      <span>{a}</span>
      <button onClick={() => setA(Math.random())}>button</button>
    </div>
  );
}

The invertal always log 0 despite the fact that count state variable has actually being increased by clicked the button a few times.

And many people say this is because the interval function capture a. But I really can not undersand it. When a variable can not be found in current environment, it will resolve to parent (or global).

And look at this example in contrast:

function Page() {
  let a = 0;
  useEffect(() => {
    const interval = setInterval(() => { console.log(a) }, 2000)
    return () => clearInterval(interval)
  }, []);
  return (
    <div>
      <button onClick={() => a = Math.random()}>button</button>
    </div>
  );
}

This is an obvious example for my expression. The interval closure capture the outer a variable. It will log the fresh value as it changes.

So how react useEffect implement this feature?

As my view (a bad pseudocode):

const hooks = [];
function useState(val) {
  let state = hooks[0] || val;
  hooks[0] = state;
  function setVal(v) {
    state = v;
    hooks[0] = state;
  }
  return [state, setVal];
}
let cleanup = null;
function useEffect(callback) {
  if (cleanup) cleanup()
  cleanup = callback();
}
function Foo() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setTimeout(() => { console.log(count)}, 1000);
  })
  return setCount;
}
var setCount = Foo(); // log 0
setCount(1);
Foo(); // log 1

But this is not way react was implement. So how react implement this?

1 Answers

Each time the component is rendered, the function is called.

First a value is assigned to a. This is either 0 (the first time) or the current value from the state (in subsequent times).

const [a, setA] = React.useState(0);

Then there is a useEffect hook. This takes two arguments. A function and a dependency array.

The function will be called every time the values in the dependency array change.

In your example, the dependency array is []. So the values never change. This means the function will only be called on the first render.

During the first render, the function it called. The value of a is 0.

The function passes a function to to setInterval. This function reads a. It has closed over the a variable from the first render.

In subsequent renders of the function, there will be a new a variable (belonging to this call to the function) which is assigned whatever value is in the state. The effect hook doesn't run again though and the existing interval is still looking at the original a variable.


In your second example, you are mutating the value of the original a variable (which is the variable the interval has closed over).

Related