-
Notifications
You must be signed in to change notification settings - Fork 113
Support memoization of component functions during render phase #33
Comments
Possibly related: facebook/react#1654 |
Just use pure functions and composition: var Hello = React.createClass({
getExpensiveNumber: function() {
return ...; // expensive value computed from props and state
},
computeSomethingBasedOnExpensiveValue: function(expensiveNumber) {
return expensiveNumber + 1;
},
render: function() {
var expensiveNumber = this.getExpensiveNumber();
return <div>Number is {expensiveNumber}, next number is {this.computeSomethingBasedOnExpensiveValue(expensiveNumber)}</div>;
}
}); |
@nfroidure this is exactly the same as what I described here:
As you can see, the code you provide requires more complex method signatures. On real production components this can lead to much more complex code, so I generally avoid these optimizations as they tend to make the code more complex than it needs to compared to the performance gain it involves. |
Imo, that's not a bad thing. Using a context object or ES6 destructuring can avoid complex functions signatures. Also, adding memoization features would make React slower for everyone and encourage bad design patterns while well known and performant ones already resolve the case you pointed out. |
@nfroidure sorry but I can't understand your point about using context object (you mean the "hidden" context feature of React?) nor about using ES6 destructuring. Also, yes it would have a very small overhead for anyone not using this feature, but help people using it to have better performances without introducing more complexity. With the same reasonment you could argue that I would really like to know where you see a bad design pattern. Memoization is not at all a bad design pattern when you evolve in functional purity. React somehow also uses it because it memoizes the value returned by Please provide examples of the well known and performant patterns to solve this problem, because the code you provided introduce boilerplate and does not seem to use any of these patterns. And as far as I know memoization is a performant and well known pattern :) |
I think, it is, until you wrap it into a lib ;). What you call a boilerplate is for me just a realization of a pattern. Here is an example of a context object and destructuring usage: function computeSomethingBasedOnExpensiveValue({expensiveNumber: n}) {
return n + 1;
}
var Hello = React.createClass({
getExpensiveNumber: function() {
return ...; // expensive value computed from props and state
},
render: function() {
var cachingContext = {
expensiveNumber: this.getExpensiveNumber(),
expensiveString: this.getExpensiveString()
};
return <div>Number is {cachingContext.expensiveNumber}, next number is {computeSomethingBasedOnExpensiveValue(cachingContext)}</div>;
}
}); |
Hmmm yes I see your point and how this tricks do the job on this simple example. However I still think on more complex components this pattern will increase complexity and you would have to pass your caching context to many component instances. Also, you have to precompute the caching context at the beginning of the render method. The advantage of memoization is that the expensive computation is done lazily on demand and avoid doing the computation if it is finally not required by the rendering code. Sure you can also create the caching context conditionnally according to props, but this involves another layer of complexity compared to my proposition |
Lazy computing could be achieved with proxys / getters/setters. To avoid extra code you also could isolate all the caching context related code into a separated module in order to reuse it in several components. Since, expensive computing done synchronously is very rare (and should be avoided), i think adding that kind of functionality to React is not a good idea. On the other hand, that's just my opinion, and i'm not using React intensively right now ;). |
@nfroidure I'm not sure using proxys getters setters and a separate module for a little performance gain would keep things simple enough. I'm not trying to run expensive and synchronous machine learning algorithms during the render phase of my React components. I just point out that there are cases where the same component method that computes something can be called multiple times during a single render phase. Sometimes this method returns an object, an array, a helper or anything. This produces extra computation, memory allocation and garbage collection. This is a minor optimization detail at a component level (as we are not performing extremely complex computations) but additionned together these optimizations could make sense. That's why the way to trigger these optimizations should rather be very simple, like @andreypopp deleted his answer but suggested using https://github.com/andreypopp/memoize-decorator var Hello = React.createClass({
@renderMemoized
getExpensiveNumber: function() {
return ...; // expensive value computed from props and state
},
@renderMemoized
computeSomethingBasedOnExpensiveValue: function() {
return this.getExpensiveNumber() + 1;
},
render: function() {
return <div>Number is {this.getExpensiveNumber()}, next number is {this.computeSomethingBasedOnExpensiveValue()}</div>;
}
}); |
What if we use loadash for this? |
I don't think React plans to depend on a third party library at any point. Memoization is not complicated to implement btw, and in this case it should be well-integrated with component lifecycle methods. |
Is there any objection to using a simple instance variable to store the result of the computation, to avoid having to pass it around? |
Where to handle expensive calculations is a really good question and has large implications on the structure of applications. I've been struggling with this for a while and have tried many non-ideal solutions. For instance if we have a Component with an expensive filter function to pass data down to a child component, it is a problem to me that the expensive function gets run every time some other prop change occurs. I would really like to avoid using I would really love to just handle this at the last possible moment => in the I've been searching for an opinion from @gaearon on this (because I believe his comments have very much experience and thought put into them) but couldn't find anything. Hopefully he can find the time to shed some light here. Seeing as this has been left open for years, I imagine there is an answer out there. |
You can implement it in render with a HOC using memoized selectors. Example of usage: import memoHoc from 'hypothetical-package';
const C = (props) => (
<div>
foo: {props.foo((x, y) => x + y)}
bar: {props.bar((x, z) => x + z)}
</div>
);
const memos = {
foo: [props => props.x, props => props.y],
bar: [props => props.x, props => props.z],
};
export default memoHoc(memos)(C); In this example, when This is just shifting around the arguments in the reselect api, but couldn't be implemented with reselect. Also not sure how you'd make it work if you called To extend its versatility, you could instead define the full or partial selectors in render. Here there's another argument computed in render for whatever reason, where it recomputes the selector value when props.foo(valueOfExtra, (x, y, extra) => x + y + extra)
const memos = {
foo: [props => props.x, props => props.y],
}; |
@brigand very cool. I just tried this out and I like it a lot. |
Also, guys, take at this RFC which introduces new lifecycle for your task. Will come in v16.3 |
In the following example, we can see
this.getExpensiveNumber()
is called twice during the render phase, which means the expensive code runs twice.Current solutions for this execution cost problem are:
Since React's render method is supposed to not have any side effect, and during the render phase neither props and state are supposed to change, another possibility is to be able to memoize the expensive render functions. During a single render phase, they are supposed to always return the exact same value (for primitives at least, it could be another object's identity...).
We could probably have a syntax like this one:
And this will memoize the function per render phase, meaning the expensive call will only run once per render instead of twice.
The text was updated successfully, but these errors were encountered: