React挂钩:React.memo和useState问题

时间:2019-08-18 11:14:43

标签: reactjs react-hooks

我玩了反应钩子,遇到了一个我不明白的问题。

代码在这里https://codesandbox.io/embed/hnrch-hooks-issue-dfk0t 该示例包含2个简单的组件:

应用组件

const App = () => {
  const [num, setNum] = useState(0);
  const [str, setStr] = useState();

  const inc = () => {
    setNum(num + 1);
    setStr(num >= 5 ? ">= 5" : "< 5");
    console.log(num);
  };

  const button = <button onClick={inc}>++</button>;

  console.log("parent rerender", num);

  return (
    <div className="App">
      <h1>App</h1>
      <Child str={str}>{button}</Child>
    </div>
  );
};

子组件

const Child = React.memo(
  ({ str, children }) => {
    console.log("child rerender");
    return (
      <div>
        <>
          <h2>Functional Child</h2>
          <p>{str}</p>
          {children}
        </>
      </div>
    );
  }
  (prev, props) => {
    return prev.str === props.str;
  }
);

因此,我将孩子包裹在React.memo中,并且只有在str不同的情况下,才应重新渲染孩子。但也有孩子,并且传递了一个按钮,该按钮正在增加父(App)内部的计数器。

问题是:计数器设置为1后,计数器将停止递增。有人可以向我解释此问题以及避免那些错误我需要了解什么吗?

3 个答案:

答案 0 :(得分:1)

这是一个“关闭问题”。

这是App的第一次渲染时间,inc函数是第一次创建的:(我们称之为inc#1

const inc = () => {
  setNum(num + 1);
  setStr(num >= 5 ? ">= 5" : "< 5");
  console.log(num);
};

inc#1范围内,num当前为0。然后将该函数传递到button,然后传递给Child

到目前为止一切都很好。现在按下按钮,inc#1被调用,这意味着

setNum(num + 1);

发生num === 0的位置。 App被重新渲染,但是 Child不是。条件是如果prev.str === props.str,则我们不会再次渲染Child。

我们现在处于App的第二次渲染中,但是Child仍然拥有inc#1实例,其中num0

您知道问题出在哪里吗?您仍将调用该函数,但是inc现在已过时。

您可以通过多种方式解决此问题。您可以确保Child始终具有更新的道具。

或者您可以将回调传递给setState以获取当前值(而不是存在于闭包范围内的陈旧值)。这也是一个选择:

const inc = () => {
  setNum((currentNum) => currentNum + 1);
};

React.useEffect(() => {
  setStr(num >= 5 ? ">= 5" : "< 5");
}, [num])

答案 1 :(得分:1)

这里有几件事。

  1. 如果您要修改状态,并且其新值取决于状态的先前值,请使用setState的功能形式:
 setNum(num => num + 1);   
  1. setState是异步的,因此当您尝试setStr时,num值尚未更新。甚至,在您的特定情况下,inc将从状态中关闭(即创建一个闭合)num的值,因此在该函数内部,它将始终具有其初始值-0。 要解决此问题,您需要在num更改时使用效果钩子来更新字符串:
useEffect(() => {
    setStr(num >= 5 ? ">= 5" : "< 5");
  }, [num]) // Track the 'num' var here

答案 2 :(得分:0)

解决该错误的一种方法是将inc函数更改为此:

const inc = () => {
  setNum(n => {
    const newNum = n + 1;
    setStr(newNum >= 5 ? ">= 5" : "< 5");
    return newNum;
  });
};

请注意,setState现在传递了一个回调函数,该函数接收旧值并返回新值。这样,“关闭”问题就解决了。