Browse Source

[Hooks] Add recommendations about useMemo and lazy init (#1565)

main
Dan Abramov 6 years ago
committed by GitHub
parent
commit
d7d5533278
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 67
      content/docs/hooks-faq.md
  2. 2
      content/docs/hooks-reference.md

67
content/docs/hooks-faq.md

@ -41,6 +41,7 @@ This page answers some of the frequently asked questions about [Hooks](/docs/hoo
* [Can I skip an effect on updates?](#can-i-skip-an-effect-on-updates) * [Can I skip an effect on updates?](#can-i-skip-an-effect-on-updates)
* [How do I implement shouldComponentUpdate?](#how-do-i-implement-shouldcomponentupdate) * [How do I implement shouldComponentUpdate?](#how-do-i-implement-shouldcomponentupdate)
* [How to memoize calculations?](#how-to-memoize-calculations) * [How to memoize calculations?](#how-to-memoize-calculations)
* [How to create expensive objects lazily?](#how-to-create-expensive-objects-lazily)
* [Are Hooks slow because of creating functions in render?](#are-hooks-slow-because-of-creating-functions-in-render) * [Are Hooks slow because of creating functions in render?](#are-hooks-slow-because-of-creating-functions-in-render)
* [How to avoid passing callbacks down?](#how-to-avoid-passing-callbacks-down) * [How to avoid passing callbacks down?](#how-to-avoid-passing-callbacks-down)
* [How to read an often-changing value from useCallback?](#how-to-read-an-often-changing-value-from-usecallback) * [How to read an often-changing value from useCallback?](#how-to-read-an-often-changing-value-from-usecallback)
@ -342,7 +343,9 @@ const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
This code calls `computeExpensiveValue(a, b)`. But if the inputs `[a, b]` haven't changed since the last value, `useMemo` skips calling it a second time and simply reuses the last value it returned. This code calls `computeExpensiveValue(a, b)`. But if the inputs `[a, b]` haven't changed since the last value, `useMemo` skips calling it a second time and simply reuses the last value it returned.
Conveniently, this also lets you skip an expensive re-render of a child: `useMemo` is treated as a hint rather than guarantee. React may still choose to "forget" some previously memoized values to free memory, and recalculate them on next render.
Conveniently, `useMemo` also lets you skip an expensive re-render of a child:
```js ```js
function Parent({ a, b }) { function Parent({ a, b }) {
@ -361,6 +364,67 @@ function Parent({ a, b }) {
Note that this approach won't work in a loop because Hook calls [can't](/docs/hooks-rules.html) be placed inside loops. But you can extract a separate component for the list item, and call `useMemo` there. Note that this approach won't work in a loop because Hook calls [can't](/docs/hooks-rules.html) be placed inside loops. But you can extract a separate component for the list item, and call `useMemo` there.
### How to create expensive objects lazily?
`useMemo` lets you [memoize an expensive calculation](#how-to-memoize-calculations) if the inputs are the same. However, it only serves as a hint, and doesn't *guarantee* the computation won't re-run. But sometimes need to be sure an object is only created once.
**The first common use case is when creating the initial state is expensive:**
```js
function Table(props) {
// ⚠️ createRows() is called on every render
const [rows, setRows] = useState(createRows(props.count));
// ...
}
```
To avoid re-creating the ignored initial state, we can pass a **function** to `useState`:
```js
function Table(props) {
// ✅ createRows() is only called once
const [rows, setRows] = useState(() => createRows(props.count));
// ...
}
```
React will only call this function during the first render. See the [`useState` API reference](/docs/hooks-reference.html#usestate).
**You might also occasionally want to avoid re-creating the `useRef()` initial value.** For example, maybe you want to ensure some imperative class instance only gets created once:
```js
function Image(props) {
// ⚠️ IntersectionObserver is created on every render
const ref = useRef(new IntersectionObserver(onIntersect));
// ...
}
```
`useRef` **does not** accept a special function overload like `useState`. Instead, you can write your own function that creates and sets it lazily:
```js
function Image(props) {
const ref = useRef(null);
// ✅ IntersectionObserver is created lazily once
function getObserver() {
let observer = ref.current;
if (observer !== null) {
return observer;
}
let newObserver = new IntersectionObserver(onIntersect);
ref.current = newObserver;
return newObserver;
}
// When you need it, call getObserver()
// ...
}
```
This avoids creating an expensive object until it's truly needed for the first time. If you use Flow or TypeScript, you can also give `getObserver()` a non-nullable type for convenience.
### Are Hooks slow because of creating functions in render? ### Are Hooks slow because of creating functions in render?
No. In modern browsers, the raw performance of closures compared to classes doesn't differ significantly except in extreme scenarios. No. In modern browsers, the raw performance of closures compared to classes doesn't differ significantly except in extreme scenarios.
@ -497,6 +561,7 @@ function useEventCallback(fn, dependencies) {
In either case, we **don't recommend this pattern** and only show it here for completeness. Instead, it is preferable to [avoid passing callbacks deep down](#how-to-avoid-passing-callbacks-down). In either case, we **don't recommend this pattern** and only show it here for completeness. Instead, it is preferable to [avoid passing callbacks deep down](#how-to-avoid-passing-callbacks-down).
## Under the Hood ## Under the Hood
### How does React associate Hook calls with components? ### How does React associate Hook calls with components?

2
content/docs/hooks-reference.md

@ -290,6 +290,8 @@ Pass a "create" function and an array of inputs. `useMemo` will only recompute t
If no array is provided, a new value will be computed whenever a new function instance is passed as the first argument. (With an inline function, on every render.) If no array is provided, a new value will be computed whenever a new function instance is passed as the first argument. (With an inline function, on every render.)
**Don't rely on `useMemo` for correctness.** React treats it as an optimization hint and does not *guarantee* to retain the memoized value. For example, React may choose to "forget" some previously memoized values to free memory, and recalculate them on next render.
> Note > Note
> >
> The array of inputs is not passed as arguments to the function. Conceptually, though, that's what they represent: every value referenced inside the function should also appear in the inputs array. In the future, a sufficiently advanced compiler could create this array automatically. > The array of inputs is not passed as arguments to the function. Conceptually, though, that's what they represent: every value referenced inside the function should also appear in the inputs array. In the future, a sufficiently advanced compiler could create this array automatically.

Loading…
Cancel
Save