Skip to content
This repository was archived by the owner on May 4, 2020. It is now read-only.

How is this any different from PureRenderMixin? #19

Closed
kayneb opened this issue Feb 19, 2016 · 17 comments
Closed

How is this any different from PureRenderMixin? #19

kayneb opened this issue Feb 19, 2016 · 17 comments
Labels

Comments

@kayneb
Copy link

kayneb commented Feb 19, 2016

The docs don't really explain the difference between this library and PureRenderMixin. Could you clarify for me please?

@jurassix
Copy link
Owner

jurassix commented Feb 19, 2016

PureRenderMixin checks referential equality of plain js objects. This library uses Immutable.is() to validate Immutable objects underlying value is equal to the previous.

Immutable data structures excel at referential comparison, there is no need to deeply check the diff on the objects; referential equality is guaranteed only if the objects are identical.

@jurassix
Copy link
Owner

This may be better than words...

var user1 = {a: '1'};
var user2 = {a: '1'};
console.log('user1 === user2', user1 === user2); //false
user1.a = '2';
user2.a = '2';
console.log('user1 === user2', user1 === user2); //false

var iuser1 = Immutable.fromJS({a: '1'});
var iuser2 = Immutable.fromJS({a: '1'});
console.log('Immutable.is(iuser1, iuser2)', Immutable.is(iuser1, iuser2)); //true
console.log('iuser1 === iuser2', iuser1 === iuser2); //false

iuser1 = iuser1.set('a', '2');
iuser2 = iuser2.set('a', '2');
console.log('Immutable.is(iuser1, iuser2)', Immutable.is(iuser1, iuser2)); //true
console.log('iuser1 === iuser2', iuser1 === iuser2); //false

@jurassix
Copy link
Owner

Note: You can test the above on the console here: https://facebook.github.io/immutable-js/

@jurassix
Copy link
Owner

closing please comment if this is still unclear

@natorojr
Copy link

Greetings @jurassix,

First off, hope all is well and that Q1 2016 has treated you well.

I understand your reasoning/explanation above regarding the use of Immutable.is, but I'm still struggling to understand the purpose of this library. Isn't the recommended way simply to use https://facebook.github.io/react/docs/shallow-compare.html?

The beauty of using Immutable data structures, as you stated above, is the fact that value equality effectively boils down to referential equality.

Suppose you have a component which takes in an Immutable prop, like so...

<EntityDetails entity={entity} />  // EntityDetails uses shallowCompare to implement shouldComponentUpdate

... and, internally, this component implements shouldComponentUpdate using shallowCompare.

Further suppose, that a change in a parent component's state, say for example via a Redux store state change, causes a rerender down the component hierarchy which changes the the prop entity of EntityDetails. Then, the EntityDetails instance shouldComponentUpdate will properly return true because, internally, this.props.entity !== nextProps.entity

What am I missing?

@jurassix
Copy link
Owner

@natorojr Long time no talk! Hope all is well with you too.

Firstly, I would agree with you that this library should not be needed, given the exact argument you sited about referential equality and shallowEquality. However, ~2 years ago when I was building out benchmarks using React and Immutable Data Structures, using PureRenderMixin didn't increase my performance. So I created this library, which was a direct copy of PureRenderMixin with a check to Immutable.is() added. Then plugged it into my app and saw a ~8x performance increase.

I have spoken to @leebyron in the past and he expressed referential equality should work for Immutable Data Structures within React.

Next Steps
I haven't revisited React + Immutable.js in quite awhile, would love to see any examples you have that can render this library vestigial. If this library is no longer needed, let's use this space to document modern best practices for React + Immutable.js.

Prior Art
Presentation: https://github.com/jurassix/react-examples/tree/master/advanced/presentation
Immutable Benchmark: https://github.com/jurassix/react-examples/tree/master/advanced/table-multiple-immutable-components

@jurassix jurassix reopened this Apr 12, 2016
@natorojr
Copy link

Thanks for the quick response, explanation, and history, @jurassix Makes a lot of sense.

I did some more research and experimentation into this topic (including poking around in the Immutable.js code and playing around with your benchmarks) and, as it turns out, unfortunately, my previous statement is not entirely true...

The beauty of using Immutable data structures, as you stated above, is the fact that value equality effectively boils down to referential equality.

Although I believe that statement should be true in theory, it's not in practice/implementation of Immutable.js.

From your code snippet above:

var iuser1 = Immutable.fromJS({a: '1'});
var iuser2 = Immutable.fromJS({a: '1'});
console.log('Immutable.is(iuser1, iuser2)', Immutable.is(iuser1, iuser2)); //true
console.log('iuser1 === iuser2', iuser1 === iuser2); //false

For some reason, I was under the impression that the last log line would return true. I guess I thought that Immutable.js internally generated a hash code (of some sort) and a reference to Immutable objects previously created such that it could easily return the same exact immutable given two plain old JavaScript objects with the same value equality. But, I think that was wishful thinking and a misunderstanding of how Immutable.js actually works.

As such, I think there is definitely a purpose for this library.

That said, one should be warned that Immutable.is does in fact do a "deep" equality check as far as I can tell from the source.

Refer to:
https://github.com/facebook/immutable-js/blob/master/src/IterableImpl.js#L283
https://github.com/facebook/immutable-js/blob/master/src/utils/deepEqual.js

I guess the question becomes: What's faster? A complete re-render of the component hierarchy or a deep equality check of your props/state.

Apparently, from your benchmarks, the later is definitely faster for a deeply nested HTML table.

Any other thoughts/insights that might help me clarify my confusion?

Best -NT

@jurassix
Copy link
Owner

I think this is illuminating... it looks like fromJS call is really the culprit.

var o = {b: {a: '1'}};
var i = Immutable.fromJS(o);
var prev = i;
var b1 = i.get('b');
var b2 = i.get('b');
console.log('Immutable.is(b1, b2)', Immutable.is(b1, b2)); //true
console.log('b1 === b2', b1 === b2); //true
console.log('b1.get(a) === b2.get(a)', b1.get('a') === b2.get('a')); //true

i = i.setIn(['c'], '2');
console.log('i.getIn([b]) === prev.getIn([b])', i.getIn(['b']) === prev.getIn(['b'])); //true

@dkingman
Copy link

This issue in Immutable's repo may shed some light on this. Essentially, two different immutable objects that were created independently will never have referentially equality. So, it's not just fromJS that is the culprit, but any object creation that happens independently.

From an example in that repo:

var map1 = Immutable.Map({a:1, b:1, c:1});
var map2 = Immutable.Map({a:1, b:1, c:1});

var map1 = Immutable.Map({a:1, b:1, c:1});
var map2 = Immutable.Map({a:1, b:1, c:1});
assert(map1 !== map2); // **The important bit**
assert(Immutable.is(map1, map2) === true);

var list1 = Immutable.List([1]);
var list2 = Immutable.List([1]);
assert(list1 !== list2); // **The important bit**
assert(Immutable.is(list1,list2) === true);

I'm not sure what the performance characteristics are of Immutable.is, but it could still be better than calling render on a React component when nothing has actually changed.

@jurassix
Copy link
Owner

jurassix commented May 25, 2016

@dkingman Thanks for clearing up the root of the issue.

Depending on your data structures and how deeply nested your components are it is probably better to do more processing in JavaScript than trying to re-render. Im my previous benchmarks my data structure was a multi-dimensional array, rendered as a 10,000 cell table. I would make a single cell update and render the entire tree from root. Immutable.is() optimization was way faster.

Another facet of my testing was simulating data sets being pushed realtime to the client. If you are using a PATCH to update your client data, which would translate into set() and setIn() operations, then you should not break referential equality and PureRenderMixin would be fine. However, if you are serializing your application state, or a large part of it, over the wire and rendering from root then it's effectively like creating two equal maps, just not referentially equal and PureRenderMixin will not help.

I think in general the school of thought is don't even use PureRenderMixin until you absolutely need it. React is super fast, and this is just another premature optimization that could expose a UI bug since we are halting rendering a child tree, etc. I think that goes doubly for this library, since their are two abstractions at play, React optimizations and Immutable data equality checks.

So basically, if you need this you know you do. Like I did when I was barely able to update a 10,000 cell table using Vanilla React. I layered on PureRenderMixin, still no perf increases. Finally, I created a custom shouldComponentUpdate and got the perf I was after. Same thinking should help @natorojr decide if he needs to use this lib or not. In general, nope.

@mbylstra
Copy link

Hi @jurassix. You use the phrase "referential integrity", but I don't think that's what you mean. This has a particular meaning in database systems (https://en.wikipedia.org/wiki/Referential_integrity). I haven't read the details of this issue, but perhaps you mean one of "referential equality", "value equality" or possibly "referential transparency". The problem is that I did a google search for the phrase "referential integrity immutable" as I was researching "referential integrity" (the database meaning) for immutable data, and this search result is very misleading. Because of React's popularity, this post shows up as the No.3 result.

Could you please reword so this doesn't show up google results for "referential integrity"? The same goes for #6. Thanks.

@jurassix
Copy link
Owner

@mbylstra typo is now corrected. Sorry for the confusion. You were correct, 'referential equality' was my intent.

@leebyron
Copy link

I think in general the school of thought is don't even use PureRenderMixin until you absolutely need it. React is super fast, and this is just another premature optimization that could expose a UI bug since we are halting rendering a child tree, etc. I think that goes doubly for this library, since their are two abstractions at play, React optimizations and Immutable data equality checks.

Just to chime in here, I wouldn't personally advocate this, however there are two sorts of approaches to take:

  1. You use mutation to manage your models changing over time. In this case, don't use PureRenderMixin unless you need it. You're very likely to introduce bugs without stepping carefully.

  2. You use immutable models (regardless of implementation). In this case, use PureRenderMixin by default, and only don't use it when you have an explicit reason not to. There's even an issue on the react github to see if it can be even easier to use React this way, and let React make even more optimizations. Because your models are immutable, the only bugs you're likely to introduce are bugs where you're mutating your immutable models - and then you have larger problems. React reconciliation is fast, but a referential equality check is way way way faster.

Should you use this library instead of PureRenderMixin? By default no - but yes if it speeds things up for you in specific places. As was already pointed out, this library will use Immutable.is (value equality) over === (referential equality) in determining if a component should update. Why default to not using this over PureRenderMixin? Immutable.is is O(N) of the whole size of the data you're comparing (if you're comparing a deep structure, it will compare deep) while === is O(1). If your input data is large, then it's entirely possible that the shouldComponentUpdate check is far more expensive than the re-rendering itself. So it's important to performance test!

When would using this library be better than PureRenderMixin? If the values provided to your component repeatedly come from a source that's creating new Immutable.js collections. That should be relatively rare, but it definitely happens, and then this lib is exactly what you want. More typically though, you're using the Immutable.js methods like set and setIn, etc, which are what provide good performance for doing updates, and if those updates re-use nested data or are no-ops, then referential transparency (PureRenderMixin) is going to do a great job.

I think this is illuminating... it looks like fromJS call is really the culprit.

Definitely! One of the most common culprits I find when investigating a codebase that uses Immutable.js and has performance issues is pervasive use of fromJS and toJS - these methods are useful at the fringes of your system, like at a network boundary, but if used within your app's regular lifecycle then effectively erase all the value of structural sharing and cause PureRenderMixin to be useless.

@jurassix
Copy link
Owner

jurassix commented May 31, 2016

@leebyron Thanks for your insightful response and analysis of this thread!

I think based on this issue alone, I should add warnings to the README persuading people to only use library as a last recourse. I can follow up that statement with some samples of best practices for Immutable.js and emphasize when this library could help them get the perf they are after, if these best practices haven't helped.

@leebyron
Copy link

Great idea!

@markerikson
Copy link

@leebyron : thanks for the info there. I've recently added several articles regarding use of Immutable.js and performance to my React/Redux links repo, and this fits in well.

Your comment about use of toJS is obviously in line with the other articles and discussions I've seen, and is a good clarification of intended usage. For example, I've seen a number of people trying to call toJS inside of a Redux mapStateToProps function, which presumably defeats the purpose since you're doing an expensive call many times and not getting cheap equality checks in shouldComponentUpdate. I've told a few people they probably shouldn't call that until inside render, and that seems to be where you're pointing.

I'll add your comment to that list shortly: https://github.com/markerikson/react-redux-links/blob/master/react-performance.md#immutable-data .

@jurassix
Copy link
Owner

@leebyron I've discovered my previous experimentations with Immutable and PureRenderMixin that ultimately led me to creating this library were erroneous. I've gone back to my performance tests and proved this library to be both slower and redundant.

All, I've update the README to reflect that this library should not be needed and PureRenderMixin should be sufficient. Performance issues that are helped by this library are highlighting issues with the usage of Immutable.

At this point I've frozen development of this library. One possible future is to convert this repo into a recipe book for usages of Immutable.js with React. Any help is appreciated.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

7 participants