[Reserved | translation] how to identify and resolve duplicate rendering React

Reprinted and translated freecodeacmp in Nayeem Reza article: How to identify and resolve wasted renders in React
description link: https://www.freecodecamp.org/news/how-to-identify-and-resolve-wasted-renders -in-react-cc4b1e910d10 /

Recently, I think the problem I'm developing react performance analysis applications, suddenly thought of setting some performance indicators. The first thing I discovered is that each page rendering waste problem I want to solve. You might wonder what is rendering waste? Let's look further.

Initially, React changed the philosophy of web development, but also changed the way of thinking front-end developers. After the introduction of VirtualDOM, React update UI makes possible efficient, web application experience becomes clean. Have you ever thought about how to make your application React faster? Why medium-sized React web application or poor performance? The problem is how we use React.

React How does it work

React like a modern front-end library does not make our application more quickly. First, developers should understand React how it works. How components throughout the life cycle of components within the application life cycle? So, before further optimization techniques, we need to work on how to React to have a better understanding.

React in the core, we have JSX grammar and build, compare VirtualDOM powerful capabilities. After their release, React influenced many other front-end library. For example, Vue also depends on VirtualDOM.

React each application begins from a root component. We can be the entire application as a tree structure, each node is a component. React the components based on the data to render UI. That means it receives props and state

UI = CF(data) // Component Function

User interaction through the UI to change the data. Users can do everything in our application is interactive. For example: Click the button, slide images, drag and drop list items, call the AJAX request to the API. All these interactions only changed data, UI never change.

Here, the definition would only apply state data. We exist not just something in the database, or even a different front-end state, such as the currently selected tab, or when the money whether to check the box, all data part . Whenever data changes, React by re-render UI component functions, but only virtually:

UI1 = CF(data1) UI2 = CF(data2)

React comparison between different new VirtualDOM UI and UI (UI after re-render) the current through DOMdiff algorithm.

Changes = Difference(UI1, UI2)

Then React UI changes only apply to the real part of the browser UI, when the data associated with a component changes, React determine whether real need to update the DOM. This avoids React many 'expensive'DOM operating in the browser, for example: Create a DOM node, unnecessarily access to the existing node.

Repeat distinguish between components and rendering may be one of the major sources of any React application performance problems. React to build the application in the case of DOM diff algorithms can not effectively coordinated, will lead to repeat the entire application rendering, which is rendered waste, then get a slow sense of experience.

In the initial rendering process, React made DOM like this

Suppose a part of the change data, we expect that this re-rendered directly dependent on the partial data component, or even skip the process of other components diff. Suppose the above figure 2 changes in the component parts data, data transmitted from the R B, R thence to 2. If the re-rendering, then each of its child components A, B, C, D are re-rendered. This process, React really doing is (note Compared with the previous)

The figure above, all the yellow nodes are re-rendered and diff, which leads to a waste of time and computing resources. This is our main optimized --- each component is configured to re-render and diff if necessary. This recycling waste CPU cycles. First, let's examine how to identify our application to render wasted.

Recognition rendering waste

There are several different methods to identify. The simplest option is to Highlight Updates React dev tools in.

当与app交互时,就会有颜色的边框闪烁提示。这就能看出重复渲染的组件了。这可以让我们发现不需要的重渲染。

跟随下面的例子。

注意,当我们输入第二个todo项时,每次按键,第一个todo还在闪烁。这意味着它连同输入被React重新渲染。这就是所谓的“渲染浪费”。我们知道这是不必要的,因为第一个todo的内容并没有改变,但是React可不知道。

即使React只更新了改变的DOM节点,重渲染仍花了一些时间。在许多情况下,这并不是问题,但是当交互变得缓慢时,我们就要考虑以下阻止这些冗余渲染了。

使用shouldComponentUpdate方法

默认情况下,React会渲染虚拟DOM并在树中每个组件props和state变化时比较它们的不同。这显然是不合理的:随着应用的增长,每次变化都试图重新渲染并比较整个虚拟DOM最终会拖慢应用。

React提供了一个简单的生命周期方法shouldComponentUpdate来指示一个组件是否需要重新渲染,它在重新渲染开始前触发,默认返回为 true.

function shouldComponentUpdate(nextProps, nextState) {
    return true
}

当任意组件的shouldComponentUpdate函数返回true时,它允许触发diff渲染过程,这就给了我们控制重新渲染的能力。假设我们要阻止组件被重新渲染,我们只需在函数中返回false即可。正如我们看到的,我们可以比较当前和下一个props和state来决定是否重新渲染。

function shouldComponentUpdate(nextProps, nextState) {
    return nextProps.id !== this.props.id
}

使用纯组件pure component

你使用React,你一定知道React.Component是什么,但是React.PureComponent是个啥玩意儿?上面我们讨论了shouldComponentUpdate函数,在纯组件中,有一个默认的shouldComponentUpdate实现,进行浅层的prop和state比较。所以,纯组件就是只会在props和state与之前不一样时才重新渲染的组件。

function shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this.props, nextProps) && shallowCompare(this.state, nextState)
}

在浅层比较中,基本数据类型像字符串,布尔,数字,通过值进行比较;复杂数据类型,比如数组、对象、函数通过引用比较(地址)

但是,如果我们有一个函数无状态组件在每次冲渲染之前要实现比较怎么办?React有一个高阶组件React.memo.它和React.PureComponent类似,只不过是用于函数组件。

const functionalComponent = (props) => {
    /* render using props */
}

export default React.memo(functionalComponent)

默认情况下,它做的事情和shouldComponentUpdate()一样,只是浅层的比较props对象,但是如果我们想要控制整个比较过程呢?可以自定义比较函数作为第二个参数。

const functionalComponent = (props) => {
    /* render using props */
}

const areEqual = (prevProps, nextPoprs) => {
    /*
        传入nextProps得到的渲染结果和prevProps一样时返回true,否则返回false
    */
}

export default React.memo(functionalComponent, areEqual)

使数据不可变

如果我们可以使用React.PureComponent,但仍然有一个有效的方式来判断任何复杂的props或state(如数组、对象等)何时自动改变。这就是不可变数据结构使生命周期更简单。

使用不可变数据结构的背后思想很简单。正如我们之前提到的,对于复杂的数据类型,比较在它们的引用上执行。当一个包含复杂数据的对象改变时,不是改变原有对象,而是复制原有对象的内容到新建的对象,对新建对象做出改变。

ES6的对象结构运算可以实现这一操作。

const profile = { name: 'Lebrown James', age: 35}

const updateProfile = (profile) => {
    return { ...profile, gender: 'male' }
}

也可以用数组

this.state = { languages: ['French', 'English' ]}

this.setState(state => ({
    words: [ ...state.languages, 'Spanish' ]
}))

对于同一个旧数据,避免传入新的引用

我们知道每当组件的props改变就会初发重新渲染。有时props没有改变,但我们用了一种React认为确实改变了props的方式编写代码,就会造成渲染浪费。所以,我们要确定传递不同的引用作为不同数据的props.同样,对于同样的数据也要避免传递新的引用。现在,我们来看一下这种问题的例子:

class BookInfo extends Component {
    render() {
        const book = {
            name: '1984',
            author: 'George Orwell',
            publishedAt: 'June 8, 1949',
        }

        return (
            <div>
                <BookDescription book={book} />
                <BookReview reviews={this.props.reviews} />
            </div>
        )
    }
}

我们在BookInfo组件中渲染了BookDescription和BookReview两个组件。代码是正确的,工作正常,但是有一个问题:每当我们获取一个新的reviews数据作为props时,BookDescription就会重新渲染。为什么会这样?BookInfo组件一接收到新的props,render函数就被调用来创建其元素树。render函数创建了一个新的book常量,也就是创建了一个新的引用。所以,BookDescription将会得到这个book常量作为引用,BookDescription也就会重新渲染。我们可以将这段代码分解为:

class BookInfo extends Component {
    
    const book = {
        name: '1984',
        author: 'George Orwell',
        publishedAt: 'June 8, 1949',
    }
    
    render() {
        return (
            <div>
                <BookDescription book={this.book} />
                <BookReview reviews={this.props.reviews} />
            </div>
        )
    }
}

现在,引用就总是一样的了-- this.book并且渲染时不会创建新的对象了。重新渲染的概念适用于每一个prop包括事件处理,比如:

class Foo extends Component {
    handleClickOne() {
        console.log('Click happened in One')
    }

    handleClickTwo() {
        console.log('Click happened in Two')
    }

    render() {
        return (
            <Fragment>
                <button onClick={this.handleClickOne.bind(this)}>
                    Click me One!
                </button>
                <button onClick={() => this.handleClickTwo()}>
                    Click me Two!
                </button>
            </Fragment>
        )
    }
}

这里,我们用了两种不同的方式(在render中使用bind方法和箭头函数)来调用事件处理方法但是每次在组件重新渲染时两种方法都会创建一个新的函数。所以,为了解决这个问题,我们可以在constructor中进行方法绑定

class Foo extends Component {
    constructor() {
        this.handleClickOne = this.handleClickOne.bind(this)
    }
    
    handleClickOne() {
        console.log('Click happened in One')
    }

    handleClickTwo() => {
        console.log('Click happened in Two')
    }

    render() {
        return (
            <Fragment>
                <button onClick={this.handleClickOne}>
                    Click me One!
                </button>
                <button onClick={this.handleClickTwo}>
                    Click me Two!
                </button>
            </Fragment>
        )
    }
}

总结

在内部,React使用了一些聪明的技术来最小化更新UI所需的代价高昂的DOM操作的数量。对于许多应用,使用React都能得到一个快速的用户界面,并且不用做太多的工作来优化性能。尽管如此,如果能遵循前面提到的技术来解决渲染的浪费,对于大型应用,也能在性能方面有非常流畅的体验。

Guess you like

Origin www.cnblogs.com/wydumn/p/12149216.html