There are many articles about React performance tuning
In general, if some state updates are slow, you need to:
- Verify that one is running
The production environment
Build. (The development environment
The build will be deliberately slow,extreme
May be an order of magnitude slower. - Verify whether the treeStates are placed in a higher position than they really need to be. (For example, the input box
state
I put it in the centerstore
Is probably not a good idea) - run
React Developer Tools
To determine what caused itThe second renderAnd, inWrap Memo () on expensive subtrees. (and use it where neededuseMemo()
)
The last step is annoying, especially for components in between
Ideally, the compiler can do this for you. Maybe in the future.
In this article, I want to share two different techniques. They are very basic, which is why people rarely realize that they can improve rendering performance.
These techniques complement what you already know. They won’t replace memo or useMemo, but it’s good to try them out first
A (manual) deceleration component
Here is a component with serious rendering performance issues
import { useState } from 'react';
export default function App() {
let [color, setColor] = useState('red');
return (
<div>
<input value={color} onChange={(e)= > setColor(e.target.value)} />
<p style={{ color}} >Hello, world!</p>
<ExpensiveTree />
</div>
);
}
function ExpensiveTree() {
let now = performance.now();
while (performance.now() - now < 100) {
// Manual delay -- 100 milliseconds to do nothing
}
return <p>I am a very slow component tree.</p>;
}
Copy the code
The problem is that when the color in the App changes, we will re-render the
I could just write a memo() on it and call it a day, but there are plenty of articles out there, so I won’t spend time on how to optimize using memo().
I just want to show you two different solutions.
Solution 1: Move State down
If you take a closer look at the rendering code, you’ll notice that only part of the returned tree really cares about the current color:
export default function App() {
let [color, setColor] = useState('red');
return (
<div>
<input value={color} onChange={(e)= > setColor(e.target.value)} />
<p style={{ color}} >Hello, world!</p>
<ExpensiveTree />
</div>
);
}
Copy the code
So let’s extract this into the Form component and move the state into it:
export default function App() {
return (
<>
<Form />
<ExpensiveTree />
</>
);
}
function Form() {
let [color, setColor] = useState('red');
return (
<>
<input value={color} onChange={(e)= > setColor(e.target.value)} />
<p style={{ color}} >Hello, world!</p>
</>
);
}
Copy the code
Now if the color changes, only the Form will be rerendered. Problem solved.
Solution 2: Content enhancement
This solution doesn’t work when a portion of state is used in code above a high-overhead tree.
For example, if we put color inside the parent div element.
export default function App() {
let [color, setColor] = useState('red');
return (
<div style={{ color}} >
<input value={color} onChange={(e)= > setColor(e.target.value)} />
<p>Hello, world!</p>
<ExpensiveTree />
</div>
);
}
Copy the code
Now it looks like we can’t extract the part that doesn’t use color into another component, because this part of the code will first include the parent component’s div, then include
You can’t avoid using memo at this point, can you? Or is there a way to avoid it?
export default function App() {
return (
<ColorPicker>
<p>Hello, world!</p>
<ExpensiveTree />
</ColorPicker>
);
}
function ColorPicker({ children }) {
let [color, setColor] = useState("red");
return (
<div style={{ color}} >
<input value={color} onChange={(e)= > setColor(e.target.value)} />
{children}
</div>
);
}
Copy the code
We split the App component into two child components.
The code that depends on color goes into the ColorPicker component along with the color State variable.
The parts that don’t care about color remain in the App component and are passed to the ColorPicker as JSX content, also known as the children property.
When the color changes, the ColorPicker rerenders.
But it still holds the same children property it got from the App last time, so React doesn’t access those subtrees. Therefore ExpensiveTree does not re-render.
What’s the moral?
When you’re optimizing with Memo or useMemo, it might seem like it makes sense if you can separate the changing parts from the unchanged ones.
The interesting part about these approaches is that they are not really about performance per se
Using the children attribute to split components generally makes the application’s data flow easier to track and reduces the number of props across the tree
Improving performance in this case is the icing on the cake, not the end goal.
Oddly enough, this pattern will provide even more performance benefits in the future
For example, when the server component is stable and adoptable, our ColorPicker component can get its children from the server.
The entire
That’s something Memo can’t do! However, the two approaches are complementary. Don’t ignore the state drop (and content boost!)
Haven’t I read this before?
Probably is
This is not a new idea. This is just a natural consequence of a React combination model. It is too simple to be appreciated, yet it deserves more love.