Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preventing react from rendering unchanged componets into the virtual DOM

Tags:

reactjs

I have written a simple treeview in typescript using react to render the DOM.

Every node has to follow the simple interface.

interface INode {
    label: string;
    children: INode[];
}

and the tree is then described by:

var tree = {
    label: "root",
    children: [
        {
            label: "node 1",
            children: [
                {
                    label: "node 1.0",
                    children: []
                },
                {
                    label: "node 1.1",
                    children: []
                }
            ]
        },
        {
            label: "node 2",
            children: []
        }
    ]
};

Very simple. The components are now also very simple. For the Nodes I have:

class Node extends React.Component<INode, void>{
    render() {
        console.log(`Rendering ${this.props.label}`);
        var list = this.props.children.map((node, i) => <li key={i}><Node {...node}/></li>);
        return (
            <div>
                <span>{this.props.label}</span>
                {this.props.children.length > 0 ? <ul>{list}</ul> : ""}
            </div>
        );
    }
}

and for the root of the tree I use state instead of props. But after 1 second I refresh the state. But the state itself does not change:

class Root extends React.Component<void, INode>{
    constructor() {
        super();
        this.state = tree;
    }

    componentWillMount() {
        setTimeout(() => {
            this.setState((prevState: INode, props: void) => {
                console.log(`Updating state of ${this.state.label}`);
                return prevState;
            });
        }, 1000);
    }

    render() {
        var list = this.state.children.map((node, i) => <li key={i}><Node {...node} /></li>);
        return (
            <div>
                <span>{this.state.label}</span>
                {this.state.children.length > 0 ? <ul>{list}</ul> : ""}
            </div>
        );
    }
}

To run this app there is simply these lines of code:

ReactDOM.render(
    <Root/>,
    document.getElementById("app")
);

The rendered html looks as it should. My problem now is when I look at the browser console I see

Rendering node 1

Rendering node 1.0

Rendering node 1.1

Rendering node 2

Updating state of root

Rendering node 1

Rendering node 1.0

Rendering node 1.1

Rendering node 2

The whole tree was rerended (into the virtual DOM) while the value of the state and the props of the children did not change.

Assume the rendering process would be very complex and time consuming. Is there possibility to stop the rerendering of childrens into the virtual DOM?

like image 477
Matthias Avatar asked Mar 14 '16 14:03

Matthias


1 Answers

Improving ReactJS rendering performance

First off, don't spend time on this unless you see the need for it in your application. Checking against virtual DOM is a pretty cheap operation. If, however, you need to optimize your app, there are a few tactics to achieve this.

Preventing react from rendering unchanged componetns into the virtual DOM (like you asked)

There are two broad approaches to doing this:

  • Not re-rendering a component that will produce the same output
  • Not applying changes to component unless it's changed

Not re-rendering a component that will produce the same output

You can avoid re-rendering using shouldComponentUpdate. To make shouldComponentUpdate more efficient, you can use frameworks to ensure you only have immutable objects in state, or you can simply make a habit of not mutating objects, but rather creating new ones. See React Immutability Helpers. That will allow you to do a shallow check when comparing nextState and nextProps to current state and props (can be done easily with shallowCompare).

Not applying non-changes to the component

This means only updating state or props if the new values are different from the current values. This approach is not brought forth as often, but I feel it has deserves some merit.

State: Only do setState({ value: newValue }) if newValue !== this.state.value. This can be achieved in multiple ways, but one way is to actually make the check before you do setState().

Props: If non-changes come from props, it's typically because the parent component holds a value (in it's state) that is passed to one of it's other children as a prop. The parent component then needs to re-render itself and all of it's children. If you only want the components that depend on the value to change, you can have the components listen a part of a global app state and inject only what is useful for that component. That way, the value will be passed directly to those components who are interested in it, and no other components will be re-rendered.

(This removes the need to store the state in a common parent component, in the cases where multiple components rely on the same data. In addition to improving efficiency, this often makes it easier to create new components that rely on some part of the existing app state.)

You can maintain a global app state using e.g. Flux, Redux or Baobab, to name a few options.

Another advantage of using a global app state, is that you can implement a check (in the app state handler) to ensure you only update the app state with actual changes (i.e. not setting a value if it's the same as it was before).

Smaller components: Reducing amount of DOM elements to re-render per change

Another tactic to reduce re-rendering costs is to make your components smaller. Then, each each data change only applies to a little bit of the DOM, because each component has fewer elements to render.

In my opinion you should always try to do this, as, in addition to the performance aspect, it makes you code more understandable, reusable and maintainable.

Choosing tactics

Each of the tactics can be used alone or in conjunction, but applying both tactics from the first group is typically not going to show notable improvements over applying just one of them.

I'd recommend starting with making your components smaller, because that gives you other advantages as well, as mentioned above.

like image 194
ArneHugo Avatar answered Oct 28 '22 15:10

ArneHugo