Доступ к состоянию из обратного вызова

#reactjs #typescript

#reactjs #typescript

Вопрос:

У меня возникли проблемы с доступом к состоянию компонента из обратного вызова.

Значение состояния num изменяется правильно, но такие изменения не видны функции обратного вызова, определенной во время загрузки.

 import React, {useState} from "react";

class MyObject {
  callback: (() => void);

  constructor(callback: () => void) {
    this.callback = callback;
  }
};

export const TestPage = () => {
  const [num, setNum] = useState<number>(0);

  // will print the changing values
  console.log(`num: ${num}`);

  const counter = useState<MyObject>(() => new MyObject(() => {
    // will always print 0
    console.log(`from callback. num: ${num}`);
  }))[0];

  const btnClick = () => {
    setNum((new Date()).getTime());
    counter.callback();
  };

  return (
      <div>Test, num: {num}
        <div>
          <button onClick={btnClick}>click to update state</button>
        </div>
      </div>
  );
}
  

вышеупомянутое не происходит с простым кодом без реакции следующим образом

   let myVar = 0;
  const obj = new MyObject(() => console.log(`myVar: ${myVar}`));
  obj.callback(); // prints 0
  myVar = 1;
  obj.callback(); // prints 1
  

редактировать: я пытался воспроизвести проблему с замыканиями, но не могу

  const makeFun = (callback: (() => void)) => {
    return () => {
      callback();
    }
  }
  let myVar = 0;
  const f1 = makeFun(() => console.log(`myVar: ${myVar}`));
  myVar = 1;
  const f2 = makeFun(() => console.log(`myVar: ${myVar}`));
  f1(); // prints 1
  f2(); // prints 1
  

поэтому я подозреваю, что это что-то конкретное для React и useState.

Есть идеи?

редактировать # 2: решение. вот как сделать его видимым в коде без реакции.

 let callbackPersistent: (() => void);
const createCallback = (callback: (() => void)) => {
  if (!callbackPersistent) {
    callbackPersistent = callback;
  }
  return callbackPersistent;
}

const myFunction = (num:number) => {
  const callback = createCallback(() => console.log(`callback num: ${num}`));
  callback();
};
  

а затем вызовите его как

   myFunction(0); // prints 0
  myFunction(1); // prints 0
  

существует только один экземпляр обратного вызова, и он указывает на значение, которое он видел при первом создании.

редактировать # 3:

Разумное решение можно найти с помощью useRef and useEffect , следующим образом:

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

class MyObject {
  callback: (() => void);

  constructor(callback: () => void) {
    this.callback = callback;
  }
};

export const TestPage = () => {
  const [num, setNum] = useState<number>(0);
  const numRef = useRef(num);

  // will print the changing values
  console.log(`num: ${num}`);

  const counter = useState<MyObject>(() => new MyObject(() => {
    // will always print the latest value
    console.log(`from callback. num: ${numRef.current}`);
  }))[0];

  useEffect(()=> {numRef.current = num}, [num]);

  const btnClick = () => {
    setNum(new Date().getTime());
    counter.callback();
  };

  return (
      <div>Test, num: {num}
        <div>
          <button onClick={btnClick}>click to update state</button>
        </div>
      </div>
  );
}
  

useRef гарантирует, что numRef это всегда один и тот же объект при нескольких вызовах. Когда значение num изменяется onEffect , вызов обновляет numRef.current значение, к которому затем обращается обратный вызов. Существует только один экземпляр обратного вызова и numRef существует, чтобы убедиться, что изменения правильно подобраны