Performance optimization is a big topic, let’s take a look at performance optimization of the React Function component. Fine rendering refers to making the granularity of each render finer, rendering parts of the render, caching parts of the render that are not necessary,
setState
React goes through these steps from a SetState to a UI update:
Call SetState(update State) => render Function(component render, Function execution) => diff => commit => render Dom(update interface)
Each render does not necessarily result in an update to the page UI, which is diff optimized
We mainly talk about how to reduce the unnecessary render Function, reduce unnecessary component Function hanging.
He that would do well must sharpen his tools
-
Install react DevTools first
-
Turn on Highlight updates when Components Render in component-setting-General.
This way you can see which components are rendered after setState
-
Turn on Record Why each Component rendered while Profiling in component-setting-general Profiling.
So you know what is causing the component to rerender
List Rendering Examples
Let’s take a common list rendering example where we want to update the num of the first item in the list by clicking a button
We might write the following code
const listData = [
{ id: 'id-1'.num: 1 },
{ id: 'id-2'.num: 2}]export const List = () = > {
const [list, setList] = useState(listData)
const handleUpdateFirstItem = () = > {
const newList = [...list]
newList[0] = { ...newList[0].num: Math.random() }
Num = math.random () // newList[0].num = math.random () // newList[0].num = math.random (
setList(newList)
}
return (
<ul>
{list.map((item) => (
<li key={item.id}>Num : {item.num} {console.log(`renderItemId: ${item.id}`)}</li>
))}
<button onClick={handleUpdateFirstItem}>Modify the first item</button>
</ul>)}Copy the code
RenderItemId (id-1, id-2); renderItemId (renderItemId); renderItemId (renderItemId);
Fine list rendering + Memo cache component
PureComponent and memo. The memo works like the React.PureComponent, but in function components, it makes a shallow comparison between props and state. If there is no change, the component is not updated.
export const List = () = > {
const [list, setList] = useState(listData)
const handleUpdateFirstItem = () = > {
const newList = [...list]
newList[0] = { ...newList[0].num: Math.random() }
// newList[0].num = math.random (); // If newList[0].num = math.random (
setList(newList)
}
return (
<ul>
{list.map((item) => (
<Item key={item.id} item={item}/>
))}
<button onClick={handleUpdateFirstItem}>Modify the first item</button>
</ul>)}const Item = React.memo(({ item }) = > {
console.log('renderItemId: ' + item.id)
return (
<li>
{item.num}
</li>)})Copy the code
Click on the button and we can seerenderItemId
Only theid-1
Printed, see here, need to remember: function componentmemo
And the class componentReact.PureComponent
, is a good helper of performance optimization.
We need to make sure that the props passed to each Item component do not change as much as possible. For example, if you want to know if the current Item is selected, check on the List component, not the Item component. Item only has isActive props. Instead of passing the entire activeIdList to each Item to compare its ID, update the activeIdList prop will render each Item. The props only accepts isActive and only render Item if the value actually changes.
How to optimize with Event passing
Again, as a common requirement, we update the num of an item by clicking on it based on the list above
There are several ways we might do this:
Method one: thelist
Introduced to eachItem
(Highly not recommended)
export const List = () = > {
const [list, setList] = useState(listData)
return (
<ul>
{list.map((item) => (
<Item setList={setList} list={list} key={item.id} item={item}/>
))}
</ul>)}const Item = React.memo(({ item, setList, list }) = > {
const handleClick = () = > {
const newList = [...list]
const index = newList.findIndex((s) = >s.id === item.id) newList[index] = { ... newList[index],num: Math.random() }
setList(newList)
}
console.log('renderItemId: ' + item.id)
return (
<li>
{item.num}
<button onClick={handleClick}>Click on the</button>
</li>)})Copy the code
Why is it extremely not recommended? What we found was that we just had to redorender
Current item, but otherItem
It will also be updated.
React DevTools we can see that the list of props for each Item causes a re-render
Method 2: The update function is written in the parent component and useduseCallback
Cache functionUnable to cache components
export const List = () = > {
const [list, setList] = useState(listData)
const handleChange = useCallback((id) = > {
const newList = [...list]
const index = newList.findIndex((item) = >item.id === id) newList[index] = { ... newList[index],num: Math.random() }
setList(newList)
}, [list])
return (
<ul>
{list.map((item) => (
<Item setList={setList} onClick={handleChange} key={item.id} item={item}/>
))}
</ul>)}const Item = React.memo(({ item, onClick }) = > {
const handleClick = useCallback(() = > {
onClick(item.id)
}, [item.id, onClick])
console.log('renderItemId: ' + item.id)
return (
<li>
{item.num}
<button onClick={handleClick}>Click on the</button>
</li>)})Copy the code
These twoItem
Or do they all start overrender
From the analysis toolprops
In theonClick
The function has changed becausehandleChange
Even if it doesuseCallback
Cache, but because must depend onlist
But again every timesetList
Cause each incominghandleChange
It’s new. It’s brokenmeno
The effect.
Method 3: Improved Method 2: Cache list
Method 2 is that because handleChange relies on list, the function is created every time, so we’ll try to cache it with ref.
export const List = () = > {
const [list, setList] = useState(listData)
// Cache the list with ref
const ref = useRef(list)
// listen for list changes stored in ref
useEffect(() = > {
ref.current = list
}, [ref, list])
const handleChange = useCallback((id) = > {
const newList = [...ref.current]
const index = newList.findIndex((item) = >item.id === id) newList[index] = { ... newList[index],num: Math.random() }
setList(newList)
}, [ref]) // deps relies on ref instead of list
return (
<ul>
{list.map((item) => (
<Item setList={setList} onClick={handleChange} key={item.id} item={item}/>
))}
</ul>)}const Item = React.memo(({ item, onClick }) = >{... })Copy the code
So you can just click on whatever you wantrender
Which one. But it’s kind of a hassle to write c every time.
Method 4:useEventCallBack (Recommended)
UseEventCallBack hook (); React (); useEventCallBack hook (); useEventCallBack hook ();
export const List = () = > {
// ...
const handleChange = useEventCallBack((id) = >{... },[list])return (
// ...)}Copy the code
Method 5: UseuseReducer
+ useContext
(Recommended method of multi-layer data transmission)
This method is suitable for multi-tier component structures, but I won’t say more.
conclusion
In a nutshell, re-render components that only need to be re-rendered as much as possible.
Back to the context of this article:
In general: try to make each component more granular, let the memo component cache. Keep the components props as constant as possible. However, some scenarios must cause the component render scenario, repeated memo comparison will also incur overhead, so the specific situation needs to be handled according to the business scenario.
Manual optimization: Generally, manual optimization is based on the specific business scenarios to compare the props. Sometimes, lodash pick,omit and other methods can be used to select the comparison fields, and then isEqual can be used to compare the values. It is important to note that these values and comparison calculations are also expensive, so you need to make trade-offs based on actual business scenarios
Reference documentation
Optimizing Performance React Official document