React hooks:React.memo 和 useState 的问题

IT技术 reactjs react-hooks
2021-05-27 04:08:37

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

代码在这里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;
  }
);

所以我把孩子包裹在 a 中React.memo,只有在str不同时才应该重新渲染但是它也有子项,并且它传递了一个按钮,该按钮在父项(应用程序)内增加一个计数器。

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

3个回答

这是一个“关闭问题”。

这是 的第一次渲染时间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始终具有更新的props。

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

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

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

这里有几件事。

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

修复错误的一种方法是将inc函数更改为:

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

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