Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Pure -> memo
  • Loading branch information
gaearon authored Oct 19, 2018
commit eb6f7fcaecac24f2be7d7376e47d48fe061dd958
40 changes: 21 additions & 19 deletions text/0000-pure.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@

# Summary

`React.pure` lets you bail out of updates from a function component. It is an optimization, similar to how you'd use `React.PureComponent` if you were writing a class.
`React.memo` lets you bail out of updates from a function component. It is an optimization, similar to how you'd use `React.PureComponent` if you were writing a class.

# Basic example

```js
import { pure } from 'react';
import { memo } from 'react';

function Button(props) {
// Component code
}

export default pure(Button);
export default memo(Button);
```

Wrapping a component into `React.pure` makes it bail out of rendering when the props are shallowly equal.
Wrapping a component into `React.memo` makes it bail out of rendering when the props are shallowly equal.

You can also pass an `arePropsEqual(prevProps, nextProps)` function as a second argument to customize the bailout condition:

Expand All @@ -28,7 +28,7 @@ function arePropsEqual(prevProps, nextProps) {
return prevProps.color.id === nextProps.color.id;
}

export default pure(Button, arePropsEqual);
export default memo(Button, arePropsEqual);
```

# Motivation
Expand All @@ -37,20 +37,20 @@ Today in React, function components and `PureComponent` provide two different ki

Function components let us avoid constructing a class instance. This benefits the initial render. Function components also tend to minify better than classes, which helps reduce the bundle size.

On the other hand, in order to optimize updates we sometimes want to bail out of rendering. To do that today, you have to convert a function component to a `PureComponent` class (or a class with custom `shouldComponentUpdate`). This is true even if you don't use features like state and other lifecycle methods. So it makes the initial render time a bit worse, but updates are potentially faster.
On the other hand, in order to optimize updates we sometimes want to bail out of rendering by **[memoizing](https://en.wikipedia.org/wiki/Memoization)** the rendered result. To do that today, you have to convert a function component to a `PureComponent` class (or a class with custom `shouldComponentUpdate`). This is true even if you don't use features like state and other lifecycle methods. So it makes the initial render time a bit worse, but updates are potentially faster.

By having `pure` as a first-class API in React itself, we can remove the need to make a choice between these optimizations. It makes it easy to add (or remove) update bailouts from function components without introducing other extra costs.
By having `memo` as a first-class API in React itself, we can remove the need to make a choice between these optimizations. It makes it easy to memoize the output of function components without introducing other extra costs.

This also helps address a common argument in teams that can't decide whether to use one or the other optimization, by letting you use them together. And unlike a userland `pure()` higher-order component implementation, the one built into React can be more efficient by avoiding an extra component layer.
This also helps address a common argument in teams that can't decide whether to use one or the other optimization, by letting you use them together. And unlike a userland `memo()` higher-order component implementation, the one built into React can be more efficient by avoiding an extra component layer.


# Detailed design

`React.pure` returns a component type that tells React "render the inner type, but bail out on updates if props are shallowly equal". The prop comparison function can be specified as a second argument to `React.pure`.
`React.memo` returns a component type that tells React "render the inner type, but bail out on updates if props are shallowly equal". In other words, the rendering result is memoized. The prop comparison function can be specified as a second argument to `React.memo`.

`React.pure` only accepts function components as the first argument. It doesn't accept classes since those can extend `PureComponent` directly, and mixing the two approaches is confusing. `React.pure` is intended to be applied at the definition site.
`React.memo` only accepts function components as the first argument. It doesn't accept classes since those can extend `PureComponent` directly, and mixing the two approaches is confusing. `React.memo` is intended to be applied at the definition site.

`React.pure` returns a special component type, similar to `React.forwardRef`. Returning an actual function or a class instead would defeat the optimization as it would create an additional layer and couldn't be faster than just `React.PureComponent`.
`React.memo` returns a special component type, similar to `React.forwardRef`. Returning an actual function or a class instead would defeat the optimization as it would create an additional layer and couldn't be faster than just `React.PureComponent`.

The second argument is `arePropsEqual` (rather than, say, `arePropsDifferent`) so that you can more easily pass a different off-the-shelf implementation (which tend to be written in the positive form).

Expand All @@ -59,24 +59,26 @@ The second argument is `arePropsEqual` (rather than, say, `arePropsDifferent`) s
- It's doable in user space (with worse performance).
- There is some duplication with `PureComponent` API.
- There may be confusion over whether the second argument is `arePropsEqual` or `arePropsDifferent`.
- `pure` is a confusing name as it overlaps with the concept of purity.
- This concept was previously known as "pure component" even though it didn't match the definition of purity.

# Alternatives

- Only allow classes to have bailouts (status quo).
- Function components could be `pure` by default (breaking change).
- Function components could have `memo` by default (breaking change and potentially slow).
- Instead of a wrapper, put a flag on the component instead.
- Give it a long name instead of `pure`.
- Call it something different like `memoized`.
- Give it a long name instead of `memo`.
- Call it something different like `pure`.
- Don't allow to specify the custom equality function.
- `pure` could be an actual higher-order component rather than return a special type.
- `pure` could accept `arePropsDifferent` as a second argument instead.
- `pure` could be made to work with class components too.
- `memo` could be an actual higher-order component rather than return a special type.
- `memo` could accept `arePropsDifferent` as a second argument instead.
- `memo` could be made to work with class components too.

# Adoption strategy

This is not a breaking change. You can start using function components in more places in case you previously felt limited by lack of a `PureComponent` alternative. You don't have to use this.

# How we teach this

We're continuing the existing naming of `PureRenderMixin` and `PureComponent` by calling the wrapper `pure`. This is also consistent with naming of a popular userland solution (`pure` in Recompose). We can now decouple teaching _when to use_ this optimization from _how to apply_ it, and applying it no longer forces you to rewrite a component as a class.
We're intentionally breaking away from the existing naming of `PureRenderMixin` and `PureComponent` by calling the wrapper `memo`. This is because conceptually this is *memoization* and is unrelated to the function purity.

We can now decouple teaching _when to use_ this optimization from _how to apply_ it, and applying it no longer forces you to rewrite a component as a class.