反应useState setter导致重新渲染

时间:2020-02-25 01:42:57

标签: reactjs react-hooks

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

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

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

2 个答案:

答案 0 :(得分:3)

如果您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钩子也有相同的概念,但是文档很烂。 You can check out这部分文档,特别是上面写着“第9行:” ...

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

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

在我看来,React很难控制状态和重新渲染,而Redux可以提供帮助,但是memouseCallback等整体事物都感觉像乐队-对我有帮助。如果将状态放在错误的组件中,那将是一段糟糕的时光。

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

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


原始答案/摘要:

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>


片段#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>


片段#4:

此代码段更多是一个思想实验,展示了如何使用渲染道具。

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方法传递到父对象,通过回调对象。

通过这种方式,父级可以操纵状态,而不必实际保持状态。

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>

答案 1 :(得分:0)

是的,这是预期的。

如果避免重新渲染,则不会在屏幕上看到设置的值。

如果您要创建一个可以更改而不会触发重新渲染的值,请使用useRef