简体   繁体   中英

Why does this closure not work when used in a React component?

I'm trying to understand why this closure doesn't work as expected when used from a React component.

// state.ts
const state = { things: [] };

export function setThings(newThings: number[]) {
  state.things = newThings;
}
// App.tsx
import { setThings } from "./state";

function App() {
  const setThingsToArray = () => setThings([0, 1, 2]);
  return (
    <div>
      <button onClick={setThingsToArray}>Set things</button>
    </div>
  );
}

When setThings is called from non-React code it works as expected. It changes the things property of state .

However when I try to call it from a React component, as in App.tsx, it doesn't work at all. Debugging shows that when setThings is called from inside App , it thinks that state.things is an empty array [] , the value it was initialised to, even though state.things already contains lots of items.

This seems to imply that React is somehow using a copied version of setThings with a copied version of state . How is this possible? Shouldn't it work as a simple closure and reference the real state ?


I don't want to use React state management like Hooks or Redux because I want high performance for the changes in the array, as this is powering a canvas app with lots of particles and redraws per second. So I don't want to have to go through the entire React lifecycle if I don't need the React components to display the data.

EDIT: I made a mistake when writing up my component code for this question 🤦‍♂️. I fixed it now. The problem in my question is still happening.

EDIT 2:

Because @KevinB asked for proof that calling setThings elsewhere is correctly updating my array: https://youtu.be/Gp77NtqNQzo

Every "planet" in the canvas is represented by one item in the array. Coordinates and acceleration of every planet changes on every frame. When planets collide setThings is called with the changed array of planets. This needs to happen because multiple planets can "crash" into each other and combine into one, thus resulting in a state.things array with 2 items removed and 1 new item added.

Clicking the button calls setThings with an array containing only a single planet object. But as you can see it does nothing.

You are not returning anything from the setThings in state.ts

Yet you are assigning it to a const setThings in App.tsx

And on the <button onClick={setThings}> the setThings you are referencing is the undefined return from the call you make.

Try assigning an actual function to the <button> like this:

// App.tsx
import { setThings } from "./state";

function App() {
  const setThingsClickHandler = () => {
     setThings([0, 1, 2]);
  };

  return (
    <div>
      <button onClick={setThingsClickHandler}>Set things</button>
    </div>
  );
}

It turns out that because I was importing main.ts and App.tsx from my HTML file directly via two separate <script> tags, Parcel created two entirely separate bundles for them, which means that there was a separate state object in each bundle.

This is why updating one would not affect the other. 😕

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