React怎么判断什么时候该重新渲染组件?

4,087 阅读5分钟
原文链接: segmentfault.com

React因为他的性能而著名。因为他有一个虚拟DOM层并且只有在需要时才更新真实DOM。即使是同样地信息这也比一直直接更新DOM要快很多。但是,React的智能仅此而已(目前为止),我们的任务是知道React的预期行为以及限制,这样我们才不会意外损失性能。

我们需要关注的一方面是React如何决定什么时候重新渲染组件。不是重新渲染DOM节点,只是调用render方法来改变虚拟DOM。我们可以通过告诉React什么时候需要渲染什么时候不需要渲染来帮助React。让我们依次来看看这些。

1. 组件的状态发生改变

只有在组件的state变化时才会出发组件的重新渲染。状态的改变可以因为props的改变,或者直接通过setState方法改变。组件获得新的状态然后React决定是否应该重新渲染组件。不幸的是,React难以置信简单地将默认行为设计为每次都重新渲染。

组件改变?重新渲染。父组件改变?重新渲染。一部分没有导致视图改变的props改变?重新渲染。

class Todo extends React.Component {

    componentDidMount() {
        setInterval(() => {
            this.setState(() => {
                console.log('setting state');
                return { unseen: "does not display" }
            });
        }, 1000);
    }

    render() {
        console.log('render called');
        return (<div>...</div>);
    }
}

在这个(非常刻意的)例子中,Todo将会每秒重新渲染依次,即使render方法根本没有使用unseen。事实上,unseen值甚至都不改变。你可以在CodePen里查看这个例子的实际版本。

好吧,但是每次都重新渲染没有什么帮助。

我的意思是,我非常感谢React的细心谨慎。如果状态改变但是组件没有正确渲染的话更糟。权衡之下,每次都重新渲染绝对是一个安全的选择。

但是重新渲染的时间成本看起来非常昂贵(例子里非常夸张地表现了出来)。

是的,在不必要的时候重新渲染会浪费循环并且不是一个好的想好。但是,React不能知道什么时候可以安全的跳过重新渲染,所以React无论是否重要每次都重新渲染。

我们如何告诉React跳过重新渲染?

那就是第二点要说的内容。

2. shouldComponentUpdate方法

shouldComponentUpdate方法默认返回true,这就是导致每次更新都重新渲染的原因。但是你可以在需要优化性能时重写这个方法来让React更智能。比起让React每次都重新渲染,你可以告诉React你什么时候不像触发重新渲染。

当React将要渲染组件时他会执行shouldComponentUpdate方法来看它是否返回true(组件应该更新,也就是重新渲染)。所以你需要重写shouldComponentUpdate方法让它根据情况返回true或者false来告诉React什么时候重新渲染什么时候跳过重新渲染。

当你使用shouldComponentUpdate方法你需要考虑哪些数据对与重新渲染重要。让我们回到这个例子。

正如你所看到的,我们只想在titledone属性改变的时候重新渲染Todo。我们不关心unseen是否改变,所以我没有把它包含在shouldComponentUpdate方法中。

当React渲染Todo组件(通过setState触发)他会首先检查状态是否改变(通过propsstate)。假设状态改变了(因为我们显式地调用了setState所以这会发生)React会检查TodoshouldComponentUpdate方法。React会根据shouldComponentUpdate方法返回值为true或者false来决定从哪里渲染。

更新后的代码仍然会每秒调用一次setState但是render只有在第一次加载时(或者titledone属性改变后)才会调用。你可以在这里看到。

看起来有很多工作去做。

是的,这个例子非常冗长因为有两个属性(titledone)需要关注并且只有一个可以忽略(unseen)。根据你的数据可能仅检查一个或两个属性并且忽略其他会更有意义。

重要提示

当子组件的的state变化时, 返回false并不能阻止它们重渲染。

– Facebook的React文档

这作用于子组件的状态而不是他们的props。所以如果一个子组件内部管理了一些他自己的状态(使用他自己的setState),这仍然会更新。但是如果父组件的shouldComponentUpdate方法返回了false就不会传递更新后的props给他的子组件,所以子组件不会重渲染,即使他们的props变化了。

额外内容:简单性能测试

编写并且在shouldComponentUpdate方法中运行计算的时间成本可能会很昂贵,所以你需要确保值得做。在写shouldComponentUpdate方法前你可以测试React一个周期默认会消耗多少时间。有了这个信息做参考,在做性能优化时你可以做一个不盲目的决定。

使用React的性能工具去发现浪费的周期:

Perf.start()
// Do the render
Perf.stop()
Perf.printWasted()

哪一个组件浪费了很多渲染周期?你怎么通过shouldComponentUpdate方法让他们更智能?试着使用性能测试工具来比较他们的性能。