reactjs - react useState setter导致重新渲染

标签 reactjs react-hooks

我想知道设置程序中的设置程序是否预期行为:const [state, setState] = useState(0)触发组件重新渲染。因此,如果我将setter作为 Prop 传递给组件,是否应该触发重新渲染?如果是,为什么?有什么办法可以避免这种情况?

我创建了一个非常简单的沙箱来演示此行为:https://codesandbox.io/s/bold-maxwell-qj5mi

在这里我们可以看到,当查看console.logs时,每次单击都会重新渲染按钮组件,而传递给它的incrementCounter函数没有改变。是什么赋予了?

最佳答案

如果您按一下memoize按钮,则不会遇到此现象。

具体来说,这是:

const Button = memo(({ incrementCounter }) => {
  const renderCount = useRef(1);
  console.log("button rendered: ", renderCount.current);
  renderCount.current++;
  return <button onClick={incrementCounter}>Increment</button>;
});

CodeSandbox镜像:

Edit delicate-http-b6sym

更新

如果您想要something from the docs,第一句话将告诉您为什么会这样。我知道那是setState的意思,但是useState Hook 也有同样的概念,但是文档很烂。 You can check out这部分文档,特别是说“第9行:” ...

请记住,当X组件中的状态发生变化时,X组件及其所有子组件都将被重新渲染。我知道React会告诉您“提升状态”,这是我从未理解的事情,因为提升状态会导致大量的重新渲染。

这就是为什么按钮会重新渲染的原因。因为状态在其父级中正在更改。父级(<App />)的counter状态已更改,从而触发<App />组件及其子级(包括<Button />)的重新呈现。

在我看来,React很难控制状态和重新渲染,而Redux可以提供帮助,但是总体像memouseCallback等之类的东西对我来说都像是创可贴。如果您将状态放在错误的组件中,那将是一段糟糕的时光。

包装<Button />组件in memo 基本上是这样说的:如果该组件有一个父级(在我们的情况下为<App />),并且该父级重新渲染,则我要查看我们所有的 Prop ,并且如果我们的 Prop 与我们收到的相比没有变化上一次,请勿重新渲染。本质上,只有在我们的 Prop 改变的情况下才重新渲染。这就是memo修复此问题的原因。.因为我们用来处理incrementCounter Prop 的函数没有改变-它保持不变。

我在下面添加了一些示例来说明这一点。

原始答案/摘录:

const { memo, useState, useCallback, useEffect, useRef } = React;
const { render } = ReactDOM;

const App = () => {
  const [counter, setCounter] = useState(0);
  const incrementCounter = useCallback(() => {
    setCounter(c => c + 1);
  }, [setCounter]);

  useEffect(() => {
    console.log("increment changed!");
  }, [incrementCounter]);

  return (
    <div>
      <CountValue counter={counter} />
      <Button incrementCounter={incrementCounter} />
    </div>
  );
}

const CountValue = ({ counter }) => {
  return <div>Count value: {counter}</div>;
};

const Button = memo(({ incrementCounter }) => {
  const renderCount = useRef(1);
  console.log("button rendered: ", renderCount.current);
  renderCount.current++;
  return <button onClick={incrementCounter}>Increment</button>
});

render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>



SNIPPET#2:

此代码段显示了如何重新渲染所有内容,而不仅仅是按钮。

const { useState, useEffect } = React;
const { render } = ReactDOM;

const App = () => {
  console.log("App rendered");
  const [counter, setCounter] = useState(0);
  const incrementCounter = () => setCounter(c => c + 1);

  useEffect(() => {
    console.log(" - Increment fired!");
    console.log();
  }, [incrementCounter]);

  return (
    <div>
      <CountValue counter={counter} />
      <Button incrementCounter={incrementCounter} />
      <p>Open console</p>
    </div>
  );
}

const CountValue = ({ counter }) => {
  console.log("CountValue rendered");
  return <div>Count value: {counter}</div>;
};

const Button = ({ incrementCounter }) => {
  console.log("Button rendered");
  return <button onClick={incrementCounter}>Increment</button>
};

render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>



SNIPPET#3:

此代码段显示了如何将状态等移动到<CountValue />组件中,但<App />组件不会重新呈现。

const { useState, useEffect } = React;
const { render } = ReactDOM;

const App = () => {
  console.log("App rendered");
  
  return (
    <div>
      <CountValue />
      <p>Open console</p>
    </div>
  );
}

const CountValue = () => {
  console.log("CountValue rendered");
  
  const [counter, setCounter] = useState(0);
  const incrementCounter = () => setCounter(c => c + 1);
  
  return (
    <div>
      <div>Count value: {counter}</div>
      <Button incrementCounter={incrementCounter} />
    </div>
  );
};

const Button = ({ incrementCounter }) => {
  console.log("Button rendered");
  console.log();
  return <button onClick={incrementCounter}>Increment</button>
};

render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>



SNIPPET#4:

该片段更多是一个思想实验,展示了如何使用渲染 Prop 。

const { useState, useEffect } = React;
const { render } = ReactDOM;

const App = () => {
  console.log("App rendered");

  return (
    <div>
      <CountValue present={({ increment, counter }) => {
        return (
          <div><Button incrementCounter={() => increment()} />
          <p>Counter Value: {counter}</p></div>
        )
      }} />
      <p>Open console</p>
    </div>
  );
}

const CountValue = ({ present }) => {
  const [counter, setCounter] = useState(0);
  
  const increment = () => {
    setCounter(c => c + 1);
  }
  
  console.log("CountValue rendered");
  return (
    <React.Fragment>
      {present({ increment, counter })}
    </React.Fragment>
  );
};

const Button = ({ incrementCounter }) => {
  console.log("Button rendered");
  return <button onClick={incrementCounter}>Increment</button>
};

render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>



SNIPPET#5:

看来这就是您要执行的操作。这仅重新渲染CountValue。这是通过将setCounter生成的useState方法通过回调对象传递给父级来实现的。

这样, parent 就可以操纵状态,而不必实际保持状态。

const { useState, useEffect } = React;
const { render } = ReactDOM;

const App = () => {
  console.log("App rendered");
  let increaseCount;

  return (
    <div>
      <CountValue callback={({increment}) => increaseCount = increment} />
      <Button incrementCounter={() => increaseCount()} />
      <p>Open console</p>
    </div>
  );
}

const CountValue = ({ callback }) => {
  console.log("CountValue rendered");
  const [counter, setCounter] = useState(0);
  
  callback && callback({
    increment: () => setCounter(c => c + 1)
  });
  
  return <p>Counter Value: {counter}</p>;
};

const Button = ({ incrementCounter }) => {
  console.log("Button rendered");
  return <button onClick={incrementCounter}>Increment</button>
};

render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

关于reactjs - react useState setter导致重新渲染,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60386389/

相关文章:

javascript - React - 如何映射嵌套对象值?

reactjs - react 背景图像叠加不透明度

javascript - 为什么 immer.js 不允许在草稿上设置动态属性?

javascript - 如果当前路径位于任何有效路线上,React 路由器仅显示导航组件

javascript - 暂时删除事件监听器,然后在执行某些操作后立即将其添加回来

reactjs - 如何在 React 中使用 Relax.js?

reactjs - 使用 react 钩子(Hook)操作数组

react-hooks - useState 取决于其他状态

ReactJS Hook : how to useContext in two different . js 文件?

javascript - 为什么 React 状态没有正确更新?