简体   繁体   中英

Locking behavior of object in JS?

On every click of increment button:
Expectation: current count is logged
Reality: initial value of count , ie 3 is logged

import React, { useState, useEffect } from "react";

function SomeLibrary(props) {
  const [mapState, setMapState] = useState(undefined);

  useEffect(() => {
    console.log("setting map");
    // Run exactly once at mount of component
    setMapState(props.map);
  }, []);

  useEffect(() => {
    if (mapState) {
      mapState.key();
    }
  }, [props]);

  return <div> ... </div>;
}

export default function App() {
  const [count, setCount] = React.useState(3);

  const map = { key: () => {
    console.log("fn", count);
  }};

  return (
    <div>
      Active count: {count} <br />
      <button onClick={() => {
          setCount(count + 1);
        }}
      >
        Increment
      </button>
      <SomeLibrary map={map} />
    </div>
  );
}

Run here

Does the object in JS locks the values of variables inside it after initializing?

I want to know the reason why function in object doesn't use the current value of count whenever invoked but React ref gets the current value in that same scenario

I don't understand why this works:
Replace the map variable with this:

const [count, setCount] = React.useState(3);
const stateRef = useRef();
stateRef.current = count;

const map = { key: () => {
  console.log("fn", stateRef.current);
}};

Does the object in JS locks the values of variables inside it after initializing?

No.

You're effectively setting state of SomeLibrary with an initial value when it mounts, and never again updating that state, so it continually logs its initial value.

const [mapState, setMapState] = useState(undefined);

useEffect(() => {
  console.log("setting map");
  // Run only once at mount of component
  setMapState(props.map); // <-- no other `setMapState` exists
}, []); // <-- runs once when mounting

By simply adding props.map to the dependency array this effect runs only when map updates, and correctly updates state.

useEffect(() => {
  console.log("setting map");
  // Run only once at mount of component
  setMapState(props.map);
}, [props.map]);

编辑 hardcore-kilby-n0ogv

Notice, however, the state of SomeLibrary is a render cycle behind that of App . This is because the value of the queued state update in SomeLibrary isn't available until the next render cycle. It is also an anti-pattern to store passed props in local component state ( with few exceptions ).

Why React ref gets the current value in that same scenario?

const [count, setCount] = React.useState(3);
const stateRef = useRef();
stateRef.current = count; // <-- stateRef is a stable reference

const map = { key: () => {
  console.log("fn", stateRef.current); // <-- ref enclosed in callback
}};

When react component props or state update, a re-render is triggered. The useRef does not, it's used to hold values between or through render cycles, ie it is a stable object reference. This reference is enclosed in the callback function in the map object passed as a prop. When the count state updates in App a rerender is triggered and stateRef.current = count; updates the value stored in the ref, ie this is akin to an object mutation.

Another piece to the puzzle is functional components are always rerendered when their parent rerenders. The passed map object is a new object when passed in props.

It's this rerendering that allows SomeLibrary to run the second effect to invoke the non-updated-in-state callback mapState.key , but this time the object reference being console logged has been mutated.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM