反应 useState setter 导致重新渲染

Cap*_*ggz 5 reactjs react-hooks

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

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

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

Mat*_*ich 6

如果您记住按钮,则不会遇到此行为。

具体来说,这:

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

代码沙盒镜像:

编辑 detail-http-b6sym


更新

如果你想从文档中得到一些东西,第一句话会告诉你为什么会这样。我知道那是为了setState但同样的概念遵循useState钩子,但是那种糟糕的文档。您可以查看文档的这一部分,特别是“第 9 行:”...

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

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

在我看来阵营是很难尽可能状态,并重新渲染控制,其终极版可以帮助,但总体喜欢的东西memouseCallback等等。所有的感受就像创可贴给我。如果你把你的状态放在错误的组件中,你会过得很糟糕。

包装的<Button />成分memo基本上是说:如果组件有一个父(在我们的情况下<App />),而家长重新呈现,我想看看我们所有的道具,如果我们的道具都没有从我们收到的最后一次改变,不要重新渲染。本质上,只有在我们的道具发生变化时才重新渲染。这就是为什么要memo修复这个……因为我们用来处理incrementCounterprop 的函数不会改变——它保持不变。

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


原始答案/片段:

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);
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)


片段 #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);
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)


片段 #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);
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)


片段 #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);
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)


片段 #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);
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)