【问题标题】:ReactJS - Does render get called any time "setState" is called?ReactJS - 每次调用“setState”时都会调用渲染吗?
【发布时间】:2019-08-14 05:26:16
【问题描述】:

React 是否会在每次调用 setState() 时重新渲染所有组件和子组件?

如果是这样,为什么?我认为这个想法是 React 只在需要时渲染 - 当状态改变时。

在下面的简单示例中,两个类在单击文本时再次呈现,尽管状态在后续单击时不会改变,因为 onClick 处理程序总是将 state 设置为相同的值:

this.setState({'test':'me'});

我原以为只有在 state 数据发生变化时才会进行渲染。

这是示例的代码,as a JS Fiddle 和嵌入的 sn-p:

var TimeInChild = React.createClass({
    render: function() {
        var t = new Date().getTime();

        return (
            <p>Time in child:{t}</p>
        );
    }
});

var Main = React.createClass({
    onTest: function() {
        this.setState({'test':'me'});
    },

    render: function() {
        var currentTime = new Date().getTime();

        return (
            <div onClick={this.onTest}>
            <p>Time in main:{currentTime}</p>
            <p>Click me to update time</p>
            <TimeInChild/>
            </div>
        );
    }
});

ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>

【问题讨论】:

  • 我有同样的问题,我不知道确切的解决方案。但是我已经从它开始正常工作的组件中清除了不需要的代码。
  • 我想指出,在您的示例中 - 因为您的元素的设计方式不仅仅依赖于独特的状态 - 即使使用虚拟数据调用 setState() 也会导致元素呈现不同的方式,所以我会说是的。当某些事情可能发生变化时,它绝对应该尝试重新渲染您的对象,否则您的演示 - 假设它是预期的行为 - 将无法正常工作!
  • 你可能是对的@TadhgMcDonald-Jensen - 但据我了解,React 会第一次渲染它(因为状态从无变为第一次),然后再也不需要渲染.当然,我错了——因为看起来 React 要求你编写自己的 shouldComponentUpdate 方法,我假设它的简单版本必须已经包含在 React 本身中。听起来像 react 中包含的默认版本只是返回 true - 这会强制组件每次重新渲染。
  • 是的,但它只需要在虚拟 DOM 中重新渲染,然后如果组件渲染不同,它只会更改实际的 DOM。对虚拟 DOM 的更新通常可以忽略不计(至少与修改实际屏幕上的内容相比),因此每次可能需要更新时调用渲染然后看到没有发生任何更改,这并不比假设它应该渲染相同的成本和安全性更高。

标签: javascript reactjs


【解决方案1】:

React 是否会在每次调用 setState 时重新渲染所有组件和子组件?

默认情况下 - 是的。

有一个方法boolean shouldComponentUpdate(object nextProps, object nextState),每个组件都有这个方法,它负责确定“应该组件更新(运行render函数) ?”每次更改 state 或从父组件传递新的 props 时。

您可以为您的组件编写自己的 shouldComponentUpdate 方法实现,但默认实现始终返回 true - 意味着始终重新运行渲染函数。

引用官方文档http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate

默认情况下,shouldComponentUpdate 总是返回 true 以防止 当状态发生变化时出现细微的错误,但如果你小心 始终将 state 视为不可变的,并且从 props 和 state 中只读 在 render() 然后你可以用一个覆盖 shouldComponentUpdate 将旧的道具和状态与其进行比较的实现 替换。

你问题的下一部分:

如果是这样,为什么?我认为这个想法是 React 只在需要时渲染 - 当状态改变时。

我们可以称之为“渲染”的有两个步骤:

  1. Virtual DOM 渲染:当调用 render 方法时,它会返回组件的新 virtual dom 结构。正如我之前提到的,当你调用 setState() 时总是会调用这个 render 方法,因为默认情况下 shouldComponentUpdate 总是返回 true。因此,默认情况下,React 中没有优化。

  2. Native DOM 渲染:React 仅在虚拟 DOM 中更改真实 DOM 节点时才会更改浏览器中的真实 DOM 节点,并且只需少量更改 - 这是 React 的伟大功能,可优化真实 DOM 突变并使 React 更快。

【讨论】:

  • 很好的解释!在这种情况下,真正让 React 仍然大放异彩的一件事是它不会改变 real DOM,除非虚拟 DOM 已经改变。因此,如果我的渲染函数连续 2 次返回相同的内容,那么真正的 DOM 根本不会改变......谢谢!
  • 我认为 @tungd 是正确的 - 请参阅下面的答案。这也是组件之间的父子关系的问题。例如,如果 TimeInChildMain 的兄弟,那么它的 render() 将不会被不必要地调用。
  • 如果你的 state 包含一个相对较大的 javascript 对象(大约 1k 个属性),它被渲染为一个大的组件树(大约 100 个)......你应该让渲染函数构造虚拟dom,或者您是否应该在设置状态之前手动将新状态与旧状态进行比较,并且仅在检测到有差异时才调用setState?如果是这样,如何做到最好 - 比较 json 字符串,构造和比较对象哈希,...?
  • @Petr ,但是即使 react 重构了虚拟 dom,如果旧的虚拟 dom 和新的虚拟 dom 一样,浏览器的 dom 不会被触动,不是吗?
  • 另外,考虑使用 React.PureComponent (reactjs.org/docs/react-api.html#reactpurecomponent)。只有当组件的状态或道具实际发生变化时,它才会更新(重新渲染)。但是请注意,这种比较是肤浅的。
【解决方案2】:

不,当状态发生变化时,React 不会渲染所有内容。

  • 只要组件变脏(其状态发生更改),就会重新渲染该组件及其子组件。这在某种程度上是为了尽可能少地重新渲染。唯一不调用 render 的时候是某个分支移动到另一个根目录时,理论上我们不需要重新渲染任何东西。在您的示例中,TimeInChildMain 的子组件,因此当Main 的状态发生变化时,它也会重新渲染。

  • React 不比较状态数据。当setState 被调用时,它会将组件标记为脏(这意味着它需要重新渲染)。需要注意的重要一点是,尽管调用了组件的 render 方法,但只有在输出与当前 DOM 树不同时才会更新真实 DOM(也就是虚拟 DOM 树和文档的 DOM 树之间的差异)。在您的示例中,即使 state 数据没有更改,但上次更改的时间确实发生了变化,使得虚拟 DOM 与文档的 DOM 不同,因此 HTML 会更新。

【讨论】:

  • 是的,这是正确的答案。作为实验,将最后一行修改为 React.renderComponent(
    , document.body)
    ;并从 Main 组件的 render() 主体中删除 TimeInChildrender() 默认不会被调用,因为它不再是 Main 的子节点。
  • 谢谢,像这样的东西有点棘手,这就是为什么 React 作者建议 render() 方法应该是“纯”的——独立于外部状态。
  • @tungd,some branch is moved to another root 是什么意思?你叫什么branch?你叫什么root
  • 我真的更喜欢标记为正确的答案。我理解你对原生“真实”方面的“渲染”的解释......但是如果你从 react-native 方面看它,你必须说它再次渲染。幸运的是,它足够聪明,可以确定真正发生了什么变化并只更新这些东西。这个答案可能会让新用户感到困惑,首先你说不,然后你解释说事情确实会被渲染......
  • @tungd 你能解释一下格林的问题what does it mean some branch is moved to another root? What do you call branch? What do you call root?
【解决方案3】:

即使此处的许多其他答案中都已说明,该组件也应该:

  • 实现 shouldComponentUpdate 以仅在状态或属性更改时呈现

  • 切换到扩展 PureComponent,它已经在内部实现了 shouldComponentUpdate 方法以进行浅层比较。

这是一个使用shouldComponentUpdate 的示例,它仅适用于这个简单的用例和演示目的。使用此选项时,组件不再在每次单击时重新呈现自身,而是在第一次显示时和被单击一次后呈现。

var TimeInChild = React.createClass({
    render: function() {
        var t = new Date().getTime();

        return (
            <p>Time in child:{t}</p>
        );
    }
});

var Main = React.createClass({
    onTest: function() {
        this.setState({'test':'me'});
    },

    shouldComponentUpdate: function(nextProps, nextState) {
      if (this.state == null)
        return true;
  
      if (this.state.test == nextState.test)
        return false;
        
      return true;
  },

    render: function() {
        var currentTime = new Date().getTime();

        return (
            <div onClick={this.onTest}>
            <p>Time in main:{currentTime}</p>
            <p>Click me to update time</p>
            <TimeInChild/>
            </div>
        );
    }
});

ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>

【讨论】:

    【解决方案4】:

    是的。每次我们调用setState 时,它都会调用render() 方法,除非shouldComponentUpdate 返回false

    【讨论】:

    • 请详细说明
    【解决方案5】:

    当使用 React 钩子时,似乎接受的答案不再是这种情况(使用原始值,有关详细信息,请参阅此答案上的 cmets)。在这段代码沙箱中可以看到,当状态设置为相同的值时,类组件会重新渲染,而在函数组件中,将状态设置为相同的值不会导致重新渲染。

    https://codesandbox.io/s/still-wave-wouk2?file=/src/App.js

    【讨论】:

    • 这是因为在您的示例中,您将原始值传递给 useState 挂钩,并将复杂对象传递给 setState 方法。尝试将对象或数组传递给 useState 挂钩以查看函数组件中的重新渲染,因为您正在更改对象引用。只是为了澄清你的例子,但你是对的,使用带有原始值的 useState 钩子可以防止重新渲染。
    • @MichalSvorc 确实,如果我们在 useState 中使用了一个对象,那么它也会重新渲染,但关键是不可能将简单的原语与类组件的 setState 函数一起使用。该示例仅展示了使用最简单的类组件状态形式和最简单的 useState 形式之间的区别,以及 hooks 版本如何(稍微)更高性能?
    • 当然,我刚刚添加了一个解释,说明为什么在您的示例中 useState 没有重新渲染。有人可能会将一个对象传递给 useState(这是合法的),认为该钩子会自动阻止重新渲染,因为您说“使用 React 钩子时,接受的答案不再是这种情况”。有人可能不会立即发现使用基元和对象引用的区别。
    • 有道理,谢谢。我对答案添加了一个小的说明
    【解决方案6】:

    “丢失更新”的另一个原因可能是下一个:

    如果是问题那么U可以避免在更新期间设置状态,你应该像这样检查状态参数值

    static getDerivedStateFromProps(props: TimeCorrectionProps, state: TimeCorrectionState): TimeCorrectionState {
       return state ? state : {disable: false, timeCorrection: props.timeCorrection};
    }
    

    另一种解决方案是给state添加一个已初始化的属性,并在第一时间设置它(如果state被初始化为非空值。)

    【讨论】:

      【解决方案7】:

      并非所有组件。

      组件中的state看起来像是整个APP状态瀑布的来源。

      所以改变发生在 setState 调用的地方。然后从那里调用renders 的树。如果你使用过纯组件,render 将被跳过。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-05-31
        • 2017-07-23
        • 2019-04-22
        • 2021-02-04
        • 2020-11-22
        • 1970-01-01
        • 2019-11-23
        相关资源
        最近更新 更多