从回调访问状态

时间:2020-09-22 13:21:36

标签: 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特有的。

有什么想法吗?

edit#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

可以使用useRefuseEffect找到合理的解决方案,如下所示:

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的一个实例,以确保正确提取了更改

1 个答案:

答案 0 :(得分:0)

回调函数在num状态下有一个closure,其值为零,因为在定义回调时,num的值为零。

要解决此问题,您可以使用useEffect钩子,该钩子使用新的回调函数创建一个新的MyObject,该回调函数对最新值num进行封闭。

useEffect(() => {
   // update counter state
}, [num]);

编辑

非反应代码之所以能按预期工作的原因是因为对变量而不是其值关闭功能。因此,在第二个代码示例中,传递给MyObject的构造函数的回调函数对myVar变量进行了关闭。这意味着,如果您更改myVar的值,则回调函数在被调用时将记录myVar的最新值。

现在您可能会问,如果闭包是在变量而不是变量的值之上,那么React代码中的回调函数不应该看到num的最新值吗?

造成回调函数总是记录为零的原因是因为在React中,回调函数看到定义了该回调函数时组件中可用的stateprop的值


我认为您根本不应该使用MyObject类。如果您有一些复杂的状态,那么我建议您考虑使用useReducer挂钩来管理状态。