ReactJS-每次调用“ setState”时都会调用渲染吗?


502

每次setState调用时,React都会重新渲染所有组件和子组件吗?

如果是这样,为什么?我以为这个想法是,当状态改变时,React只渲染所需的内容。

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

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

我曾希望只有在state数据更改的情况下才会进行渲染。

这是示例代码,例如JS Fiddle和嵌入式代码段:

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>

[1]: http://jsfiddle.net/fp2tncmb/2/

我遇到了同样的问题,我不知道确切的解决方案。但是我已经从正常启动的组件中清除了不需要的代码。
杰森

我想指出的是,在您的示例中-因为元素的设计方式并不仅仅依赖于唯一状态- setState()即使使用伪数据进行调用也确实会导致元素呈现不同的外观,因此我想说是。绝对应该在可能发生某些更改时尝试重新渲染您的对象,因为否则您的演示(假设这是预期的行为)将无法正常工作!
塔德格·麦当劳-詹森

@ TadhgMcDonald-Jensen您可能是正确的-但据我了解,React会第一次渲染它(因为状态会从零变为第一次),然后再也不必渲染。当然,我错了-因为看起来React要求您编写自己的shouldComponentUpdate方法,我认为它的简单版本必须已经包含在React本身中。听起来像react中包含的默认版本只是返回true-这迫使组件每次都重新渲染。
布拉德·帕克斯

是的,但是只需要在虚拟DOM中重新渲染,然后仅在组件呈现方式不同时才更改实际DOM。对虚拟DOM的更新通常可以忽略不计(至少与在实际屏幕上进行修改相比),因此每次可能需要更新时都调用render,然后看到没有发生任何更改并不比假定应进行渲染那样昂贵且安全。
塔德格·麦当劳-詹森,

Answers:


569

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

默认情况下-是。

有一个方法boolean shouldComponentUpdate(object nextProps,object nextState),每个组件都有此方法,它负责确定“组件应该更新(运行渲染功能)吗?”。每次更改状态或从父组件传递新的道具时

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

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

默认情况下,在状态发生适当变化时,shouldComponentUpdate始终返回true以防止细微的错误,但是如果您始终将状态视为不可变并在render()中对props和state进行只读处理,则可以使用比较旧道具和状态及其替换的实现。

问题的下一部分:

如果是这样,为什么?我以为这个想法是,当状态改变时,React只渲染所需的内容。

我们可以将“渲染”分为两个步骤:

  1. 虚拟DOM渲染:调用render方法时,它将返回组件的新虚拟dom结构。如前所述,此渲染方法总是在您调用setState()时调用,因为默认情况下shouldComponentUpdate始终返回true。因此,默认情况下,React中没有优化。

  2. 原生DOM渲染:只有在虚拟DOM中更改了真实DOM节点后,React才更改浏览器中的真实DOM节点,并且需要的次数很少-这就是React的一项出色功能,它可以优化真实DOM变异并提高React的速度。


47
很好的解释!在这种情况下,真正使React发光的一件事是,除非更改了虚拟DOM,否则它不会更改实际 DOM。因此,如果我的render函数连续两次返回相同的东西,那么真正的DOM根本不会改变...谢谢!
布拉德·帕克斯

1
我认为@tungd是正确的-请在下面查看他的答案。这也是组件之间的父子关系的问题。例如,如果TimeInChildMain的同级,则不会不必要地调用其render()
gvlax

2
如果您的状态包含一个相对较大的javascript对象(共约1k个属性),将其渲染为一棵大型组件树(共约100个...)...应让渲染函数构造虚拟dom,还是应在设置之前状态,手动将新状态与旧状态进行比较,并且仅setState在发现有差异时才调用?如果是这样,如何做到最好-比较json字符串,构造和比较对象哈希,...?
Vincent Sels

1
@Petr,但是即使react重构了虚拟dom,如果旧的虚拟dom与新的虚拟dom相同,浏览器dom也不会被触摸,不是吗?
Jaskey '16

3
另外,请研究使用React.PureComponent(reactjs.org/docs/react-api.html#reactpurecomponent)。仅在组件的状态或道具实际上已更改时才更新(重新渲染)。但是请注意,比较是浅薄的。
辩论者

104

不,状态改变时,React不会渲染所有内容。

  • 每当组件变脏(状态更改)时,都会重新渲染该组件及其子代。在某种程度上,这是要尽可能少地重新渲染。唯一不调用render的时候就是将某个分支移到另一个根,从理论上讲,我们不需要重新渲染任何东西。在您的示例中,TimeInChild是的子组件Main,因此在状态Main更改时也会重新呈现。

  • React不比较状态数据。当setState被调用时,它标记组件为脏(这意味着它需要重新呈现)。需要注意的重要一点是,尽管render调用了组件的方法,但是仅当输出与当前DOM树不同(也就是在虚拟DOM树和文档的DOM树之间进行区分)时,才更新实际DOM。在您的示例中,即使state数据没有发生变化,上次更改的时间也发生了变化,从而使Virtual DOM与文档的DOM有所不同,因此更新了HTML。


是的,这是正确的答案。作为实验,将最后一行修改为React.renderComponent(<div> <Main /> <TimeInChild /> </ div>,document.body) ; 并除去<TimeInChild />从所述主体呈现()的的主要组件。默认情况下不会调用TimeInChildrender(),因为它不再是Main的子级。
gvlax

谢谢,像这样的东西有些棘手,这就是为什么React作者建议该render()方法应该是“纯”的-与外部状态无关。

3
@tungd是什么意思some branch is moved to another root?你叫branch什么 你叫root什么
格林

2
我真的更喜欢标记为正确答案的答案。我理解您对本机,“真实”方面的“渲染”的解释...但是,如果您从反应本机方面看,您必须说它再次渲染。幸运的是,它足够聪明,可以确定真正更改的内容并仅更新这些内容。对于新用户来说,这个答案可能会令人困惑,首先您说不,然后您解释事情确实得到了呈现
WiRa

1
@tungd可以ü请解释格林的问题what does it mean some branch is moved to another root? What do you call branch? What do you call root?
madhu131313

6

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

  • 实现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>



2

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

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

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

另一种解决方案是在状态中添加一个初始化的属性,并在第一次设置它(如果状态被初始化为非空值)。


0

并非所有组件。

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

因此更改发生在setState调用的位置。renders然后从那里叫树。如果您使用的是纯组分,render则将被跳过。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.