How will React 0.14's Stateless Components offer performance improvements without shouldComponentUpdate?
Asked Answered
E

4

14

This question has been going round and round in my head since I read the release notes (and other related hype) around React 0.14 - I'm a big fan of React and I think that stateless components (https://facebook.github.io/react/blog/2015/09/10/react-v0.14-rc1.html#stateless-function-components) are an excellent idea, both for the ease of writing such components and for expressing in code the intention that these components should be "pure" in terms of rendering consistently for the same props data.

The question is: how will it be possible for React to optimise these stateless component functions without going whole-hog and assuming that props references are not only immutable in that they shouldn't be manipulated within the component, but also that they can never change outside of the component lifecycle? The reason that "regular" components (aka stateful components - in other words, the components that go through the whole lifecycle; componentWillMount, getInitialState, etc..) have an optional "shouldComponentUpdate" function is that React does not assume that all props and state references are completely immutable. After components have been rendered, certain properties of the props references may change and so the same "props" instance may have different contents later on. This is partially why there was a lot of excitement over the use of fully-immutable structures and why it was said that using Om with React could offer great performance gains; because the immutable structures used there guaranteed that any given instance of any object could never be mutated, so shouldComponentUpdate could perform really cheap reference equality checks on props and state (http://swannodette.github.io/2013/12/17/the-future-of-javascript-mvcs/).

I've been trying to find out more information about this but haven't got anywhere. I can't envisage what performance improvements could be made around stateless components without presuming that props data will consist of immutable types.. maybe some preliminary analysis of non-immutable props types to try to guess whether "props" and "nextProps" represent the same data?

I just wondered if anyone had any inside information or some otherwise enlightening insights onto this. If React did start demanding that props types be "fully immutable" (allow reference equality comparisons to confirm that data has not changed) then I think that would be a great step forward, but it could also be a big change.

Economizer answered 14/11, 2015 at 0:30 Comment(0)
E
1

Avoiding Unnecessary Allocations

If I understand correctly, stateless functional components are converted into regular components. From the source:

function StatelessComponent(Component) {
}
StatelessComponent.prototype.render = function() {
  var Component = ReactInstanceMap.get(this)._currentElement.type;
  return Component(this.props, this.context, this.updater);
};

When an instance of a stateless component is created, a new object is allocated. This new object has lifecycle methods such as componentWillMount and componentWillReceiveProps. I'm guessing that the plan is to not create these objects at all. Not creating the objects will avoid unnecessary allocations.

Avoiding Unnecessary Checks

Implementing the lifecycle methods requires a number of checks like this:

if (inst.componentWillUpdate) {
  inst.componentWillUpdate(nextProps, nextState, nextContext);
}

Stateless functional components can be assumed to not have these lifecycle methods. That could be what the docs are referring to, but I'm not sure.

EDIT

Removed stuff on memoization, which didn't answer the question or explain stuff well.

Ehrlich answered 7/1, 2016 at 3:20 Comment(3)
That's interesting, thanks! Your first two points definitely make sense, but the third is one that could only easily be implemented in user code (rather than the React library) since it requires some knowledge of the props data type, presumably - such as whether referential equality checks are sufficient to separate props (which would work for immutable types) or whether deeper analysis of props instances is required? (I've only glanced briefly at the memoize implementation but it appears that that presumes that any given instance's data will never change, does it?).Economizer
@DanDanDan, the stuff I had on memoization didn't answer your question, so I removed it. I was experimenting with a user-land performance tweak. The problem is, the characteristics of memoize are really implementation-dependent. It should be possible to make a version that depends on referential equality AND to make a version that does a deep equality check.Ehrlich
I'm not quite sure that I've got an answer that 100% answers my original question - but, then, the question might not have an answer yet.. it's possible that the idea is for stateless components to allow for more optimisations in the future! So I think that your answer comes closest and so deserves to be accepted - thanks!Economizer
A
3

Since your component is just a pure function of its parameters, it would be straightforward to cache it. This is because of the well known property of pure functions, for same input they will always return same output. Since they only depend on their parameters alone, not some internal or external state. Unless you explicitly referred some external variables within that function that might be interpreted as a state change.

However, caching would not be possible if your function component reads some external variables to compose the return value, so that, those external variables might change over time, making cached value obsolete. This would be a violation of being a pure function anyways and they wont be pure anymore.

On React v0.14 Release Candidate page, Ben Alpert states:

This pattern is designed to encourage the creation of these simple components that should comprise large portions of your apps. In the future, we’ll also be able to make performance optimizations specific to these components by avoiding unnecessary checks and memory allocations.

I am pretty sure that he meant cacheability of pure functional components.

Here is a very straight forward cache implementation for demonstration purposes:

let componentA = (props) => {
  return <p>{ props.text }</p>;
}

let cache = {};
let cachedA = (props) => {
  let key = JSON.stringify(props); // a fast hash function can be used as well
  if( key in cache ) {
    return cache[key];
  }else {
    cache[key] = componentA(props);
    return cache[key];
  }
}

And there are other good properties of pure functional components that I can think of at the moment:

  • unit test friendly
  • more lightweight than class based components
  • highly reusable since they are just functions
Artificer answered 24/12, 2015 at 15:48 Comment(4)
Your example cache uses referential equality checks to compare one props instance to another, which would only really work if the same instance of a props object always had the same data - if it was immutable, basically. Which is part of my original question; do you think that React is making the leap to more strictly demand that props types are reliably immutable then?Economizer
You have mistaken, my above code checks for strict data equality by serializing data with JSON.stringify. And you are right, in order to use referential equality check, which is very more efficient than deep data equality check, one must be sure that underlying data is immutable, ie, if some small part of data has to be changed, then a new copy of data should be created. But you can easily implement this on your app, by using immutable data structures, no need to wait React team to implement an official solution. Thanks.Cyperaceous
Sorry, you're quite right about the cache key serialisation! And that I can take advantage of the benefits of using immutable structures myself, without requiring any React framework changes. My question still stands, though, how React could optimise these stateless components without assuming that immutable types are being used - with stateful components I can use ShouldComponentUpdate to give React more information but I can't do this with stateless ones. If the framework attempted props serialisation or deep equality checks, it couldn't know if those processes would be expensive or not.Economizer
You are right, React could not use referential equality since it seems not probable for them to force people to use immutable data structures, or make them promise so that they do not mutate data. So if they have to do some optimization, involving caching, it should use deep data equality. And I believe if nested hierarchy of components are deep enough, it will be a useful optimization.Cyperaceous
E
1

Avoiding Unnecessary Allocations

If I understand correctly, stateless functional components are converted into regular components. From the source:

function StatelessComponent(Component) {
}
StatelessComponent.prototype.render = function() {
  var Component = ReactInstanceMap.get(this)._currentElement.type;
  return Component(this.props, this.context, this.updater);
};

When an instance of a stateless component is created, a new object is allocated. This new object has lifecycle methods such as componentWillMount and componentWillReceiveProps. I'm guessing that the plan is to not create these objects at all. Not creating the objects will avoid unnecessary allocations.

Avoiding Unnecessary Checks

Implementing the lifecycle methods requires a number of checks like this:

if (inst.componentWillUpdate) {
  inst.componentWillUpdate(nextProps, nextState, nextContext);
}

Stateless functional components can be assumed to not have these lifecycle methods. That could be what the docs are referring to, but I'm not sure.

EDIT

Removed stuff on memoization, which didn't answer the question or explain stuff well.

Ehrlich answered 7/1, 2016 at 3:20 Comment(3)
That's interesting, thanks! Your first two points definitely make sense, but the third is one that could only easily be implemented in user code (rather than the React library) since it requires some knowledge of the props data type, presumably - such as whether referential equality checks are sufficient to separate props (which would work for immutable types) or whether deeper analysis of props instances is required? (I've only glanced briefly at the memoize implementation but it appears that that presumes that any given instance's data will never change, does it?).Economizer
@DanDanDan, the stuff I had on memoization didn't answer your question, so I removed it. I was experimenting with a user-land performance tweak. The problem is, the characteristics of memoize are really implementation-dependent. It should be possible to make a version that depends on referential equality AND to make a version that does a deep equality check.Ehrlich
I'm not quite sure that I've got an answer that 100% answers my original question - but, then, the question might not have an answer yet.. it's possible that the idea is for stateless components to allow for more optimisations in the future! So I think that your answer comes closest and so deserves to be accepted - thanks!Economizer
K
0

You can use a decorator to compose your stateless function components to perform high order optimisation to determine if React should renders this component or not. I'm using immutable to perform strict equality checks between props.

Let's say we have this kind of component :

ClickableGreeter.js

const ClickableGreeter = (props) => (
    <div onClick={(e) => props.onClick(e)}>
        {"Hello " + props.name}
    </div>
)

ClickableGreeter.propTypes = {
    onClick: React.PropTypes.func.isRequired,
    name: React.PropTypes.text.isRequired
}

export default ClickableGreeter;

We want React to not rendering it if the name does not change. I'm using a simple decorator that use immutable library to create immutable representation of props and nextProps and perform a simple equality check :

pureImmutableRenderDecorator.js:

import React from 'react'
import Immutable from 'immutable';

const pureComponent = (Component, propsToRemove = []) => {

    class PureComponent extends React.Component {
        constructor(props) {
            super(props);
            this.displayName = 'PureComponent';
        }
        comparator(props, nextProps, state, nextState) {
            return (
                !Immutable.is(Immutable.fromJS(props), Immutable.fromJS(nextProps)) ||
                !Immutable.is(Immutable.fromJS(state), Immutable.fromJS(nextState))
            )
        }
        removeKeysFromObject(obj, keys) {
            var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target;
        }
        shouldComponentUpdate(nextProps, nextState) {
            let propsToCompare = this.removeKeysFromObject(this.props, propsToRemove),
                nextPropsToCompare = this.removeKeysFromObject(nextProps, propsToRemove);

            return this.comparator(propsToCompare, nextPropsToCompare, this.state, nextState)
        }
        render() {
            return <Component {...this.props} {...this.state} />
        }
    }

    return PureComponent;

}

export default pureComponent;

Then, you can create a PureClickableGreeter component by doing :

const PureClickableGreeter = pureComponent(ClickableGreeter, ['onClick']) 
//we do not want the 'onClick' props to be compared since it's a callback

Of course, using immutable here is overkill because it's a simple string comparison but as soon as you need some nested props, immutable is the way to go. You should also keep in mind that Immutable.fromJS() is a heavy operation, but it's fine if you do not have to many props (and that's normally the whole point of stateless functional components : to keep as many props as possible for better code splitting and reusability).

Kumasi answered 22/4, 2016 at 11:1 Comment(0)
E
0

There's finally an answer! I'm not sure what version of React that this arrived in (I suspect that 0.14 did not include this but merely laid the groundwork) but now a PureComponent does not implement "shouldComponentUpdate" because it is handled automatically by React, which:

only shallowly compares the objects

This does mean that you have to be careful about using this type of component if you can't reliably detect changes with a shallow comparison but, if you can, it makes them very performant and can potentially avoid a lot of changes to the virtual DOM!

See here for more info ReactJs.org's 'React.PureComponent' section.

Economizer answered 25/8, 2021 at 18:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.