-
Notifications
You must be signed in to change notification settings - Fork 46.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add React.PureComponent, inherit purity for functional components #6914
Conversation
This provides an easy way to indicate that components should only rerender when given new props, like PureRenderMixin. If you rely on mutation in your React components, you can continue to use `React.Component`. Inheriting from `React.PureComponent` indicates to React that your component doesn't need to rerender when the props are unchanged. We'll compare the old and new props before each render and short-circuit if they're unchanged. It's like an automatic shouldComponentUpdate.
67a3976
to
5a893e0
Compare
Two questions:
|
I guess what I mean to say is this: this would definitely be a great idea if the creator of a component was also always in control of the parent of the component he created, since this is not true (eg. |
@nfcampos In react-router's case, given that we are pretty much always at the root of the render tree, having us be Edit: Also, react-redux does some really neat tricks to cache the rendered element and update only when things have actually changed. It's pretty neat and would eliminate some of the need to have a separate version that extends Is there any support here for What would be interesting is the ability to swap out "pureness" at runtime, depending on certain conditions (for example, when an app might be trickling in a lot of data that doesn't get batched up). It looks like this is achievable via |
Intuitively I kind of agree with @nfcampos on this. It seems to me that pureness isn't primarily a trait of the component, but the caller/usage of the component. It seems that you would have to expose one non-pure and one pure of every third-party component and that may even be complicated in more complex cases where there is a larger hierarchy being rendered internally. Also, I'm curious, what's the behavior of |
Agree with @nfcampos. IMO it's better and easier to let the component decide if it's pure or not instead of the parent component doing it. In the long run, it might create confusion and unintended behaviour. |
I think something like |
This is correct.
In some cases, but not necessarily. This is not much different from today: libraries have to take a stance on where they are on the mutability spectrum. For example React Redux already has pure class containers, so it will switch to As for other libraries, it’s no different then the situation today. If you don’t want to force your users to be immutable, you can just export a regular class. If the consumer wants optimizations, they can wrap the children into their pure container: <Router> // not optimized
<MyApp> // my own, optimized!
<Header /> // functional, optimized thanks to MyApp So I think
is unnecessary overkill, and doesn’t need to happen. If you’re not sure your users are immutable, just provide I expect that most libraries will provide just The optimizations kick in if the closest class parent is a In any case, there should be no goal of “making every component pure”. It’s just an optimization that would cover many cases, and generally benefit apps that use immutability. You shouldn’t be chasing that optimization with every component in your app—React reserves the right to not enable it in some cases anyway. |
It's worth noting that in JS having the same arguments isn't enough to ensure purity. function Since(props) {
return <p>{new Date() - props.date}</p>;
} Treating a component like this as pure because of some property of the parent would not work. I think for functional components to be marked as pure it'd have to be opt-in or opt-out per component, and for back-compat reasons that probably means opt-in. |
this._context, | ||
// Element updates are enqueued only at the top level, which we consider | ||
// impure | ||
false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe isParentPure
? I didn’t realize it’s a boolean until this far.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure.
@glenjamin If this worked before, it only worked because some component above it caused it to re-render by |
To clarify again: this change doesn’t make all functional components pure. You still have to opt in by making their closest class parent a |
From: Dan Abramov [email protected] for an app that consists entirely of functional components to opt in to pure functional components the root component needs to be a class component (which extends PureComponent), it can't be a functional component itself, right? This is correct. presumably every library that renders user-provided components — eg. react-router, react-redux, etc. — will need to provide two versions of whatever internal component renders the user provided components, one that extends PureComponent and one which extends Component so that library users can be offered the choice of having their functional components further down the tree be pure or not. I guess this is not really a question, more of a realisation that it looks like this will end up being an option in the api of a majority of libraries once this gets released. In some cases, but not necessarily. This is not much different from today: libraries have to take a stance on where they are on the mutability spectrum. For example React Redux already has pure class containers, so it will switch to PureComponent. (But it already provides pure: false opt-out, so I guess we’ll have to switch there.) As for other libraries, it’s no different then the situation today. If you don’t want to force your users to be immutable, you can just export a regular class. If the consumer wants optimizations, they can wrap the children into their pure container: // not optimized // my own, optimized! // functional, optimized thanks to MyAppSo I think It seems that you would have to expose one non-pure and one pure of every third-party component is unnecessary overkill, and doesn’t need to happen. If you’re not sure your users are immutable, just provide Component. It’s no different from choosing whether to provide shouldComponentUpdate() today—it’s exactly the same decision third party component have been doing for a long time. I expect that most libraries will provide just Components, and if you want to opt into the optimizations, you just wrap your components in a PureComponent. The optimizations kick in if the closest class parent is a PureComponent so, e.g. in case of React Router, you’d only need to make your App top-level handler a PureComponent, for the rest of the app to work. In any case, there should be no goal of “making every component pure”. It’s just an optimization that would cover many cases, and generally benefit apps that use immutability. You shouldn’t be chasing that optimization with every component in your app—React reserves the right to not enable it in some cases anyway. — |
I don't think this is quite the same thing as considering the functional components pure. Adding an automatic These two actions are only equivalent if the component is actually referentially transparent - which is a property that cannot be known from outside in JS. |
Sure, but that wouldn’t work for people already using mutation (e.g. some implementations of Flux, some perf optimizations, making React work inside existing apps with Backbone, etc). So we want to keep that use case.
I’m not sure I follow your point. There is no inlining in this PR (at least, not yet). I thought you were saying that adding this heuristic can potentially break functional components that use something like |
I'm only on my phone at the mo, can do something a bit more concrete (although possibly contrived) later. When a function component renders is entirely controlled by its parent, but what it renders with could be anything. I think that: Here's a slightly contrived example that should demonstrate: function LastRendered() {
return <p>This component last rendered at {new Date().toString()}</p>;
} |
agree with @glenjamin here, having components act differently based on there parent seems a bit crazy, and that not the only thing determining pure essential of the component. I appreciate the attempts to optimize function components however this sort of heuristic feels really leaky. as it is parent components have to sometimes be aware of their children types in order to attach refs, that leanings is less bad since refs are an escape hatch. With this we also have to be aware that changing a parent might cascade pure render checks down. thinking about libraries that wrap children to attach refs or refactoring a parent fn component to be statefull bc it's needed now. |
How would that be noticeable? If you made the parent pure you already effectively short-circuited it. What happens below is just an additional optimization that React can make now; it doesn’t affect your behavior. Can you show an example demonstrating why this could be a problem? |
@gaearon IMO the pureness of component should be up-to the component. A parent can not always know if a child component is pure or not. For example, the following is always a pure component, regardless of its parents purity, const Badge = ({ count }: { count: number }) => <span style={{ color: 'red' }}>{count}</span>; The following is always impure, const LastTime = () => <span>{Date.now()}</span>; These are very bad examples, I know, but this demonstrates the fact that the child can be pure/impure regardless of its parent, and it should be up to the child to define its behaviour. |
I think some confusion comes from us giving another meaning to “pure” when we say “component”. React never allowed
So functions that use something like In the context of this discussion, “pure component” means that not only it is a pure function (which it already must be anyway), but it also doesn’t rely on any deep mutations in the props. In other words, it means that the props are immutable rather than the function is pure. And whether the props are immutable or not, depends on the caller, and not on the component itself. Sorry about the confusion. |
the examples above of non pure components with "pure" props all would/could change behavior depending on if the parent changes its component type. more so tho assuming that functional components that are the children of functional components are also eligible for pure render checks. you can easily create/break chains when refactoring components along the hierarchy no? That all feels like a bit of hard to track down magic that could case subtle bugs. Not to mention the issues around context propagating through pure components |
(Of course React allows impure |
The idea is you shouldn’t have to think about it at all. This heuristic is intended to be an optimization React makes on its own; not something you need to be aware of. Because it doesn’t break the existing scenarios (does it?) it is effectively safe to add, so it’s a nice-to-have feature that you shouldn’t have to think about it.
It’s not any different than components with |
Could you please provide a specific example that demonstrates how it causes a bug? It would need to show that adding Otherwise this discussion goes into a very theoretical tangent 😄 . |
@gaearon Not sure if that's a good argument considering it's an issue right now. :P But yeah, the rest of your argument seems sound. I guess in a sense you mark your "logic containers" as pure (because they deal with the data) and the children (being passed the data also inherits pureness) and it makes sense because they're mostly concerned with presentation only.
Won't this break components that deal with mutable data internally? Even more so considering your example, ReactRouter must not mark itself as pure or my entire app must then be pure? I may be missing something, but it seems like there should be three states; PURE, NON-PURE and INHERIT. So that you can go from being pure to being non-pure for instance, because you may be using some isolated component that may be internally non-pure and it must not inherit pureness. Or? |
I have an answer for this:
😄 We’re getting too abstract and might mean some different things; code will help.
Not entire app, just the descendants between React Router and next impure class. React Router marking itself as pure would have the same effect as React Router implementing a strict |
Doesn't work if there's more than one child. |
We've heard clearly that most React users intend for their functional components to be pure and to produce different output only if the component's props have changed (https://mobile.twitter.com/reactjs/status/736412808372314114). However, a significant fraction of users still rely on mutation in their apps; when used with mutation, comparing props on each render could lead to components not updating even when the data is changed. Therefore, we're changing functional components to behave as pure when they're used inside a React.PureComponent but to rerender unconditionally when contained in a React.Component: ```js class Post extends React.PureComponent { // or React.Component render() { return ( <div className="post"> <PostHeader model={this.props.model} /> <PostBody model={this.props.model} /> </div> ); } } function PostHeader(props) { // ... } function PostBody(props) { // ... } ``` In this example, the functional components PostHeader and PostBody will be treated as pure because they're rendered by a pure parent component (Post). If our app used mutable models instead, Post should extend React.Component, which would cause PostHeader and PostBody to rerender whenever Post does, even if the model object is the same. We anticipate that this behavior will work well in real-world apps: if you use immutable data, your class-based components can extend React.PureComponent and your functional components will be pure too; if you use mutable data, your class-based components will extend React.Component and your functional components will update accordingly. In the future, we might adjust these heuristics to improve performance. For example, we might do runtime detection of components like ```js function FancyButton(props) { return <Button style="fancy" text={props.text} />; } ``` and optimize them to "inline" the child Button component and call it immediately, so that React doesn't need to store the props for Button nor allocate a backing instance for it -- causing less work to be performed and reducing GC pressure.
@spicyj updated the pull request. |
Updated with |
this._debugID, | ||
'shouldComponentUpdate' | ||
); | ||
var pureSelf = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, could call this isSelfPure
for clarity.
FYI Let's say we have WebSocket connection and backend does all checks to be sure it sends only new data to client. Then (using Redux, Flux or any other mutable or immutable approach) you change store (or whatever) or immidiately pass props to component. In that case any new props should trigger rerender immidiately and any |
I was looking forward to using this. |
Yes, this will be merged. |
Are there any updates relating to #6914 (comment) and surrounding conversations? Is the recommendation still that libraries offering container components should in general use |
Yep, that’s the recommendation for now. We may adjust and extend the heuristic later—this shouldn’t stop us from adding |
@gaearon We need to solve this heuristic situation. We should not merge something with a broken heuristic. The bugs that this would introduce are too difficult to debug because the reason for the seemingly random failure is not visible by inspection and difficult to reproduce (unless you already know what you're looking for). We don't want people banging their heads against the wall for hours, trying to understand what went wrong. We can merge PureComponent without the heuristic (figure out a heuristic later if we don't want to block PureComponent), or with a smarter heuristic that passes #6914 (comment) (like owner-based), but not with the current heuristic. |
Do you mean the context test linked there? You basically can't rely on context updates propagating anyway today so I don't think that is a problem. |
@spicyj Err, no, sorry, I meant:
When writing this code, everything looks right. When I write my pure component, it looks pure, so that's good. I write my stateless functional component, it looks stateless and functional, so that seems good. I write my The problem is that a user would never expect that the data being passed to MyFunctionalInputComponent is actually captured as a deeply nested prop of PureComponent, and is thus illegal. It feels like I'm following all the rules, because it feels like I'm only passing pure data to my pure component. My functional input component is just a function, and I expect it to behave like a function. Everything looks very reasonable. For an even moderately complex component, you'd never spot the bug. Even after I explain it to people, most people have trouble realizing what went wrong. The story is absolutely terrible. |
The interesting case with your Would it be reasonable to not apply the heuristic at all here for functional components? If I want to take advantage of the logic here, I'm going to have to change all of my components to extend Alternatively, a really conservative heuristic that just impure-ifies everything any non-pure component seems like it would avoid potential problems. As a library maintainer I'd just cut all of my library components over to |
This one is tricky. Firstly, it seemed like a no big deal improvement. Just a shorter way to implement a SCU method with But the problem is, this PR takes this concept further and tries to apply some additional heuristics which can have unexpected causes. Like #6914 (comment) and #6914 (comment). What I suggest is to just make all the functionall components behave like they had SCU implemented using shallowCompare on props, assuming users use immutable data as props. There will be a clear warning about this in docs. And people which want to do mutation can stick with classic Component's. Additionally if PureComponent only did implement the SCU as I mentioned, I would not be against it. |
Gonna do the first half of this in #7195. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes requested above.
React.PureComponent
This provides an easy way to indicate that components should only rerender when given new props, like PureRenderMixin. If you rely on mutation in your React components, you can continue to use
React.Component
.Inheriting from
React.PureComponent
indicates to React that your component doesn't need to rerender when the props are unchanged. We'll compare the old and new props before each render and short-circuit if they're unchanged. It's like an automatic shouldComponentUpdate, but it also affects the behavior of functional children that are rendered:Functional components
We've heard clearly that most React users intend for their functional components to be pure and to produce different output only if the component's props have changed (https://mobile.twitter.com/reactjs/status/736412808372314114). However, a significant fraction of users still rely on mutation in their apps; when used with mutation, comparing props on each render could lead to components not updating even when the data is changed.
Therefore, we're changing functional components to behave as pure when they're used inside a React.PureComponent but to rerender unconditionally when contained in a React.Component:
In this example, the functional components PostHeader and PostBody will be treated as pure because they're rendered by a pure parent component (Post). If our app used mutable models instead, Post should extend React.Component, which would cause PostHeader and PostBody to rerender whenever Post does, even if the model object is the same.
We anticipate that this behavior will work well in real-world apps: if you use immutable data, your class-based components can extend React.PureComponent and your functional components will be pure too; if you use mutable data, your class-based components will extend React.Component and your functional components will update accordingly.
In the future, we might adjust these heuristics to improve performance. For example, we might do runtime detection of components like
and optimize them to "inline" the child Button component and call it immediately, so that React doesn't need to store the props for Button nor allocate a backing instance for it -- causing less work to be performed and reducing GC pressure.