-
Notifications
You must be signed in to change notification settings - Fork 667
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
df450e9
commit ef1b523
Showing
15 changed files
with
317 additions
and
95 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Unnecessary Rerenders |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { useState } from 'react' | ||
import * as ReactDOM from 'react-dom/client' | ||
|
||
function CountButton({ | ||
count, | ||
onClick, | ||
}: { | ||
count: number | ||
onClick: () => void | ||
}) { | ||
return <button onClick={onClick}>{count}</button> | ||
} | ||
|
||
function NameInput({ | ||
name, | ||
onNameChange, | ||
}: { | ||
name: string | ||
onNameChange: (name: string) => void | ||
}) { | ||
return ( | ||
<label> | ||
Name: <input value={name} onChange={e => onNameChange(e.target.value)} /> | ||
</label> | ||
) | ||
} | ||
|
||
function App() { | ||
const [name, setName] = useState('') | ||
const [count, setCount] = useState(0) | ||
const increment = () => setCount(c => c + 1) | ||
return ( | ||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}> | ||
<div> | ||
<CountButton count={count} onClick={increment} /> | ||
</div> | ||
<div> | ||
<NameInput name={name} onNameChange={setName} /> | ||
</div> | ||
{name ? <div>{`${name}'s favorite number is ${count}`}</div> : null} | ||
</div> | ||
) | ||
} | ||
|
||
const rootEl = document.createElement('div') | ||
document.body.append(rootEl) | ||
ReactDOM.createRoot(rootEl).render(<App />) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import '@total-typescript/ts-reset' | ||
import '@total-typescript/ts-reset/dom' | ||
|
||
// eslint-disable-next-line react/no-typos | ||
import 'react' | ||
|
||
declare module 'react' { | ||
interface CSSProperties { | ||
[key: `--${string}`]: string | number | ||
} | ||
|
||
function use<T>(context: React.Context<T> | Promise<T>): T | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"include": ["**/*.ts", "**/*.tsx"], | ||
"compilerOptions": { | ||
"lib": ["DOM", "DOM.Iterable", "ES2023"], | ||
"isolatedModules": true, | ||
"esModuleInterop": true, | ||
"jsx": "react-jsx", | ||
"module": "ES2022", | ||
"moduleResolution": "Bundler", | ||
"resolveJsonModule": true, | ||
"target": "ES2022", | ||
"strict": true, | ||
"noImplicitAny": true, | ||
"allowJs": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"skipLibCheck": true, | ||
"allowImportingTsExtensions": true, | ||
"noEmit": true, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,21 @@ | ||
# Component Memoization | ||
|
||
Click "force re-render" | ||
👨💼 We've got a problem. Here's how you reproduce it in our component. | ||
|
||
- In browser devtools search for ListItem function call | ||
- In React devtools search for the ListItem in the Ranked view | ||
In this exercise, pull up the React DevTools Profiler and start a recording. | ||
Observe when you click the "force rerender" button, the `CityChooser` and | ||
`ListItem` components are rerendered even though no DOM updates were needed. | ||
This is an unnecessary rerender and a bottleneck in our application (especially | ||
if we want to start showing all of the results rather than just the first 500... | ||
which we do want to do eventually). If you enable 6x throttle on the CPU (under | ||
the Performance tab in Chrome DevTools) then you'll notice the issue is more | ||
stark. | ||
|
||
Your job is to optimize the `ListItem` component to be memoized via `memo`. Make | ||
note of the before/after render times. | ||
|
||
Make sure to check both the React Profiler and the Chrome DevTools Performance | ||
tab. | ||
|
||
As with most of these exercises, the code changes are minimal, but the impact | ||
is significant! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
# Component Memoization | ||
|
||
Click "force re-render" | ||
|
||
- In browser devtools you can't find the ListItem function call | ||
- In react devtools you don't see it either | ||
👨💼 Great job! You've managed to really improve the performance of our list when | ||
unrelated updates occur. But what if there are updates to the list itself? | ||
Hmm... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,39 @@ | ||
# Custom Comparator | ||
|
||
Hover over one of the list items and notice they all re-render | ||
👨💼 We've improved things so the `ListItem` components don't rerender when there | ||
are unrelated changes, but what if there are changes to the list item state? | ||
|
||
Hover over one of the list items and notice they all rerender. But we really | ||
only need the hovered item to rerender (as well as the one that's no longer | ||
highlighted). | ||
|
||
So let's add a custom comparator to the `memo` call in `ListItem` to only | ||
rerender when the changed props will affect the output. | ||
|
||
Here's an example of the comparator: | ||
|
||
```tsx | ||
const Avatar = memo( | ||
function Avatar({ user }: { user: User }) { | ||
return <img src={user.avatarUrl} alt={user.name} /> | ||
}, | ||
(prevProps, nextProps) => { | ||
const avatarChanged = prevProps.user.avatarUrl !== nextProps.user.avatarUrl | ||
const nameChanged = prevProps.user.name !== nextProps.user.name | ||
return avatarChanged || nameChanged | ||
}, | ||
) | ||
``` | ||
|
||
So even if the user object changes, the `Avatar` component will only rerender if | ||
the `avatarUrl` or `name` properties change. | ||
|
||
By default, React just checks the reference of the props, so by providing a | ||
custom comparator, we override that default behavior to have a more fine-grained | ||
control over when the component should rerender. | ||
|
||
So let's add a custom comparator to the `ListItem` component so it only rerenders | ||
when absolutely necessary. | ||
|
||
Pull up the React Profiler and the DevTools Performance tab to see the impact | ||
of this optimization as you hover over different list items. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
# Custom Comparator | ||
|
||
Hover over one of the list items and notice only one re-renders | ||
👨💼 Great! The performance is better! Unfortunately, that custom comparator is | ||
kinda complex. I wonder if we could simplify it a bit... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,43 @@ | ||
# Primitives | ||
|
||
👨💼 I would love to have the performance improvement without all the complexity | ||
of the custom comparator function. | ||
|
||
The default comparator function is a simple `===` comparison. So if we changed | ||
the props a bit, we could take advantage of this. | ||
|
||
Remember our Avatar example before? | ||
|
||
```tsx | ||
const Avatar = memo( | ||
function Avatar({ user }: { user: User }) { | ||
return <img src={user.avatarUrl} alt={user.name} /> | ||
}, | ||
(prevProps, nextProps) => { | ||
const avatarChanged = prevProps.user.avatarUrl !== nextProps.user.avatarUrl | ||
const nameChanged = prevProps.user.name !== nextProps.user.name | ||
return avatarChanged || nameChanged | ||
}, | ||
) | ||
``` | ||
|
||
We could change the props for this component to be primitives instead of objects. | ||
|
||
```tsx | ||
const Avatar = memo(function Avatar({ | ||
avatarUrl, | ||
name, | ||
}: { | ||
avatarUrl: string | ||
name: string | ||
}) { | ||
return <img src={avatarUrl} alt={name} /> | ||
}) | ||
``` | ||
|
||
And now we can use the default comparator function without specifying our own | ||
because a simple check with `===` will be enough. | ||
|
||
Let's do this for our `ListItem`. | ||
|
||
And make sure to check the before/after of your work! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
# Primitive Values | ||
|
||
👨💼 Great job! That's much better! We still get the perf benefits with `memo` | ||
without all the complexity of a custom comparator function. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
# Optimize Rendering | ||
|
||
👨💼 When you find yourself rendering a ton of the same component, optimizing that | ||
one component can have an outsized impact on performance. Good job! |
Oops, something went wrong.