Preface
If you want to write high-quality code, it is far from enough to rely solely on the framework to help us optimize. During the writing process, we need to use the improved API ourselves, or do some optimizations and specifications based on the underlying principles.
Compared with Vue, React will no longer help us directly solve basic performance optimization related problems at the source code level of the framework, but will provide APIs (Hooks) to allow us to optimize our applications by ourselves, which is one of the reasons why it is more flexible.
The following summarizes some optimization points that can be done from the level of writing React code.
Use the key to traverse the view
The role of key can help us identify which elements have changed, such as adding and deleting. When React is updated, the React Diff algorithm will be triggered. During the diff process, the key value is used to determine whether the element is newly created or an element that needs to be moved. React will save this auxiliary state. This reduces unnecessary element rendering.
The value of key is preferably a unique string in the current list. In development, id and other elements are usually used as key values.
There will be no operation in the current list, and you can use index as the key value as the last resort.
The key should have stable, predictable, and unique traits on the list. Unstable keys such as () generated will cause many component instances and DOM nodes to be recreated unnecessary, which may lead to performance degradation and child component state loss, etc.
Cache Components
react is a one-way data stream. The update of the parent component's state will also allow the child component to re-render and update together. Even if the state of the child component has not changed, it will not be able to specifically monitor the change in the state of a certain component and then update the current component like Vue.
Therefore it can be usedCache components, so that they will only be re-rendered when the current component state value changes. If the value is the same, the component will be cached.
// Subcomponentsconst Child = (() => { ("child"); return ( <div> Child </div> ); }); // Parent componentfunction App() { const [count, setCount] = useState(0); return ( <div className="App"> <h3>{count}</h3> <button onClick={() => setCount(count + 1)}>Count++ </button> <Child /> </div> ); }
The above code<Child />
Component addedmemo
Every time you click count ++, it will not be re-rendered.
Keep the function the same reference
As in the example above, if the parent component wants to get the status value of the child component, it will usually pass it out to the parent component in the form of callback.
interface ChildProps { onChangeNum: (value: number) => void; } const Child: <ChildProps> = (({ onChangeNum }) => { ("child"); const [num, setNum] = useState(0); useEffect(() => { onChangeNum(num); }, [num]); return ( <div> <button onClick={() => { setNum((prevState) => { return prevState + 1; }); }} > Child </button> </div> ); }); function App() { const [count, setCount] = useState(0); return ( <div className="App"> <h3>{count}</h3> <button onClick={() => setCount(count + 1)}>Count++ </button> <Child onChangeNum={(num) => { (num, "childNum"); }} /> </div> ); }
Every time a component updates a num value, the parent component accepts it through onChangeNum function.
Pay attention to what I just saidmemo
It is possible to cache components to avoid re-rendering when the component's incoming value remains unchanged, but this is invalid again. Why is this?
The reason is that the parent component has been updated. A new onChangeNum will be created every time, which is equivalent to a different reference. The re-drop function passed by props is different every time, so memo loses its effect.
So how to solve it? That's useuseCallback
hooks help us keep the same references.
<Child onChangeNum={useCallback((num) => { (num, "childNum"); }, [])} />
Used in developmentmemo
After cached components, you also need to pay attention to whether anonymous functions are passed to subcomponents.
UseCallback is not necessarily only used in this case. For example, a request function or logical processing function can also be wrapped with useCallback. However, it should be noted that the internal references external state or value associations, so some values used in the second parameter, that is, the dependency array need to be added.
Avoid using inline objects
When using inline objects, react will recreate this object every time it is rerendered, and when updating component comparison props,oldProps === newProps
Just forfalse
Then it willre-render
。
ifTestComponent
The component is re-rendered, and someProps reference will be created. Pass toRootComponent
The result of the component's new and old props is different every time, resulting in re-rendering.
const TestComponent = () => { const someProps = { value: '1' } return <RootComponent someProps={someProps} />; };
A better way is to use the ES6 extension operator to expand the object, change the reference type to the value type to pass, so that the comparison of props will be equal.
const TestComponent = () => { const someProps = { value: '1' } return <RootComponent {...someProps} />; };
Use cached calculation results or components
As the React documentation says,useMemo
The basic function is to avoid high overhead calculations for each rendering.
If it is a functional component, large computing is involved, and each time the component is re-rendered, a large computing function is called again, which is very performance-consuming and we can useuseMemo
Caches the calculation results of this function to reduce the amount of work that JavaScript must perform during rendering components, and to shorten the time to block the main thread.
// Recalculate it only when the id changesconst TestComponent = () => { const value = useMemo(() => { return expensiveCalculation() }, [id]) return <Component countValue={value} /> }
Before using useMemo to cache the calculation results, you need to apply them in appropriate places. UseMemo is also costly, and it will also increase the time-consuming process of initializing the overall program, unless this calculation is really expensive, such as factorial calculation.
Therefore, it is not suitable for global use, it is more suitable for local optimization. You should not overuseMemo.
In addition, while caching the result value, it can also be used to cache components.
For example, there is a globalcontext
, As the context is filled with a lot of states in the long-term project iteration, we know that the value of the context changes, which will cause the component to be re-rendered. This component is a large component that consumes performance and will only be re-rendered by one of the variables. At this time, you can consider using useMemo for caching.
const TestComponent = () => { const appContextValue = useContext(AppContext); const theme = ; return useMemo(() => { return <RootComponent className={theme} />; }, [theme]); };
<RootComponent />
Only intheme
Re-render when the variable changes.
Use fragments
react specifies that there must be a parent element in the component, but in some cases, the root tag does not require any attributes, which will lead to the creation of many useless elements in the entire application, so the function of this tag is not very meaningful.
const TestComponent = () => { return ( <div> <ChildA /> <ChildB /> <ChildC /> </div> ); }
In fact, the more elements there are on the page, the deeper the DOM structure is nested, the more time it takes to load, which will also increase the rendering pressure of the browser.
Therefore React providesFragment
Components instead of wrapping the outer layer, it will not help us create the outer layer additionallydiv
Label.
const TestComponent = () => { return ( <> <ChildA /> <ChildB /> <ChildC /> </> ); }
Or another simple way to use empty tags<></>
The same effect is used instead:
const TestComponent = () => { return ( <> <ChildA /> <ChildB /> <ChildC /> </> ); }
There are also some practical scenarios to render elements according to conditions
const TestComponent = () => { const { isLogin, name } = useApp(); return ( <> {isLogin ? ( <> <h3>Welcome {name}</h3> <p>You are logged in!</p> </> ) : ( <h3>go login...</h3> )} </> ); };
Lazy component loading
The speed of application initialization loading is also related to the number of components. Therefore, during initialization, some pages that we cannot see, that is, components that cannot be used at the beginning, can choose to delay loading components. What we can think of is lazy loading of routes, so as to improve the loading speed and response time of the page.
react providesand
Let's help us implement lazy loading of components.
import React, { lazy, Suspense } from 'react'; const AvatarComponent = lazy(() => import('./AvatarComponent')); const renderLoader = () => <p>Loading</p>; const DetailsComponent = () => ( <Suspense fallback={renderLoader()}> <AvatarComponent /> </Suspense> )
Suspense
The function is to make up for itLazy
What you can do during this blank time before the component is loaded, especially when the component is larger, or in weaker devices and networks, it can be done throughfallback
The property adds a loading to prompt the user to load the status. The asynchronous component will be displayed after it is loaded.
If used alonelazy
React will issue an error message on the console!
Loading and uninstalling components via CSS
Rendering is expensive, and if the ‘heavy’ components are frequently loaded/unloaded, this operation can be very performance-consuming or cause delays. Under normal circumstances, we will use the ternary operator to judge the loading display, which also leads to a problem. Every time we update frequently and trigger the loading of different components, there will be certain performance losses. At this time, we can use the CSS attribute to hide it so that the DOM can be kept on the page as a heavyweight.
**However, this method is not omnipotent, and may cause some layout or window misalignment. ** But we should choose to use the method of tuning CSS when this is not the case. Another point, adjusting opacity to 0 costs the browser to almost 0 (as it won't cause reordering) and should take precedence over the more visibility and display as much as possible.
// Avoid frequent loading and unloading of large componentsconst ViewExample = () => { const [isTest, setIsTest] = useState(true) return ( <> { isTest ? <ViewComponent /> : <TestComponent />} </> ); }; // Use this method to improve performance and speedconst visibleStyles = { opacity: 1 }; const hiddenStyles = { opacity: 0 }; const ViewExample = () => { const [isTest, setIsTest] = useState(true) return ( <> <ViewComponent style={!isTest ? visibleStyles : hiddenStyles} /> <TestComponent style={{ isTest ? visibleStyles : hiddenStyles }} /> </> ); };
Separate from the unchanging place
Usually, useMemo and useCallback are used for optimization. Here we say that we do not use these Hooks for optimization.
The concept of separation between change and invariance is actually because of its own react mechanism. The state of the parent component is updated, and all child components must be rendered together, which means to separate stateful components and stateless components.
function ExpensiveCpn() { ("ExpensiveCpn"); let now = (); while (() - now < 100) {} return <p>Time-consuming components</p>; } export default function App() { const [num, updateNum] = useState(""); return ( <> <input type="text" onChange={(e) => updateNum()} value={num} /> <ExpensiveCpn /> </> ); }
The input box above will refresh the component<ExpensiveCpn
/>, we can control rendering without using useMemo and other APIs, which is actually to separate the change from the unchanging 👇🏻:
function ExpensiveCpn() { ("ExpensiveCpn"); let now = (); while (() - now < 100) {} return <p>Time-consuming components</p>; } function Input() { const [num, updateNum] = useState(""); return ( <input type="text" onChange={(e) => updateNum()} value={num} /> ); } export default function App() { return ( <> <Input /> <ExpensiveCpn /> </> ); }
The components rendered in this way will only be<Input/>
The components inside will not affect the outside.
Summarize
The above methods can be understood from several aspects:
- Reduce the number of times you re-released: memo, useMemo, useCallback, avoid using inline objects, and separation between change and unchange.
- Reduce rendering nodes: fragments and components are lazy to load.
- Reduce rendering calculations: traversal attempts to use key.
This is the end of this article about the detailed explanation of the implementation method of React performance optimization. For more related React performance optimization content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!